ajuste no simulador; mapa dinamico; envio de controle do redis para o controle c#; troca do modo direcional; blindado trajetoria; tempo aguardando

This commit is contained in:
Diego Freitas 2026-07-10 16:27:16 -03:00
parent 4344cbf14a
commit 2936df66fa
11 changed files with 3328 additions and 1137 deletions

View File

@ -4,11 +4,13 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq; using Newtonsoft.Json.Linq;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Windows.Forms; using System.Windows.Forms;
using static AgroBase.Models.Enums; using static AgroBase.Models.Enums;
using System.Diagnostics;
namespace AgroBase.Forms namespace AgroBase.Forms
{ {
@ -31,6 +33,15 @@ namespace AgroBase.Forms
List<MPCSimulacaoModel> simulacao = new List<MPCSimulacaoModel>(); List<MPCSimulacaoModel> simulacao = new List<MPCSimulacaoModel>();
private int _atualizacaoTelaPendente;
private int _tickEmExecucao;
private readonly Stopwatch _cronometroTela = Stopwatch.StartNew();
private const int IntervaloTelaMs = 100; // 10 FPS para a UI
public frmSimulacaoMapaGPS() public frmSimulacaoMapaGPS()
{ {
InitializeComponent(); InitializeComponent();
@ -70,58 +81,96 @@ namespace AgroBase.Forms
private void frmSimulacaoMapaGPS_FormClosing(object sender, FormClosingEventArgs e) private void frmSimulacaoMapaGPS_FormClosing(object sender, FormClosingEventArgs e)
{ {
tmrLeitura?.Dispose(); tmrLeitura?.Dispose();
MapaDinamico?.Dispose();
Variaveis.OperacaoEmAndamento.Sensoriamento.Operacao.OperacaoIniciada = false; Variaveis.OperacaoEmAndamento.Sensoriamento.Operacao.OperacaoIniciada = false;
} }
DateTime t0 = DateTime.Now;
private async Task tmrLeitura_Tick() private async Task tmrLeitura_Tick()
{ {
double latencia_tick = (DateTime.Now - t0).TotalSeconds; if (Interlocked.Exchange(ref _tickEmExecucao, 1) == 1)
t0 = DateTime.Now; return;
int.TryParse(txtTempoGPS.Text, out int tempo);
if (tempo > 0) try
{ {
tempoGps = tempo; int tempo = 0;
bool automatico = false;
// Esses componentes pertencem à UI.
if (!IsDisposed && IsHandleCreated)
{
Invoke(new Action(() =>
{
int.TryParse(txtTempoGPS.Text, out tempo);
automatico = chbAutomatico.Checked;
}));
}
if (tempo > 0)
tempoGps = tempo;
if (automatico)
{
// Temporariamente mantém o passo na UI porque o método ainda
// acessa vários TextBox diretamente.
Invoke(new Action(() =>
{
btnAcrescentarGPS_Click(
btnAcrescentarGPS,
EventArgs.Empty
);
}));
}
if (_cronometroTela.ElapsedMilliseconds >= IntervaloTelaMs)
{
SolicitarAtualizacaoTela();
_cronometroTela.Restart();
}
// Não reduza para 1 ms quando estiver atrasado.
tmrLeitura.SetInterval(Math.Max((int)tempoGps, 10));
}
finally
{
Interlocked.Exchange(ref _tickEmExecucao, 0);
} }
if (!AtualizandoTela) await Task.CompletedTask;
}
private void SolicitarAtualizacaoTela()
{
if (IsDisposed || !IsHandleCreated)
return;
if (Interlocked.Exchange(ref _atualizacaoTelaPendente, 1) == 1)
return;
BeginInvoke(new Action(() =>
{ {
Func<bool> funcAtt = new Func<bool>(() => try
{ {
AtualizarDadosTela(); AtualizarDadosTela();
return true; }
}); catch (Exception ex)
Task.Run(() => FuncoesGlobais.ExecutarMetodoComVerificacaoCrossThread(this, funcAtt)); {
} Variaveis.MostrarLog(
"[Simulacao] Erro ao atualizar tela: " + ex
if (chbAutomatico.Checked) );
{ }
btnAcrescentarGPS_Click(btnAcrescentarGPS, new EventArgs()); finally
} {
Interlocked.Exchange(ref _atualizacaoTelaPendente, 0);
DateTime t1 = DateTime.Now; }
double latencia_exec = (t1 - t0).TotalSeconds; }));
double frequencia_exec = 1.0 / Math.Max(latencia_exec, 1e-6);
double frequencia_tick = 1.0 / Math.Max(latencia_tick, 1e-6);
//Console.WriteLine($"Tempo por tick GPS: {Math.Round(latencia_exec, 4)} s, Frequencia: {Math.Round(frequencia_exec, 2)} Hz | Tempo entre tick GPS: {Math.Round(latencia_tick, 4)}, Frequencia: {Math.Round(frequencia_tick, 2)} Hz");
double novoDelay = Math.Max(tempoGps - (latencia_exec * 1000.0), 1);
tmrLeitura.SetInterval((int)novoDelay);
} }
bool AtualizandoTela = false;
private void AtualizarDadosTela() private void AtualizarDadosTela()
{ {
if (AtualizandoTela)
return;
AtualizandoTela = true;
var _Controle = Variaveis.OperacaoEmAndamento.Controle; var _Controle = Variaveis.OperacaoEmAndamento.Controle;
var _Sensoriamento = Variaveis.OperacaoEmAndamento.Sensoriamento; var _Sensoriamento = Variaveis.OperacaoEmAndamento.Sensoriamento;
var _Trajetoria = Variaveis.OperacaoEmAndamento.Trajetoria; var _Trajetoria = Variaveis.OperacaoEmAndamento.Trajetoria;
if (_Trajetoria == null) if (_Trajetoria == null)
{ {
AtualizandoTela = false;
return; return;
} }
@ -132,13 +181,11 @@ namespace AgroBase.Forms
if (_Trajetoria == null) if (_Trajetoria == null)
{ {
Variaveis.OperacaoEmAndamento.Mapa.AtualizarRuasSelecionadas(); Variaveis.OperacaoEmAndamento.Mapa.AtualizarRuasSelecionadas();
AtualizandoTela = false;
return; return;
} }
if (_Sensoriamento.Trajetoria == null) if (_Sensoriamento.Trajetoria == null)
{ {
AtualizandoTela = false;
return; return;
} }
@ -202,8 +249,6 @@ namespace AgroBase.Forms
Variaveis.OperacaoEmAndamento.Mapa.AtualizarRuasSelecionadas(); Variaveis.OperacaoEmAndamento.Mapa.AtualizarRuasSelecionadas();
} }
AtualizandoTela = false;
} }
@ -239,20 +284,36 @@ namespace AgroBase.Forms
private void AtualizarPosicaoGPS() private void AtualizarPosicaoGPS()
{ {
GPSService.PenultimaLeitura = new GPSModel()
{
DataHora = GPSService.UltimaLeitura.DataHora,
Longitude = GPSService.UltimaLeitura.Longitude,
LongitudeAnt = GPSService.UltimaLeitura.LongitudeAnt,
Latitude = GPSService.UltimaLeitura.Latitude,
LatitudeAnt = GPSService.UltimaLeitura.LatitudeAnt,
Heartbeat = GPSService.UltimaLeitura.Heartbeat,
OrientacaoReal = GPSService.UltimaLeitura.OrientacaoReal
};
if (!GPSService.Iniciado || (txtLongitude.Text != "" && txtLatitude.Text != "")) if (!GPSService.Iniciado || (txtLongitude.Text != "" && txtLatitude.Text != ""))
{ {
GPSService.UltimaLeitura = posicaoAtual; double orientacaoReal = GPSUtils.NormalizarAngulo(posicaoAtual.OrientacaoReal);
GPSService.UltimaLeitura.Momento = posicaoAtual.Momento;
GPSService.UltimaLeitura.DataHora = posicaoAtual.DataHora;
GPSService.UltimaLeitura.UltimoComandoRespondido = posicaoAtual.UltimoComandoRespondido;
GPSService.UltimaLeitura.Latitude = posicaoAtual.Latitude;
GPSService.UltimaLeitura.Longitude = posicaoAtual.Longitude;
GPSService.UltimaLeitura.LatitudeAnt = posicaoAtual.LatitudeAnt;
GPSService.UltimaLeitura.LongitudeAnt = posicaoAtual.LongitudeAnt;
GPSService.UltimaLeitura.OrientacaoReal = orientacaoReal;
GPSService.UltimaLeitura.AnguloCarroDefinido = orientacaoReal;
GPSService.UltimaLeitura.TipoOrientacao = "A";
GPSService.UltimaLeitura.OrientacaoMovimento =
double.IsNaN(posicaoAtual.OrientacaoMovimento) || double.IsInfinity(posicaoAtual.OrientacaoMovimento)
? orientacaoReal
: GPSUtils.NormalizarAngulo(posicaoAtual.OrientacaoMovimento);
GPSService.UltimaLeitura.Distancia = posicaoAtual.Distancia;
GPSService.UltimaLeitura.Velocidade = posicaoAtual.Velocidade;
GPSService.UltimaLeitura.TimestampOri = posicaoAtual.TimestampOri.Clone();
GPSService.UltimaLeitura.TimestampPos = posicaoAtual.TimestampPos.Clone();
GPSService.UltimaLeitura.Heartbeat = posicaoAtual.Heartbeat;
GPSService.UltimaLeitura.Inicializado = true;
} }
GPSService.AtualizarAtrasoPosicoes(Convert.ToInt32(txtDelayPosicao.Text)); GPSService.AtualizarAtrasoPosicoes(Convert.ToInt32(txtDelayPosicao.Text));
@ -277,7 +338,7 @@ namespace AgroBase.Forms
// Obtém os parâmetros // Obtém os parâmetros
double anguloAtual = double.Parse(txtAnguloGPS.Text); double anguloAtual = double.Parse(txtAnguloGPS.Text);
var _Gps = Variaveis.OperacaoEmAndamento.Sensoriamento.Gps; var _Gps = GPSService.UltimaLeitura;
double anguloControle = 0.0; double anguloControle = 0.0;
TipoMovimentoDirecional tipoMovimento = Variaveis.OperacaoEmAndamento.Controle.TipoMovimento; TipoMovimentoDirecional tipoMovimento = Variaveis.OperacaoEmAndamento.Controle.TipoMovimento;
@ -328,8 +389,8 @@ namespace AgroBase.Forms
txtAnguloGPS.Text = posicaoAtual.OrientacaoReal.ToString("0.00"); txtAnguloGPS.Text = posicaoAtual.OrientacaoReal.ToString("0.00");
txtVelocidade.Text = veloccidade.ToString("0.00"); txtVelocidade.Text = veloccidade.ToString("0.00");
txtDistanciaGPS.Text = posicaoAtual.Distancia.ToString("0.0000"); txtDistanciaGPS.Text = posicaoAtual.Distancia.ToString("0.0000");
txtLatitude.Text = posicaoAtual.Latitude.ToString(); txtLatitude.Text = posicaoAtual.LatitudeAnt.ToString();
txtLongitude.Text = posicaoAtual.Longitude.ToString(); txtLongitude.Text = posicaoAtual.LongitudeAnt.ToString();
UltimaAtualizacaoGps = DateTime.Now; UltimaAtualizacaoGps = DateTime.Now;
@ -570,7 +631,7 @@ namespace AgroBase.Forms
Heartbeat = GPSService.UltimaLeitura.Heartbeat, Heartbeat = GPSService.UltimaLeitura.Heartbeat,
OrientacaoReal = double.Parse(txtAnguloGPS.Text.Replace(".", ",")), OrientacaoReal = double.Parse(txtAnguloGPS.Text.Replace(".", ",")),
}; };
GPSService.EnviarCoordenadasParaMapa(VariaveisOperacao.PosicaoBase.Latitude, VariaveisOperacao.PosicaoBase.Longitude, VariaveisOperacao.PosicaoBase.AnguloCarroDefinido, false, 0x01); GPSService.EnviarCoordenadasParaMapa(VariaveisOperacao.PosicaoBase.Latitude, VariaveisOperacao.PosicaoBase.Longitude, VariaveisOperacao.PosicaoBase.AnguloCarroDefinido, false, 0x02);
} }
private void btnLiberarCorredor_Click(object sender, EventArgs e) private void btnLiberarCorredor_Click(object sender, EventArgs e)

File diff suppressed because it is too large Load Diff

View File

@ -554,7 +554,6 @@ namespace AgroBase.Models
}; };
op.Controle = new OperacaoControleModel() op.Controle = new OperacaoControleModel()
{ {
TipoMovimento = TipoMovimentoDirecional.RodasDianteiras,
Angulo = 0, Angulo = 0,
EmFreio = false, EmFreio = false,
PercentualVelocidadeSP = 0, PercentualVelocidadeSP = 0,
@ -615,7 +614,7 @@ namespace AgroBase.Models
DirAuxilioSonar = true, DirAuxilioSonar = true,
MovAuxilioSonar = true, MovAuxilioSonar = true,
MpcHorizonte = 4.0, MpcHorizonte = 4.0,
AnteciparManobraCorredorM = 10.0, AnteciparManobraCorredorM = 0.0,
MovVelocidadeSErvasPercent = 50, MovVelocidadeSErvasPercent = 50,
MovVelocidadeCErvasPercent = 20, MovVelocidadeCErvasPercent = 20,
@ -695,7 +694,6 @@ namespace AgroBase.Models
}; };
op.Controle = new OperacaoControleModel() op.Controle = new OperacaoControleModel()
{ {
TipoMovimento = TipoMovimentoDirecional.RodasDianteiras,
Angulo = 0, Angulo = 0,
EmFreio = false, EmFreio = false,
PercentualVelocidadeSP = 0, PercentualVelocidadeSP = 0,
@ -836,7 +834,6 @@ namespace AgroBase.Models
}; };
op.Controle = new OperacaoControleModel() op.Controle = new OperacaoControleModel()
{ {
TipoMovimento = TipoMovimentoDirecional.RodasDianteiras,
Angulo = 0, Angulo = 0,
EmFreio = false, EmFreio = false,
PercentualVelocidadeSP = 0, PercentualVelocidadeSP = 0,
@ -894,7 +891,7 @@ namespace AgroBase.Models
DirAuxilioSonar = true, DirAuxilioSonar = true,
MovAuxilioSonar = true, MovAuxilioSonar = true,
MpcHorizonte = 4.0, MpcHorizonte = 4.0,
AnteciparManobraCorredorM = 10.0, AnteciparManobraCorredorM = 0.0,
MovVelocidadeSErvasPercent = 40, MovVelocidadeSErvasPercent = 40,
MovVelocidadeCErvasPercent = 20, MovVelocidadeCErvasPercent = 20,
@ -944,7 +941,6 @@ namespace AgroBase.Models
}; };
op.Controle = new OperacaoControleModel() op.Controle = new OperacaoControleModel()
{ {
TipoMovimento = TipoMovimentoDirecional.RodasDianteiras,
Angulo = 0, Angulo = 0,
EmFreio = false, EmFreio = false,
PercentualVelocidadeSP = 0, PercentualVelocidadeSP = 0,
@ -1059,7 +1055,6 @@ namespace AgroBase.Models
op.Parametros.ParametrosMandatorios = new List<OperacaoParametrosMandatoriosModel>(); op.Parametros.ParametrosMandatorios = new List<OperacaoParametrosMandatoriosModel>();
op.Controle = new OperacaoControleModel() op.Controle = new OperacaoControleModel()
{ {
TipoMovimento = TipoMovimentoDirecional.RodasDianteiras,
Angulo = 0, Angulo = 0,
EmFreio = false, EmFreio = false,
PercentualVelocidadeSP = 0, PercentualVelocidadeSP = 0,
@ -1093,7 +1088,7 @@ namespace AgroBase.Models
break; break;
} }
op.Controle.DefinirTipoMovimento(TipoMovimentoDirecional.RodasDianteiras, op.DispMvd?.Dados?.Modulos);
op.Parametros.ModulosMandatorios.ForEach(m => m.ComponentesEmUso = op.PreencherComponentesEmUso(m.Dispositivo)); op.Parametros.ModulosMandatorios.ForEach(m => m.ComponentesEmUso = op.PreencherComponentesEmUso(m.Dispositivo));
if (CarregarModulos) if (CarregarModulos)
{ {
@ -1799,40 +1794,10 @@ namespace AgroBase.Models
var dispMvd = DispMvd; var dispMvd = DispMvd;
if (dispMvd?.Dados?.Modulos != null && value != Controle.TipoMovimento) if (dispMvd?.Dados?.Modulos != null) // && value != Controle.TipoMovimento
{ {
switch (value) Controle.DefinirTipoMovimento(value, dispMvd.Dados.Modulos);
{
case TipoMovimentoDirecional.RodasDianteiras:
dispMvd.Dados.Modulos.ForEach(x =>
{
x.MovMotor.Comandar = true;
x.DirMotor.Comandar = x.Modulo_ID.Contains("F");
x.DirMotor.Angulo_SP = x.DirMotor.Comandar ? Controle.Angulo : 0;
});
break;
case TipoMovimentoDirecional.RodasTraseiras:
dispMvd.Dados.Modulos.ForEach(x =>
{
x.MovMotor.Comandar = true;
x.DirMotor.Comandar = x.Modulo_ID.Contains("T");
x.DirMotor.Angulo_SP = x.DirMotor.Comandar ? Controle.Angulo : 0;
});
break;
default:
dispMvd.Dados.Modulos.ForEach(x =>
{
x.MovMotor.Comandar = true;
x.DirMotor.Comandar = true;
x.DirMotor.Angulo_SP = Controle.Angulo;
});
break;
}
} }
Controle.TipoMovimento = value;
} }
public void AtualizaInformacoesControleOperacao() public void AtualizaInformacoesControleOperacao()
@ -1958,7 +1923,7 @@ namespace AgroBase.Models
//Variaveis.MostrarLog($"Angulo SP alterado de {_ControleAnterior.Angulo:F2} para {_Controle.Angulo:F2}"); //Variaveis.MostrarLog($"Angulo SP alterado de {_ControleAnterior.Angulo:F2} para {_Controle.Angulo:F2}");
_ControleAnterior.Angulo = _Controle.Angulo; _ControleAnterior.Angulo = _Controle.Angulo;
_ControleAnterior.TipoMovimento = _Controle.TipoMovimento; _ControleAnterior.DefinirTipoMovimento(_Controle.TipoMovimento);
RedisService.AtualizarCampos( RedisService.AtualizarCampos(
CtxKey.DadosControle, CtxKey.DadosControle,
@ -3640,7 +3605,45 @@ namespace AgroBase.Models
} }
public bool EmFreio { get; set; } = false; public bool EmFreio { get; set; } = false;
public double AlturaBarra { get; set; } public double AlturaBarra { get; set; }
public TipoMovimentoDirecional TipoMovimento { get; set; } = TipoMovimentoDirecional.Diagnostico; [JsonProperty]
public TipoMovimentoDirecional TipoMovimento { get; private set; } = TipoMovimentoDirecional.Diagnostico;
public void DefinirTipoMovimento(TipoMovimentoDirecional value, List<ModuloMvdModel> modulos = null)
{
if (modulos != null)
{
switch (value)
{
case TipoMovimentoDirecional.RodasDianteiras:
modulos.ForEach(x =>
{
x.MovMotor.Comandar = true;
x.DirMotor.Comandar = x.Modulo_ID.Contains("F");
x.DirMotor.Angulo_SP = x.DirMotor.Comandar ? Angulo : 0;
});
break;
case TipoMovimentoDirecional.RodasTraseiras:
modulos.ForEach(x =>
{
x.MovMotor.Comandar = true;
x.DirMotor.Comandar = x.Modulo_ID.Contains("T");
x.DirMotor.Angulo_SP = x.DirMotor.Comandar ? Angulo : 0;
});
break;
default:
modulos.ForEach(x =>
{
x.MovMotor.Comandar = true;
x.DirMotor.Comandar = true;
x.DirMotor.Angulo_SP = Angulo;
});
break;
}
}
TipoMovimento = value;
}
public List<OperacaoControleTipoModel> TiposControle { get; set; } = new List<OperacaoControleTipoModel>(); public List<OperacaoControleTipoModel> TiposControle { get; set; } = new List<OperacaoControleTipoModel>();
public List<MPCSimulacaoModel> SimulacaoMPC { get; set; } = new List<MPCSimulacaoModel>(); public List<MPCSimulacaoModel> SimulacaoMPC { get; set; } = new List<MPCSimulacaoModel>();
public double Latencia { get; set; } public double Latencia { get; set; }
@ -3648,6 +3651,10 @@ namespace AgroBase.Models
public Dictionary<string, ManagerWorkerMessageResponseDebugCustoModel> DebugCustoMpc { get; set; } = new Dictionary<string, ManagerWorkerMessageResponseDebugCustoModel>(); public Dictionary<string, ManagerWorkerMessageResponseDebugCustoModel> DebugCustoMpc { get; set; } = new Dictionary<string, ManagerWorkerMessageResponseDebugCustoModel>();
public List<string> Motivos { get; set; } = new List<string>(); public List<string> Motivos { get; set; } = new List<string>();
public string UltimaSessaoComando { get; set; }
public long? UltimaSeqMovimentoAplicada { get; set; }
public long? UltimaSeqDirecionalAplicada { get; set; }
public void Reiniciar() public void Reiniciar()
{ {
PercentualVelocidadeSP = 0; PercentualVelocidadeSP = 0;
@ -3668,6 +3675,8 @@ namespace AgroBase.Models
Angulo = Angulo, Angulo = Angulo,
PercentualVelocidadeSP = PercentualVelocidadeSP, PercentualVelocidadeSP = PercentualVelocidadeSP,
_pctVelSp = _pctVelSp,
TipoMovimento = TipoMovimento,
AlturaBarra = AlturaBarra, AlturaBarra = AlturaBarra,
heartbeat = heartbeat, heartbeat = heartbeat,
ticks_sem_resposta = ticks_sem_resposta, ticks_sem_resposta = ticks_sem_resposta,
@ -3687,7 +3696,11 @@ namespace AgroBase.Models
}).ToList() ?? new List<OperacaoControleTipoModel>(), }).ToList() ?? new List<OperacaoControleTipoModel>(),
SimulacaoMPC = new List<MPCSimulacaoModel>(SimulacaoMPC ?? new List<MPCSimulacaoModel>()), SimulacaoMPC = new List<MPCSimulacaoModel>(SimulacaoMPC ?? new List<MPCSimulacaoModel>()),
DebugCustoMpc = new Dictionary<string, ManagerWorkerMessageResponseDebugCustoModel>(DebugCustoMpc ?? new Dictionary<string, ManagerWorkerMessageResponseDebugCustoModel>()), DebugCustoMpc = new Dictionary<string, ManagerWorkerMessageResponseDebugCustoModel>(DebugCustoMpc ?? new Dictionary<string, ManagerWorkerMessageResponseDebugCustoModel>()),
Motivos = new List<string>(Motivos ?? new List<string>()) Motivos = new List<string>(Motivos ?? new List<string>()),
UltimaSessaoComando = UltimaSessaoComando,
UltimaSeqMovimentoAplicada = UltimaSeqMovimentoAplicada,
UltimaSeqDirecionalAplicada = UltimaSeqDirecionalAplicada
}; };
return clone; return clone;
@ -3787,19 +3800,33 @@ namespace AgroBase.Models
tempoDecorridoSeg = Math.Max(0, (fim - Operacao.DataInicio).TotalSeconds); tempoDecorridoSeg = Math.Max(0, (fim - Operacao.DataInicio).TotalSeconds);
} }
double tempoAguardandoRaw = RedisService.GetField<double>(CtxKey.DadosOperacao, "tempo_aguardando", 0.0); double tempoAguardandoInicioTs = RedisService.GetField<double>(CtxKey.DadosOperacao, "tempo_aguardando", 0.0);
double tempoAguardarInicio = RedisService.GetField<double>(CtxKey.DadosOperacao, "tempo_aguardar_inicio_operacao", op.TempoIniciarOperacao); double tempoAguardarInicio = RedisService.GetField<double>(CtxKey.DadosOperacao, "tempo_aguardar_inicio_operacao", op.TempoIniciarOperacao);
double agoraUnix = DateTimeOffset.Now.ToUnixTimeMilliseconds() / 1000.0; double agoraUnix = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() / 1000.0;
double tempoAguardandoSeg; double tempoRestanteInicioSeg = 0.0;
if (tempoAguardandoRaw > 1_000_000_000) if (tempoAguardandoInicioTs > 1_000_000_000)
{ {
// Redis está mandando timestamp Unix de início da espera. /*
tempoAguardandoSeg = Math.Max(0.0, agoraUnix - tempoAguardandoRaw); * tempo_aguardando é o timestamp Unix
* de quando a espera começou.
*/
double tempoDecorridoEsperaSeg = Math.Max(0.0, agoraUnix - tempoAguardandoInicioTs);
tempoRestanteInicioSeg = Math.Max(0.0, tempoAguardarInicio - tempoDecorridoEsperaSeg);
}
else if (tempoAguardandoInicioTs > 0.0)
{
/*
* Compatibilidade com formato antigo:
* considera o valor como tempo decorrido.
*/
tempoRestanteInicioSeg = Math.Max(0.0, tempoAguardarInicio - tempoAguardandoInicioTs);
} }
else else
{ {
// Compatibilidade: caso algum fluxo antigo mande duração em segundos. /*
tempoAguardandoSeg = Math.Max(0.0, tempoAguardandoRaw); * Ainda não existe timestamp de espera.
*/
tempoRestanteInicioSeg = Math.Max(0.0, tempoAguardarInicio);
} }
op.TempoIniciarOperacao = Math.Max(0, Convert.ToInt32(Math.Round(tempoAguardarInicio))); op.TempoIniciarOperacao = Math.Max(0, Convert.ToInt32(Math.Round(tempoAguardarInicio)));
@ -3819,7 +3846,7 @@ namespace AgroBase.Models
DataInicio = Operacao.DataInicio, DataInicio = Operacao.DataInicio,
DataFim = Operacao.DataFim, DataFim = Operacao.DataFim,
TempoDecorridoSeg = tempoDecorridoSeg, TempoDecorridoSeg = tempoDecorridoSeg,
TempoAguardandoSeg = tempoAguardandoSeg, TempoAguardandoSeg = tempoRestanteInicioSeg,
TipoCanAtivo = CanManager.TipoServico, TipoCanAtivo = CanManager.TipoServico,
OperacaoLiberada = RedisService.GetField(CtxKey.DadosOperacao, "liberado", false), OperacaoLiberada = RedisService.GetField(CtxKey.DadosOperacao, "liberado", false),
@ -3835,10 +3862,7 @@ namespace AgroBase.Models
ModsCalibragemView = Operacao.ModsCalibragem?.ToDictionary(x => x.Key.ToString(), x => new Dictionary<string, bool>(x.Value ?? new Dictionary<string, bool>())) ?? new Dictionary<string, Dictionary<string, bool>>(), ModsCalibragemView = Operacao.ModsCalibragem?.ToDictionary(x => x.Key.ToString(), x => new Dictionary<string, bool>(x.Value ?? new Dictionary<string, bool>())) ?? new Dictionary<string, Dictionary<string, bool>>(),
}; };
if (operacaoAtualizada.StatusOperacaoAtual != Operacao.StatusOperacaoAnterior) bool statusMudou = operacaoAtualizada.StatusOperacaoAtual != Operacao.StatusOperacaoAnterior;
{
AudioAlertaService.AlertaSonoroPorStatus(operacaoAtualizada.StatusOperacaoAtual);
}
operacaoAtualizada.StatusOperacaoAnterior = operacaoAtualizada.StatusOperacaoAtual; operacaoAtualizada.StatusOperacaoAnterior = operacaoAtualizada.StatusOperacaoAtual;
var saude = OperadorSaude.ModulosSaude; var saude = OperadorSaude.ModulosSaude;
@ -3877,6 +3901,11 @@ namespace AgroBase.Models
StatusModulo.Operante; StatusModulo.Operante;
Operacao = operacaoAtualizada; Operacao = operacaoAtualizada;
if (statusMudou)
{
AudioAlertaService.AlertaSonoroPorStatus(operacaoAtualizada.StatusOperacaoAtual);
}
} }
// Atu // Atu

View File

@ -49,6 +49,11 @@ namespace AgroBase.Models.Operadores
public int versao_comando { get; set; } = 1; public int versao_comando { get; set; } = 1;
public double ts_comando { get; set; } = 0; public double ts_comando { get; set; } = 0;
public string sessao_comando { get; set; }
public long? seq_movimento { get; set; }
public long? seq_direcional { get; set; }
public bool atualizar_movimento { get; set; } = true; public bool atualizar_movimento { get; set; } = true;
public bool atualizar_direcional { get; set; } = true; public bool atualizar_direcional { get; set; } = true;

File diff suppressed because it is too large Load Diff

View File

@ -27,7 +27,7 @@ namespace AgroBase.Models
{ {
public class Variaveis public class Variaveis
{ {
public static readonly bool IniciarWorkers = true; public static readonly bool IniciarWorkers = false;
public static readonly bool Producao = false; public static readonly bool Producao = false;
public static bool DebugMode { get; set; } = true; public static bool DebugMode { get; set; } = true;
public static bool Fechando { get; set; } = false; public static bool Fechando { get; set; } = false;
@ -859,8 +859,8 @@ namespace AgroBase.Models
public static string TopicoMqttParametros { get; } = $"agrobot/v1/rover/<id>/parameters"; public static string TopicoMqttParametros { get; } = $"agrobot/v1/rover/<id>/parameters";
public static double LarguraEsquerda { get; } = 44.0; // 62 public static double LarguraEsquerda { get; } = 44.0; // 62
public static double LarguraDireita { get; } = 44.0; // 22 public static double LarguraDireita { get; } = 44.0; // 22
public static double ComprimentoFrente { get; } = 7.0; // 7 public static double ComprimentoFrente { get; } = 90.0; // 7
public static double ComprimentoTras { get; } = 107.0; // 107 public static double ComprimentoTras { get; } = 40.0; // 107
public static double DistanciaEntreEixos { get; } = 92.0; public static double DistanciaEntreEixos { get; } = 92.0;
public static double LeverArmFrontalCm { get; } = 37.0; // cm public static double LeverArmFrontalCm { get; } = 37.0; // cm
public static double LeverArmLateralCm { get; } = 0.0; // cm public static double LeverArmLateralCm { get; } = 0.0; // cm
@ -882,7 +882,7 @@ namespace AgroBase.Models
public static int QuantidadeBicosPulverizadores { get; set; } = 7; public static int QuantidadeBicosPulverizadores { get; set; } = 7;
public static double AlturaBarraPulverizadoraCm { get; set; } = 80; public static double AlturaBarraPulverizadoraCm { get; set; } = 80;
public static double ComprimentoBarraPulverizadoraCm { get; set; } = 103.7; public static double ComprimentoBarraPulverizadoraCm { get; set; } = 103.7;
public static double DistanciaEntreBicosCm { get; set; } = 50; public static double DistanciaEntreBicosCm { get; set; } = 21;
public static double DensidadeHerbicida { get; set; } = 1.0; public static double DensidadeHerbicida { get; set; } = 1.0;
public static double CapacidadeReservatorio { get; set; } = 60; public static double CapacidadeReservatorio { get; set; } = 60;
public static double PressaoMinimaCavitacao { get; set; } = 10.0; public static double PressaoMinimaCavitacao { get; set; } = 10.0;
@ -962,8 +962,8 @@ namespace AgroBase.Models
{ TipoMovimentoDirecional.MovimentoLateral, 90 }, { TipoMovimentoDirecional.MovimentoLateral, 90 },
{ TipoMovimentoDirecional.Diagnostico, 25 }, { TipoMovimentoDirecional.Diagnostico, 25 },
}; };
public static double AnguloInclinacaoRollMax { get; set; } = 30.0; // Frontal public static double AnguloInclinacaoRollMax { get; set; } = 45.0; // Frontal
public static double AnguloInclinacaoPitchMax { get; set; } = 45.0; // Lateral public static double AnguloInclinacaoPitchMax { get; set; } = 25.0; // Lateral
public static string VozAlerta { get; set; } = "Microsoft Maria Desktop"; public static string VozAlerta { get; set; } = "Microsoft Maria Desktop";
@ -1170,32 +1170,51 @@ namespace AgroBase.Models
double dLat = (dy / R_earth) * 180.0 / Math.PI; double dLat = (dy / R_earth) * 180.0 / Math.PI;
double dLon = (dx / (R_earth * Math.Cos(pos.Latitude * Math.PI / 180.0))) * 180.0 / Math.PI; double dLon = (dx / (R_earth * Math.Cos(pos.Latitude * Math.PI / 180.0))) * 180.0 / Math.PI;
double latitude = pos.Latitude + dLat; double latitude = pos.LatitudeAnt + dLat;
double longitude = pos.Longitude + dLon; double longitude = pos.LongitudeAnt + dLon;
DateTime agora = DateTime.Now;
double agoraMono = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency;
double orientacaoRealDeg = GPSUtils.NormalizarAngulo(theta * 180.0 / Math.PI);
double orientacaoMovimentoDeg = GPSUtils.NormalizarAngulo(heading * 180.0 / Math.PI);
GPSModel novaPosicao = new GPSModel GPSModel novaPosicao = new GPSModel
{ {
Momento = DateTime.Now, Momento = agora,
DataHora = agora,
UltimoComandoRespondido = agora,
Lat0 = GPSService.UltimaLeitura.Lat0, Lat0 = GPSService.UltimaLeitura.Lat0,
Lon0 = GPSService.UltimaLeitura.Lon0, Lon0 = GPSService.UltimaLeitura.Lon0,
Latitude = latitude,
LatitudeAnt = latitude, LatitudeAnt = latitude,
Longitude = longitude,
LongitudeAnt = longitude, LongitudeAnt = longitude,
OrientacaoReal = GPSUtils.NormalizarAngulo(theta * 180.0 / Math.PI), OrientacaoReal = orientacaoRealDeg,
AnguloCarroDefinido = orientacaoRealDeg,
OrientacaoMovimento = orientacaoMovimentoDeg,
TipoOrientacao = "A",
Distancia = velocidadeMs * tempoDelta, Distancia = velocidadeMs * tempoDelta,
Velocidade = velocidadeMs, Velocidade = velocidadeMs,
Heartbeat = ultimaPosicao.Heartbeat, Heartbeat = ultimaPosicao.Heartbeat + 1,
TimestampOri = ultimaPosicao.TimestampOri.Clone(), TimestampOri = ultimaPosicao.TimestampOri.Clone(),
TimestampPos = ultimaPosicao.TimestampPos.Clone(), TimestampPos = ultimaPosicao.TimestampPos.Clone(),
}; };
var corrigida =
GPSService.LeverArm.FixLeverArmLatLon_Fast(
novaPosicao.LatitudeAnt,
novaPosicao.LongitudeAnt,
novaPosicao.OrientacaoReal,
5
);
novaPosicao.Latitude = corrigida.lat;
novaPosicao.Longitude = corrigida.lon;
//(double latCor, double lonCorr) = GeoLeverArm.FixLeverArmLatLon_Fast(latitude, longitude, novaPosicao.OrientacaoReal); //(double latCor, double lonCorr) = GeoLeverArm.FixLeverArmLatLon_Fast(latitude, longitude, novaPosicao.OrientacaoReal);
//novaPosicao.Latitude = latCor; //novaPosicao.Latitude = latCor;
//novaPosicao.Longitude = lonCorr; //novaPosicao.Longitude = lonCorr;
novaPosicao.TimestampOri.valor = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency; novaPosicao.TimestampOri.valor = agoraMono;
novaPosicao.TimestampPos.valor = Stopwatch.GetTimestamp() / (double)Stopwatch.Frequency; novaPosicao.TimestampPos.valor = agoraMono;
return novaPosicao; return novaPosicao;
} }

View File

@ -94,7 +94,7 @@ namespace AgroBase.Services
// ------- helpers locais ------- // ------- helpers locais -------
string SegundosFmt(double s) string SegundosFmt(double s)
{ {
int v = (int)Math.Round(s); int v = Math.Max(0, (int)Math.Ceiling(s));
return v == 1 ? "1 segundo" : $"{v} segundos"; return v == 1 ? "1 segundo" : $"{v} segundos";
} }

View File

@ -2189,28 +2189,15 @@ namespace AgroBase.Services
AtualizarTrajetoriaDinamica(); AtualizarTrajetoriaDinamica();
int enderecoEquipamento = 1; int enderecoEquipamento = 0x02;
EnviarCoordenadasParaMapa( EnviarCoordenadasParaMapa(UltimaLeitura.Latitude, UltimaLeitura.Longitude, UltimaLeitura.AnguloCarroDefinido, op.Sensoriamento.Operacao.OperacaoIniciada, enderecoEquipamento);
UltimaLeitura.Latitude,
UltimaLeitura.Longitude,
UltimaLeitura.AnguloCarroDefinido,
op.Sensoriamento.Operacao.OperacaoIniciada,
enderecoEquipamento
);
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))
{ {
int enderecoBase = 0; int enderecoBase = 0x01;
EnviarCoordenadasParaMapa(posicaoBase.Latitude, posicaoBase.Longitude, posicaoBase.OrientacaoReal, false, enderecoBase);
EnviarCoordenadasParaMapa(
posicaoBase.Latitude,
posicaoBase.Longitude,
posicaoBase.OrientacaoReal,
false,
enderecoBase
);
} }
PenultimaLeitura.Inicializado = UltimaLeitura.Inicializado; PenultimaLeitura.Inicializado = UltimaLeitura.Inicializado;
@ -2375,23 +2362,10 @@ namespace AgroBase.Services
{ {
if (UltimasLeituras.Count < 2) if (UltimasLeituras.Count < 2)
{ {
double ang = double ang = GPSUtils.CalcularOrientacao(PenultimaLeitura, UltimaLeitura);
GPSUtils.CalcularOrientacao( double d = GPSUtils.DistanciaEntrePontos(PenultimaLeitura, UltimaLeitura);
PenultimaLeitura, PenultimaLeitura.OrientacaoMovimento = UltimaLeitura.OrientacaoMovimento;
UltimaLeitura PenultimaLeitura.Distancia = UltimaLeitura.Distancia;
);
double d =
GPSUtils.DistanciaEntrePontos(
PenultimaLeitura,
UltimaLeitura
);
PenultimaLeitura.OrientacaoMovimento =
UltimaLeitura.OrientacaoMovimento;
PenultimaLeitura.Distancia =
UltimaLeitura.Distancia;
UltimaLeitura.OrientacaoMovimento = ang; UltimaLeitura.OrientacaoMovimento = ang;
UltimaLeitura.Distancia = d; UltimaLeitura.Distancia = d;
@ -2402,24 +2376,19 @@ namespace AgroBase.Services
double sumY = 0; double sumY = 0;
double distAcum = 0; double distAcum = 0;
for (int i = 0; for (int i = 0; i < UltimasLeituras.Count - 1; i++)
i < UltimasLeituras.Count - 1;
i++)
{ {
GPSModel a = UltimasLeituras[i]; GPSModel a = UltimasLeituras[i];
GPSModel b = UltimasLeituras[i + 1]; GPSModel b = UltimasLeituras[i + 1];
double angSeg = double angSeg = GPSUtils.CalcularOrientacao(a, b);
GPSUtils.CalcularOrientacao(a, b);
double dSeg = double dSeg = GPSUtils.DistanciaEntrePontos(a, b);
GPSUtils.DistanciaEntrePontos(a, b);
if (dSeg <= 0) if (dSeg <= 0)
continue; continue;
double rad = double rad = angSeg * Math.PI / 180.0;
angSeg * Math.PI / 180.0;
sumX += Math.Cos(rad) * dSeg; sumX += Math.Cos(rad) * dSeg;
sumY += Math.Sin(rad) * dSeg; sumY += Math.Sin(rad) * dSeg;
@ -2430,44 +2399,22 @@ namespace AgroBase.Services
if (distAcum <= 0) if (distAcum <= 0)
{ {
anguloMovimento = anguloMovimento = UltimaLeitura.OrientacaoReal;
UltimaLeitura.OrientacaoReal;
} }
else else
{ {
anguloMovimento = anguloMovimento = Math.Atan2(sumY, sumX) * 180.0 / Math.PI;
Math.Atan2(sumY, sumX) * anguloMovimento = GPSUtils.NormalizarAngulo(anguloMovimento);
180.0 / Math.PI;
anguloMovimento =
GPSUtils.NormalizarAngulo(
anguloMovimento
);
} }
GPSModel first = GPSModel first = UltimasLeituras[0];
UltimasLeituras[0]; GPSModel last = UltimasLeituras[UltimasLeituras.Count - 1];
GPSModel last = double distLinear = GPSUtils.DistanciaEntrePontos(first, last);
UltimasLeituras[
UltimasLeituras.Count - 1
];
double distLinear =
GPSUtils.DistanciaEntrePontos(
first,
last
);
PenultimaLeitura.OrientacaoMovimento =
UltimaLeitura.OrientacaoMovimento;
PenultimaLeitura.Distancia =
UltimaLeitura.Distancia;
UltimaLeitura.OrientacaoMovimento =
anguloMovimento;
PenultimaLeitura.OrientacaoMovimento = UltimaLeitura.OrientacaoMovimento;
PenultimaLeitura.Distancia = UltimaLeitura.Distancia;
UltimaLeitura.OrientacaoMovimento = anguloMovimento;
UltimaLeitura.Distancia = distLinear; UltimaLeitura.Distancia = distLinear;
} }

View File

@ -236,6 +236,10 @@ namespace AgroBase.Services.Operadores
versao_comando = GetInt(dictParams, "versao_comando", 1), versao_comando = GetInt(dictParams, "versao_comando", 1),
ts_comando = GetDouble(dictParams, "ts_comando", 0.0), ts_comando = GetDouble(dictParams, "ts_comando", 0.0),
sessao_comando = dictParams.Value<string>("sessao_comando"),
seq_movimento = dictParams["seq_movimento"]?.Value<long?>(),
seq_direcional = dictParams["seq_direcional"]?.Value<long?>(),
// Default true para compatibilidade com contrato antigo // Default true para compatibilidade com contrato antigo
atualizar_movimento = GetBool(dictParams, "atualizar_movimento", true), atualizar_movimento = GetBool(dictParams, "atualizar_movimento", true),
atualizar_direcional = GetBool(dictParams, "atualizar_direcional", true), atualizar_direcional = GetBool(dictParams, "atualizar_direcional", true),
@ -283,33 +287,80 @@ namespace AgroBase.Services.Operadores
var op = Variaveis.OperacaoEmAndamento; var op = Variaveis.OperacaoEmAndamento;
var _Controle = op.Controle; if (op == null)
var pControle = op.Parametros.Controle;
if (_Controle == null || pControle == null)
return; return;
_Controle.Motivos = (novoComando.motivos ?? new string[0]).ToList(); var controle = op.Controle;
var parametros = op.Parametros?.Controle;
bool podeAtualizarMovimento = if (controle == null || parametros == null)
novoComando.atualizar_movimento && return;
pControle.MovimentoAutomatico;
bool podeAtualizarDirecional = controle.Motivos = (novoComando.motivos ?? Array.Empty<string>()).ToList();
novoComando.atualizar_direcional &&
pControle.DirecionalAutomatico;
bool movimentoMudou = bool sessaoMudou = !string.IsNullOrWhiteSpace(novoComando.sessao_comando) && !string.Equals(controle.UltimaSessaoComando, novoComando.sessao_comando, StringComparison.Ordinal);
_Controle.PercentualVelocidadeSP != novoComando.percentual_velocidade ||
_Controle.EmFreio != novoComando.em_freio;
bool direcionalMudou = bool movimentoMudou = Math.Abs(controle.PercentualVelocidadeSP - novoComando.percentual_velocidade) >= 0.001 || controle.EmFreio != novoComando.em_freio;
_Controle.Angulo != novoComando.angulo ||
_Controle.TipoMovimento != novoComando.tipo_movimento;
if (podeAtualizarMovimento && (movimentoMudou || forcarAplicacao)) bool direcionalMudou = Math.Abs(controle.Angulo - novoComando.angulo) >= 0.001 || controle.TipoMovimento != novoComando.tipo_movimento;
bool seqMovimentoMudou =
novoComando.seq_movimento.HasValue &&
(
sessaoMudou ||
!controle.UltimaSeqMovimentoAplicada.HasValue ||
controle.UltimaSeqMovimentoAplicada.Value !=
novoComando.seq_movimento.Value
);
bool seqDirecionalMudou =
novoComando.seq_direcional.HasValue &&
(
sessaoMudou ||
!controle.UltimaSeqDirecionalAplicada.HasValue ||
controle.UltimaSeqDirecionalAplicada.Value !=
novoComando.seq_direcional.Value
);
/*
* Compatibilidade:
*
* Mensagem nova:
* sequência é a autoridade.
*
* Mensagem antiga:
* usa flag e mudança de valor.
*/
bool mensagemPossuiSequenciaMov = novoComando.seq_movimento.HasValue;
bool mensagemPossuiSequenciaDir = novoComando.seq_direcional.HasValue;
bool aplicarMovimento =
parametros.MovimentoAutomatico &&
(
forcarAplicacao ||
movimentoMudou ||
seqMovimentoMudou ||
(
!mensagemPossuiSequenciaMov &&
novoComando.atualizar_movimento
)
);
bool aplicarDirecional =
parametros.DirecionalAutomatico &&
(
forcarAplicacao ||
direcionalMudou ||
seqDirecionalMudou ||
(
!mensagemPossuiSequenciaDir &&
novoComando.atualizar_direcional
)
);
if (aplicarMovimento)
{ {
var tipoMov = _Controle.TiposControle.FirstOrDefault(x => x.Tipo == T_Code.Mov); var tipoMov = controle.TiposControle.FirstOrDefault(x => x.Tipo == T_Code.Mov);
if (tipoMov != null) if (tipoMov != null)
{ {
@ -319,15 +370,21 @@ namespace AgroBase.Services.Operadores
: Direcao.Parado; : Direcao.Parado;
} }
_Controle.PercentualVelocidadeSP = novoComando.percentual_velocidade; controle.PercentualVelocidadeSP = novoComando.percentual_velocidade;
_Controle.EmFreio = pControle.FrenagemAutomaticaAoParar && novoComando.em_freio;
op.AtualizarDadosControle(false, new List<T_Code>() { T_Code.Mov }); controle.EmFreio = parametros.FrenagemAutomaticaAoParar && novoComando.em_freio;
op.AtualizarDadosControle(false, new List<T_Code> { T_Code.Mov });
if (novoComando.seq_movimento.HasValue)
{
controle.UltimaSeqMovimentoAplicada = novoComando.seq_movimento.Value;
}
} }
if (podeAtualizarDirecional && (direcionalMudou || forcarAplicacao)) if (aplicarDirecional)
{ {
var tipoDir = _Controle.TiposControle.FirstOrDefault(x => x.Tipo == T_Code.Dir); var tipoDir = controle.TiposControle.FirstOrDefault(x => x.Tipo == T_Code.Dir);
if (tipoDir != null) if (tipoDir != null)
{ {
@ -339,26 +396,38 @@ namespace AgroBase.Services.Operadores
: Direcao.Parado; : Direcao.Parado;
} }
_Controle.Angulo = novoComando.angulo; controle.Angulo = novoComando.angulo;
_Controle.TipoMovimento = novoComando.tipo_movimento;
_Controle.SimulacaoMPC = novoComando.simulacao? op.DefinirTipoMovimentoControle(novoComando.tipo_movimento);
.Where(x => x != null && x.Length >= 3)
.Select(x => new MPCSimulacaoModel()
{
latitude = x[0],
longitude = x[1],
orientacao = x[2]
})
.ToList() ?? new List<MPCSimulacaoModel>();
_Controle.Latencia = novoComando.latencia; controle.SimulacaoMPC = novoComando.simulacao?
_Controle.ErroLateral = novoComando.erro_lateral; .Where(x => x != null && x.Length >= 3)
_Controle.DebugCustoMpc = .Select(
novoComando.debug_custo ?? x => new MPCSimulacaoModel
new Dictionary<string, ManagerWorkerMessageResponseDebugCustoModel>(); {
latitude = x[0],
longitude = x[1],
orientacao = x[2]
}
)
.ToList()
?? new List<MPCSimulacaoModel>();
op.AtualizarDadosControle(false, new List<T_Code>() { T_Code.Dir }); controle.Latencia = novoComando.latencia;
controle.ErroLateral = novoComando.erro_lateral;
controle.DebugCustoMpc = novoComando.debug_custo ?? new Dictionary<string, ManagerWorkerMessageResponseDebugCustoModel>();
op.AtualizarDadosControle(false, new List<T_Code> { T_Code.Dir });
if (novoComando.seq_direcional.HasValue)
{
controle.UltimaSeqDirecionalAplicada = novoComando.seq_direcional.Value;
}
}
if (!string.IsNullOrWhiteSpace(novoComando.sessao_comando))
{
controle.UltimaSessaoComando = novoComando.sessao_comando;
} }
} }

View File

@ -1,4 +1,5 @@
import time import time
import uuid
from shared.enums import TipoMovimentoDirecional from shared.enums import TipoMovimentoDirecional
from shared.contexto_global_redis import ContextoGlobalRedis from shared.contexto_global_redis import ContextoGlobalRedis
@ -16,7 +17,11 @@ class ProcessadorEmAndamento(ProcessadorBase):
def __init__(self): def __init__(self):
self.filtro_mov = FiltroVelocidade(alpha=0.7) self.filtro_mov = FiltroVelocidade(alpha=0.7)
ang_max = ContextoGlobalRedis.get_controle().get("angulo_max", 30) controle = ContextoGlobalRedis.get_controle() or {}
ang_max = self._to_float(
controle.get("angulo_max", 30.0),
30.0
)
self.pid_dir = PIDAdaptativo( self.pid_dir = PIDAdaptativo(
kp_base=0.55, kp_base=0.55,
@ -31,7 +36,14 @@ class ProcessadorEmAndamento(ProcessadorBase):
agora = time.perf_counter() agora = time.perf_counter()
# Cadências independentes # Identifica esta execução do Manager.
# Se o Python reiniciar, o C# perceberá uma sessão nova.
self.sessao_comando = uuid.uuid4().hex
# Sequências monotônicas por módulo.
self.seq_movimento = 0
self.seq_direcional = 0
self.ultimo_envio_envelope = agora self.ultimo_envio_envelope = agora
self.ultimo_envio_movimento = agora self.ultimo_envio_movimento = agora
self.ultimo_envio_direcional = agora self.ultimo_envio_direcional = agora
@ -40,81 +52,107 @@ class ProcessadorEmAndamento(ProcessadorBase):
self.intervalo_movimento_s = 0.25 self.intervalo_movimento_s = 0.25
self.intervalo_direcional_s = 0.30 self.intervalo_direcional_s = 0.30
# Memória local para evitar publicar movimento idêntico o tempo todo
self.ultima_velocidade_enviada = None self.ultima_velocidade_enviada = None
self.ultimo_freio_enviado = None self.ultimo_freio_enviado = None
self.ultimo_angulo_enviado = None self.ultimo_angulo_enviado = None
self.ultimo_tipo_enviado = None self.ultimo_tipo_enviado = None
# Evita spam de log de bloqueio
self.ultimo_log_bloqueio = 0.0 self.ultimo_log_bloqueio = 0.0
self.ultima_msg_bloqueio = "" self.ultima_msg_bloqueio = ""
self.regras = RegrasTaticas() self.regras = RegrasTaticas()
def processar(self): mostrar_log(
try: "🧭 ProcessadorEmAndamento criado | "
agora = time.perf_counter() f"id={id(self)} | "
f"sessao={self.sessao_comando} | "
f"perf={agora:.4f}"
)
_controle = ContextoGlobalRedis.get_controle() def processar(self):
agora = time.perf_counter()
try:
controle = ContextoGlobalRedis.get_controle() or {}
movimento_automatico = bool( movimento_automatico = bool(
_controle.get("movimento_automatico", False) controle.get("movimento_automatico", False)
) )
direcional_automatico = bool( direcional_automatico = bool(
_controle.get("direcional_automatico", False) controle.get("direcional_automatico", False)
) )
# Se nenhum controle automático está ativo, este processador não comanda nada.
if not movimento_automatico and not direcional_automatico: if not movimento_automatico and not direcional_automatico:
return None return None
valores_atuais = self._ler_valores_atuais(_controle) estado = self._ler_valores_atuais(controle)
velocidade = estado["velocidade"]
frear = estado["frear"]
angulo = estado["angulo"]
tipo_movimento = estado["tipo_movimento"]
simulacao = estado["simulacao"]
latencia = estado["latencia"]
erro_lateral = estado["erro_lateral"]
debug_custo = estado["debug_custo"]
motivos = [] motivos = []
erro_geral = False erro_geral = False
velocidade = valores_atuais["velocidade"]
frear = valores_atuais["frear"]
angulo = valores_atuais["angulo"]
tipo_movimento = valores_atuais["tipo_movimento"]
simulacao = valores_atuais["simulacao"]
latencia = valores_atuais["latencia"]
erro_lateral = valores_atuais["erro_lateral"]
debug_custo = valores_atuais["debug_custo"]
atualizar_movimento = False atualizar_movimento = False
atualizar_direcional = False atualizar_direcional = False
# ============================================================ # ============================================================
# 1) Regras táticas globais # 1. Bloqueios táticos
# ============================================================ # ============================================================
motivos_parada, freio_necessario = self.regras.verifica_controle_liberado() motivos_parada, freio_necessario = (
self.regras.verifica_controle_liberado()
)
motivos_parada = motivos_parada or [] motivos_parada = motivos_parada or []
if motivos_parada: if motivos_parada:
self._logar_bloqueio(motivos_parada) self._logar_bloqueio(motivos_parada)
# Bloqueio global: para movimento e centraliza direcional, if movimento_automatico:
# mas respeitando quais módulos estão em automático.
atualizar_movimento = movimento_automatico
atualizar_direcional = direcional_automatico
if atualizar_movimento:
velocidade = 0.0 velocidade = 0.0
frear = bool(freio_necessario) frear = bool(freio_necessario)
if atualizar_direcional: atualizar_movimento = (
self._movimento_mudou(
velocidade,
frear
)
or self._venceu_intervalo_movimento(agora)
)
if direcional_automatico:
angulo = 0.0 angulo = 0.0
tipo_movimento = TipoMovimentoDirecional.RodasDianteiras.value tipo_movimento = (
TipoMovimentoDirecional
.RodasDianteiras
.value
)
simulacao = [] simulacao = []
latencia = 0.0 latencia = 0.0
erro_lateral = 0.0 erro_lateral = 0.0
debug_custo = {} debug_custo = {}
return self._publicar_comando( atualizar_direcional = (
self._direcional_mudou(
angulo,
tipo_movimento
)
or self._venceu_intervalo_direcional(agora)
)
return self._publicar_se_necessario(
agora=agora, agora=agora,
velocidade=velocidade, velocidade=velocidade,
frear=frear, frear=frear,
@ -131,134 +169,69 @@ class ProcessadorEmAndamento(ProcessadorBase):
) )
# ============================================================ # ============================================================
# 2) Movimento automático # 2. Movimento
# ============================================================ # ============================================================
if movimento_automatico: if movimento_automatico:
comando_mov = definir_comando_mov(self.filtro_mov) comando_mov = definir_comando_mov(
self.filtro_mov
)
if isinstance(comando_mov, dict): if not isinstance(comando_mov, dict):
velocidade_calculada = self._to_float(
comando_mov.get("velocidade", velocidade),
velocidade
)
freio_calculado = bool(
comando_mov.get("frear", False)
)
velocidade = velocidade_calculada
frear = freio_calculado
movimento_mudou = self._movimento_mudou(
velocidade,
frear
)
envio_movimento_periodico = (
agora - self.ultimo_envio_movimento
) >= self.intervalo_movimento_s
atualizar_movimento = (
movimento_mudou or
envio_movimento_periodico
)
else:
# Falha ao calcular movimento automático: falha segura.
erro_geral = True erro_geral = True
motivos.append("Falha ao definir comando de movimento automático")
motivos.append(
"Falha ao definir comando de movimento automático"
)
velocidade = 0.0 velocidade = 0.0
frear = True frear = True
atualizar_movimento = True atualizar_movimento = True
else:
velocidade = self._to_float(
comando_mov.get(
"velocidade",
velocidade
),
velocidade
)
frear = bool(
comando_mov.get(
"frear",
frear
)
)
atualizar_movimento = (
self._movimento_mudou(
velocidade,
frear
)
or self._venceu_intervalo_movimento(agora)
)
# ============================================================ # ============================================================
# 3) Direcional automático # 3. Direcional
# ============================================================ # ============================================================
if direcional_automatico: if direcional_automatico:
envio_direcional_necessario = ( envio_periodico_necessario = (
agora - self.ultimo_envio_direcional self._venceu_intervalo_direcional(agora)
) >= self.intervalo_direcional_s )
comando_dir = definir_comando_dir( comando_dir = definir_comando_dir(
self.pid_dir, self.pid_dir,
envio_direcional_necessario envio_periodico_necessario
) )
if isinstance(comando_dir, dict): if not isinstance(comando_dir, dict):
erro_dir = bool(comando_dir.get("erro", False))
parada_necessaria = bool(
comando_dir.get("parada_necessaria", False)
)
if erro_dir:
erro_geral = True
motivos.extend(
comando_dir.get(
"motivos",
["Erro ao definir comando direcional automático"]
) or []
)
# Se o direcional falhou, não deixa o robô seguir andando
# sob movimento automático.
if movimento_automatico:
velocidade = 0.0
frear = True
atualizar_movimento = True
# Direcional vai para posição segura.
angulo = 0.0
tipo_movimento = TipoMovimentoDirecional.RodasDianteiras.value
simulacao = []
latencia = self._to_float(comando_dir.get("latencia", 0.0), 0.0)
erro_lateral = self._to_float(comando_dir.get("erro_lateral", 0.0), 0.0)
debug_custo = comando_dir.get("debug_custo", {}) or {}
atualizar_direcional = True
else:
enviar_direcional = bool(
comando_dir.get("enviar_comando", False)
)
if enviar_direcional:
angulo = self._to_float(
comando_dir.get("angulo", angulo),
angulo
)
tipo_movimento = self._to_int(
comando_dir.get("tipo", tipo_movimento),
tipo_movimento
)
simulacao = comando_dir.get("simulacao", []) or []
latencia = self._to_float(
comando_dir.get("latencia", latencia),
latencia
)
erro_lateral = self._to_float(
comando_dir.get("erro_lateral", erro_lateral),
erro_lateral
)
debug_custo = comando_dir.get("debug_custo", {}) or {}
atualizar_direcional = True
if parada_necessaria:
motivos.extend(comando_dir.get("motivos", []) or [])
if movimento_automatico:
velocidade = 0.0
frear = True
atualizar_movimento = True
else:
# Falha ao calcular direcional automático: falha segura.
erro_geral = True erro_geral = True
motivos.append("Falha ao definir comando direcional automático")
motivos.append(
"Falha ao definir comando direcional automático"
)
if movimento_automatico: if movimento_automatico:
velocidade = 0.0 velocidade = 0.0
@ -266,35 +239,204 @@ class ProcessadorEmAndamento(ProcessadorBase):
atualizar_movimento = True atualizar_movimento = True
angulo = 0.0 angulo = 0.0
tipo_movimento = TipoMovimentoDirecional.RodasDianteiras.value tipo_movimento = (
TipoMovimentoDirecional
.RodasDianteiras
.value
)
simulacao = [] simulacao = []
latencia = 0.0 latencia = 0.0
erro_lateral = 0.0 erro_lateral = 0.0
debug_custo = {} debug_custo = {}
atualizar_direcional = True atualizar_direcional = True
# ============================================================ else:
# 4) Publicação do envelope erro_dir = bool(
# ============================================================ comando_dir.get("erro", False)
)
precisa_keepalive = ( if erro_dir:
agora - self.ultimo_envio_envelope erro_geral = True
) >= self.intervalo_keepalive_s
deve_publicar = ( motivos.extend(
atualizar_movimento or comando_dir.get(
atualizar_direcional or "motivos",
precisa_keepalive [
) "Erro ao definir comando "
"direcional automático"
]
) or []
)
if not deve_publicar: if movimento_automatico:
return None velocidade = 0.0
frear = True
atualizar_movimento = True
# Keepalive puro: Manager vivo, mas nenhum módulo precisa reaplicar. angulo = 0.0
if precisa_keepalive and not atualizar_movimento and not atualizar_direcional: tipo_movimento = (
motivos = [] TipoMovimentoDirecional
.RodasDianteiras
.value
)
return self._publicar_comando( simulacao = []
latencia = self._to_float(
comando_dir.get(
"latencia",
0.0
),
0.0
)
erro_lateral = self._to_float(
comando_dir.get(
"erro_lateral",
0.0
),
0.0
)
debug_custo = (
comando_dir.get(
"debug_custo",
{}
)
or {}
)
atualizar_direcional = True
else:
angulo_calculado = self._to_float(
comando_dir.get(
"angulo",
angulo
),
angulo
)
tipo_calculado = self._to_int(
comando_dir.get(
"tipo",
tipo_movimento
),
tipo_movimento
)
simulacao_calculada = (
comando_dir.get(
"simulacao",
[]
)
or []
)
latencia_calculada = self._to_float(
comando_dir.get(
"latencia",
latencia
),
latencia
)
erro_lateral_calculado = self._to_float(
comando_dir.get(
"erro_lateral",
erro_lateral
),
erro_lateral
)
debug_calculado = (
comando_dir.get(
"debug_custo",
{}
)
or {}
)
solicitou_envio_mpc = bool(
comando_dir.get(
"enviar_comando",
False
)
)
direcional_mudou = (
self._direcional_mudou(
angulo_calculado,
tipo_calculado
)
)
envio_periodico = (
self._venceu_intervalo_direcional(
agora
)
)
atualizar_direcional = (
solicitou_envio_mpc
or direcional_mudou
or envio_periodico
)
angulo = angulo_calculado
tipo_movimento = tipo_calculado
simulacao = simulacao_calculada
latencia = latencia_calculada
erro_lateral = erro_lateral_calculado
debug_custo = debug_calculado
if not isinstance(debug_custo, dict):
debug_custo = {}
debug_custo[
"decisao_envio_direcional"
] = {
"solicitou_envio_mpc":
solicitou_envio_mpc,
"direcional_mudou":
direcional_mudou,
"envio_periodico":
envio_periodico,
"atualizar_direcional":
atualizar_direcional,
"angulo_calculado":
round(angulo, 3),
"angulo_ultimo_enviado": (
None
if self.ultimo_angulo_enviado is None
else round(
self.ultimo_angulo_enviado,
3
)
),
"tipo_calculado":
int(tipo_movimento),
"tipo_ultimo_enviado": (
self.ultimo_tipo_enviado
),
"seq_direcional_atual":
self.seq_direcional,
"sessao_comando":
self.sessao_comando,
}
return self._publicar_se_necessario(
agora=agora, agora=agora,
velocidade=velocidade, velocidade=velocidade,
frear=frear, frear=frear,
@ -310,30 +452,86 @@ class ProcessadorEmAndamento(ProcessadorBase):
atualizar_direcional=atualizar_direcional, atualizar_direcional=atualizar_direcional,
) )
except Exception as e: except Exception as ex:
mostrar_log(f"Erro no processador EmAndamento: {e}") mostrar_log(
f"Erro no processador EmAndamento: {ex}"
)
# Falha no orquestrador em operação automática:
# tenta publicar parada segura.
try: try:
return comando_controle( return self._publicar_comando(
percentual_velocidade=0.0, agora=agora,
velocidade=0.0,
frear=True, frear=True,
angulo=0.0, angulo=0.0,
tipo_movimento=TipoMovimentoDirecional.RodasDianteiras.value, tipo_movimento=(
TipoMovimentoDirecional
.RodasDianteiras
.value
),
simulacao=[], simulacao=[],
erro=True, erro=True,
latencia=0.0, latencia=0.0,
erro_lateral=0.0, erro_lateral=0.0,
debug_custo={}, debug_custo={},
motivos=[f"Erro no processador EmAndamento: {e}"], motivos=[
f"Erro no processador EmAndamento: {ex}"
],
atualizar_movimento=True, atualizar_movimento=True,
atualizar_direcional=True, atualizar_direcional=True,
) )
except Exception as e2:
mostrar_log(f"Erro ao publicar parada segura no EmAndamento: {e2}") except Exception as ex2:
mostrar_log(
"Erro ao publicar parada segura no "
f"EmAndamento: {ex2}"
)
return None return None
def _publicar_se_necessario(
self,
*,
agora,
velocidade,
frear,
angulo,
tipo_movimento,
simulacao,
erro,
latencia,
erro_lateral,
debug_custo,
motivos,
atualizar_movimento,
atualizar_direcional,
):
precisa_keepalive = (
agora - self.ultimo_envio_envelope
) >= self.intervalo_keepalive_s
if not (
atualizar_movimento
or atualizar_direcional
or precisa_keepalive
):
return None
return self._publicar_comando(
agora=agora,
velocidade=velocidade,
frear=frear,
angulo=angulo,
tipo_movimento=tipo_movimento,
simulacao=simulacao,
erro=erro,
latencia=latencia,
erro_lateral=erro_lateral,
debug_custo=debug_custo,
motivos=motivos,
atualizar_movimento=atualizar_movimento,
atualizar_direcional=atualizar_direcional,
)
def _publicar_comando( def _publicar_comando(
self, self,
*, *,
@ -351,6 +549,14 @@ class ProcessadorEmAndamento(ProcessadorBase):
atualizar_movimento, atualizar_movimento,
atualizar_direcional, atualizar_direcional,
): ):
# A sequência aumenta somente quando aquele módulo
# deve reaplicar o comando.
if atualizar_movimento:
self.seq_movimento += 1
if atualizar_direcional:
self.seq_direcional += 1
payload = comando_controle( payload = comando_controle(
percentual_velocidade=velocidade, percentual_velocidade=velocidade,
frear=frear, frear=frear,
@ -362,77 +568,189 @@ class ProcessadorEmAndamento(ProcessadorBase):
erro_lateral=erro_lateral, erro_lateral=erro_lateral,
debug_custo=debug_custo, debug_custo=debug_custo,
motivos=motivos or [], motivos=motivos or [],
atualizar_movimento=atualizar_movimento, atualizar_movimento=atualizar_movimento,
atualizar_direcional=atualizar_direcional, atualizar_direcional=atualizar_direcional,
seq_movimento=self.seq_movimento,
seq_direcional=self.seq_direcional,
sessao_comando=self.sessao_comando,
) )
self.ultimo_envio_envelope = agora self.ultimo_envio_envelope = agora
if atualizar_movimento: if atualizar_movimento:
self.ultimo_envio_movimento = agora self.ultimo_envio_movimento = agora
self.ultima_velocidade_enviada = velocidade self.ultima_velocidade_enviada = float(
self.ultimo_freio_enviado = frear velocidade
)
self.ultimo_freio_enviado = bool(frear)
if atualizar_direcional: if atualizar_direcional:
self.ultimo_envio_direcional = agora self.ultimo_envio_direcional = agora
self.ultimo_angulo_enviado = angulo self.ultimo_angulo_enviado = float(angulo)
self.ultimo_tipo_enviado = tipo_movimento self.ultimo_tipo_enviado = int(
tipo_movimento
)
return payload return payload
def _ler_valores_atuais(self, _controle): def _venceu_intervalo_movimento(self, agora):
return (
agora - self.ultimo_envio_movimento
) >= self.intervalo_movimento_s
def _venceu_intervalo_direcional(self, agora):
return (
agora - self.ultimo_envio_direcional
) >= self.intervalo_direcional_s
def _ler_valores_atuais(self, controle):
return { return {
"velocidade": self._to_float( "velocidade": self._to_float(
_controle.get("velocidade_sp", 0.0), controle.get(
0.0 "velocidade_sp",
), 0.0
"frear": bool(
_controle.get("em_freio", False)
),
"angulo": self._to_float(
_controle.get("angulo_sp", 0.0),
0.0
),
"tipo_movimento": self._to_int(
_controle.get(
"tipo_movimento_direcional",
TipoMovimentoDirecional.RodasDianteiras.value
), ),
TipoMovimentoDirecional.RodasDianteiras.value 0.0
), ),
"simulacao": _controle.get("simulacao", []) or [],
"frear": bool(
controle.get(
"em_freio",
False
)
),
"angulo": self._to_float(
controle.get(
"angulo_sp",
0.0
),
0.0
),
"tipo_movimento": self._to_int(
controle.get(
"tipo_movimento_direcional",
TipoMovimentoDirecional
.RodasDianteiras
.value
),
TipoMovimentoDirecional
.RodasDianteiras
.value
),
"simulacao": (
controle.get(
"simulacao",
[]
)
or []
),
"latencia": self._to_float( "latencia": self._to_float(
_controle.get("latencia", 0.0), controle.get(
"latencia",
0.0
),
0.0 0.0
), ),
"erro_lateral": self._to_float( "erro_lateral": self._to_float(
_controle.get("erro_lateral", 0.0), controle.get(
"erro_lateral",
0.0
),
0.0 0.0
), ),
"debug_custo": _controle.get("debug_custo", {}) or {},
"debug_custo": (
controle.get(
"debug_custo",
{}
)
or {}
),
} }
def _movimento_mudou(self, velocidade, frear): def _movimento_mudou(
self,
velocidade,
frear,
tolerancia_velocidade=0.05
):
if self.ultima_velocidade_enviada is None: if self.ultima_velocidade_enviada is None:
return True return True
if self.ultimo_freio_enviado is None: if self.ultimo_freio_enviado is None:
return True return True
if bool(frear) != bool(self.ultimo_freio_enviado): if bool(frear) != bool(
self.ultimo_freio_enviado
):
return True return True
return abs( return abs(
float(velocidade) - float(self.ultima_velocidade_enviada) float(velocidade)
) >= 0.05 - float(self.ultima_velocidade_enviada)
) >= tolerancia_velocidade
def _direcional_mudou(
self,
angulo,
tipo_movimento,
tolerancia_angulo=0.10
):
if self.ultimo_angulo_enviado is None:
return True
if self.ultimo_tipo_enviado is None:
return True
tipo_atual = self._to_int(
tipo_movimento,
TipoMovimentoDirecional
.RodasDianteiras
.value
)
tipo_anterior = self._to_int(
self.ultimo_tipo_enviado,
TipoMovimentoDirecional
.RodasDianteiras
.value
)
if tipo_atual != tipo_anterior:
return True
return abs(
float(angulo)
- float(self.ultimo_angulo_enviado)
) >= tolerancia_angulo
def _logar_bloqueio(self, motivos): def _logar_bloqueio(self, motivos):
agora = time.perf_counter() agora = time.perf_counter()
msg = " | ".join(map(str, motivos)) mensagem = " | ".join(
map(str, motivos)
)
if msg != self.ultima_msg_bloqueio or (agora - self.ultimo_log_bloqueio) >= 1.0: mudou = (
mostrar_log(f"⛔ Controle bloqueado: {msg}") mensagem != self.ultima_msg_bloqueio
self.ultima_msg_bloqueio = msg )
venceu_intervalo = (
agora - self.ultimo_log_bloqueio
) >= 1.0
if mudou or venceu_intervalo:
mostrar_log(
f"⛔ Controle bloqueado: {mensagem}"
)
self.ultima_msg_bloqueio = mensagem
self.ultimo_log_bloqueio = agora self.ultimo_log_bloqueio = agora
@staticmethod @staticmethod
@ -440,7 +758,9 @@ class ProcessadorEmAndamento(ProcessadorBase):
try: try:
if valor is None: if valor is None:
return float(padrao) return float(padrao)
return float(valor) return float(valor)
except Exception: except Exception:
return float(padrao) return float(padrao)
@ -454,5 +774,6 @@ class ProcessadorEmAndamento(ProcessadorBase):
return int(valor.value) return int(valor.value)
return int(valor) return int(valor)
except Exception: except Exception:
return int(padrao) return int(padrao)

View File

@ -29,8 +29,11 @@ def comando_controle(
erro_lateral: float = 0.0, erro_lateral: float = 0.0,
debug_custo=None, debug_custo=None,
motivos=None, motivos=None,
atualizar_movimento: bool = True, atualizar_movimento: bool = False,
atualizar_direcional: bool = True, atualizar_direcional: bool = False,
seq_movimento: int = 0,
seq_direcional: int = 0,
sessao_comando: str =None,
): ):
_controle = ContextoGlobalRedis.get_controle() _controle = ContextoGlobalRedis.get_controle()
@ -47,9 +50,14 @@ def comando_controle(
hb_novo = _proximo_heartbeat(hb_atual) hb_novo = _proximo_heartbeat(hb_atual)
payload = { payload = {
"versao_comando": 2, "versao_comando": 3,
"ts_comando": time.time(), "ts_comando": time.time(),
"sessao_comando": sessao_comando,
"seq_movimento": int(seq_movimento),
"seq_direcional": int(seq_direcional),
# Flags novas do contrato # Flags novas do contrato
"atualizar_movimento": bool(atualizar_movimento), "atualizar_movimento": bool(atualizar_movimento),
"atualizar_direcional": bool(atualizar_direcional), "atualizar_direcional": bool(atualizar_direcional),