ajustes no imu, resultados da operacao, controle manual, can service, requisicao sequencial bms, ordem dos arquivos versionamento
This commit is contained in:
parent
4dec95cbbb
commit
22d57ec06a
|
|
@ -238,7 +238,7 @@ namespace AgroBase.Forms.Operacoes
|
||||||
pnlMapa = pnlMapa
|
pnlMapa = pnlMapa
|
||||||
};
|
};
|
||||||
MapasService.CriarArquivoMapa(new List<List<GPSModel>>() { LogsGPS.ToList() }, 1517, data.id);
|
MapasService.CriarArquivoMapa(new List<List<GPSModel>>() { LogsGPS.ToList() }, 1517, data.id);
|
||||||
Mapa.btnCarregar_Click(sender, e, Variaveis.CaminhoSistema + "/" + Variaveis.CaminhoMapasConvertidos + data.id + ".json");
|
Mapa.btnCarregar_Click(sender, e, Path.Combine(Variaveis.CaminhoSistema, Variaveis.CaminhoMapasConvertidos + data.id + ".json"));
|
||||||
|
|
||||||
trbMomento.Maximum = LogsOperacao.Count - 1;
|
trbMomento.Maximum = LogsOperacao.Count - 1;
|
||||||
trbMomento.Minimum = 0;
|
trbMomento.Minimum = 0;
|
||||||
|
|
@ -1300,8 +1300,8 @@ namespace AgroBase.Forms.Operacoes
|
||||||
|
|
||||||
txtGPS_DistanciaBase.Text = ((LogBase?.Latitude != 0 && LogBase?.Longitude != 0) ? GPSUtils.DistanciaEntrePontos(LogBase ?? new GPSModel(), Log ?? new GPSModel()) : 0).ToString("0.00") + " m";
|
txtGPS_DistanciaBase.Text = ((LogBase?.Latitude != 0 && LogBase?.Longitude != 0) ? GPSUtils.DistanciaEntrePontos(LogBase ?? new GPSModel(), Log ?? new GPSModel()) : 0).ToString("0.00") + " m";
|
||||||
|
|
||||||
GPSService.EnviarCoordenadasParaMapa(Log?.Latitude ?? 0, Log?.Longitude ?? 0, Log?.AnguloCarroDefinido ?? 0, true, rover: true);
|
Mapa?.AtualizarPosicaoMapaAsync(MapasVariaveisModel.IDMarcadorRover, Log?.Latitude ?? 0, Log?.Longitude ?? 0, Log?.AnguloCarroDefinido ?? 0, true);
|
||||||
GPSService.EnviarCoordenadasParaMapa(LogBase?.Latitude ?? 0, LogBase?.Longitude ?? 0, LogBase?.AnguloCarroDefinido ?? 0, false, rover: false);
|
Mapa?.AtualizarPosicaoMapaAsync(MapasVariaveisModel.IDMarcadorBase, LogBase?.Latitude ?? 0, LogBase?.Longitude ?? 0, LogBase?.AnguloCarroDefinido ?? 0, false);
|
||||||
|
|
||||||
lblGPS.ForeColor = !Log.Inicializado ? Color.Red : Color.Black;
|
lblGPS.ForeColor = !Log.Inicializado ? Color.Red : Color.Black;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,12 +7,14 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
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;
|
||||||
|
|
||||||
namespace AgroBase
|
namespace AgroBase
|
||||||
{
|
{
|
||||||
|
/// <summary>Leitura consolidada e tolerante a falhas do controle manual.</summary>
|
||||||
public class GeneralJoystick
|
public class GeneralJoystick
|
||||||
{
|
{
|
||||||
public static List<string> JoysticksConectados = new List<string>();
|
public static List<string> JoysticksConectados = new List<string>();
|
||||||
|
|
@ -44,6 +46,19 @@ namespace AgroBase
|
||||||
new DeParaComandos() { key = Keys.D7, botaoJoy = BotoesJoystick.Touchpad, Dispositivo = T_Code.Atu, PararAoSoltar = true, IDcontrolar = "B07" },
|
new DeParaComandos() { key = Keys.D7, botaoJoy = BotoesJoystick.Touchpad, Dispositivo = T_Code.Atu, PararAoSoltar = true, IDcontrolar = "B07" },
|
||||||
};
|
};
|
||||||
public static List<BotoesJoystick> BotoesJoyPressionados = new List<BotoesJoystick>() { };
|
public static List<BotoesJoystick> BotoesJoyPressionados = new List<BotoesJoystick>() { };
|
||||||
|
private static readonly object _syncJoystick = new object();
|
||||||
|
private static readonly SemaphoreSlim _tickGate = new SemaphoreSlim(1, 1);
|
||||||
|
private static readonly HashSet<BotoesJoystick> _estadoBotoes = new HashSet<BotoesJoystick>();
|
||||||
|
private static DateTime _ultimaTentativaReconexao = DateTime.MinValue;
|
||||||
|
private static DateTime _ultimoEnvioMov = DateTime.MinValue;
|
||||||
|
private static DateTime _ultimoEnvioDir = DateTime.MinValue;
|
||||||
|
private static Direcao _ultimaDirecaoMovEnviada = Direcao.Parado;
|
||||||
|
private static double _ultimoValorMovEnviado = double.NaN;
|
||||||
|
private static double _ultimoAnguloDirEnviado = double.NaN;
|
||||||
|
private const int IntervaloReconexaoMs = 2000;
|
||||||
|
private const int HeartbeatComandoMs = 300;
|
||||||
|
private const double VariacaoMinimaMov = 0.25;
|
||||||
|
private const double VariacaoMinimaDir = 0.20;
|
||||||
public static List<TipoMovimentoDirecional> MovimentosCombinados = new List<TipoMovimentoDirecional>()
|
public static List<TipoMovimentoDirecional> MovimentosCombinados = new List<TipoMovimentoDirecional>()
|
||||||
{
|
{
|
||||||
TipoMovimentoDirecional.MovimentoLateral,
|
TipoMovimentoDirecional.MovimentoLateral,
|
||||||
|
|
@ -59,6 +74,7 @@ namespace AgroBase
|
||||||
public static void IniciarRotinas()
|
public static void IniciarRotinas()
|
||||||
{
|
{
|
||||||
tmrJoystick = new AsyncTaskTimerModel("tmrJoystick", tmrJoystick_Tick, 100);
|
tmrJoystick = new AsyncTaskTimerModel("tmrJoystick", tmrJoystick_Tick, 100);
|
||||||
|
tmrJoystick.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AtualizaDispositivo()
|
public static void AtualizaDispositivo()
|
||||||
|
|
@ -68,7 +84,7 @@ namespace AgroBase
|
||||||
DispCon = Variaveis.DispositivosConectados.Where(x => x.Dados.ConexaoAtiva).ToList();
|
DispCon = Variaveis.DispositivosConectados.Where(x => x.Dados.ConexaoAtiva).ToList();
|
||||||
frmInstancial.frmJoystick.lblDispositivo.Text = "Dispositivos: " + (DispCon.Count == 0 ? "N/A" : string.Join(", ", DispCon.Select(x => x._Descricao + " (" + Enum.GetName(typeof(T_Code), x.Dispositivo) + ")").ToArray()));
|
frmInstancial.frmJoystick.lblDispositivo.Text = "Dispositivos: " + (DispCon.Count == 0 ? "N/A" : string.Join(", ", DispCon.Select(x => x._Descricao + " (" + Enum.GetName(typeof(T_Code), x.Dispositivo) + ")").ToArray()));
|
||||||
|
|
||||||
InicializarJoysticksConectados();
|
AtualizarListaJoysticks();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
@ -77,9 +93,10 @@ namespace AgroBase
|
||||||
|
|
||||||
public static void InicializarJoysticksConectados()
|
public static void InicializarJoysticksConectados()
|
||||||
{
|
{
|
||||||
JoysticksConectados = new List<string>();
|
lock (_syncJoystick)
|
||||||
|
{
|
||||||
var directInput = new DirectInput();
|
var directInput = new DirectInput();
|
||||||
|
var nomes = new List<string>();
|
||||||
|
|
||||||
// Find a Joystick Guid
|
// Find a Joystick Guid
|
||||||
var joystickGuid = Guid.Empty;
|
var joystickGuid = Guid.Empty;
|
||||||
|
|
@ -87,7 +104,7 @@ namespace AgroBase
|
||||||
foreach (var deviceInstance in directInput.GetDevices(DeviceType.Gamepad, DeviceEnumerationFlags.AllDevices))
|
foreach (var deviceInstance in directInput.GetDevices(DeviceType.Gamepad, DeviceEnumerationFlags.AllDevices))
|
||||||
{
|
{
|
||||||
joystickGuid = deviceInstance.InstanceGuid;
|
joystickGuid = deviceInstance.InstanceGuid;
|
||||||
JoysticksConectados.Add(deviceInstance.InstanceName);
|
nomes.Add(deviceInstance.InstanceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Gamepad not found, look for a Joystick
|
// If Gamepad not found, look for a Joystick
|
||||||
|
|
@ -96,21 +113,22 @@ namespace AgroBase
|
||||||
foreach (var deviceInstance in directInput.GetDevices(DeviceType.Joystick, DeviceEnumerationFlags.AllDevices))
|
foreach (var deviceInstance in directInput.GetDevices(DeviceType.Joystick, DeviceEnumerationFlags.AllDevices))
|
||||||
{
|
{
|
||||||
joystickGuid = deviceInstance.InstanceGuid;
|
joystickGuid = deviceInstance.InstanceGuid;
|
||||||
JoysticksConectados.Add(deviceInstance.InstanceName);
|
nomes.Add(deviceInstance.InstanceName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If Joystick not found, throws an error
|
// If Joystick not found, throws an error
|
||||||
if (joystickGuid == Guid.Empty)
|
if (joystickGuid == Guid.Empty)
|
||||||
{
|
{
|
||||||
JoystickConectado = null;
|
JoysticksConectados = nomes;
|
||||||
tmrJoystick?.Stop();
|
DesconectarJoystickInterno();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Instantiate the joystick
|
// Instantiate the joystick
|
||||||
|
DesconectarJoystickInterno();
|
||||||
JoystickConectado = new Joystick(directInput, joystickGuid);
|
JoystickConectado = new Joystick(directInput, joystickGuid);
|
||||||
|
JoysticksConectados = nomes;
|
||||||
|
|
||||||
// Query all suported ForceFeedback effects
|
// Query all suported ForceFeedback effects
|
||||||
var allEffects = JoystickConectado.GetEffects();
|
var allEffects = JoystickConectado.GetEffects();
|
||||||
|
|
@ -120,17 +138,45 @@ namespace AgroBase
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set BufferSize in order to use buffered data.
|
// Set BufferSize in order to use buffered data.
|
||||||
JoystickConectado.Properties.BufferSize = 128;
|
|
||||||
|
|
||||||
// Acquire the joystick
|
|
||||||
JoystickConectado.Acquire();
|
JoystickConectado.Acquire();
|
||||||
|
|
||||||
|
|
||||||
if (JoystickConectado != null)
|
if (JoystickConectado != null)
|
||||||
{
|
{
|
||||||
tmrJoystick.Start();
|
tmrJoystick?.Start();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AtualizarListaJoysticks()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var directInput = new DirectInput())
|
||||||
|
{
|
||||||
|
var nomes = directInput.GetDevices(DeviceType.Gamepad, DeviceEnumerationFlags.AllDevices)
|
||||||
|
.Concat(directInput.GetDevices(DeviceType.Joystick, DeviceEnumerationFlags.AllDevices))
|
||||||
|
.Select(x => x.InstanceName)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
lock (_syncJoystick) JoysticksConectados = nomes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[GeneralJoystick] Falha ao listar joysticks: {ex.GetType().Name}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DesconectarJoystickInterno()
|
||||||
|
{
|
||||||
|
var antigo = JoystickConectado;
|
||||||
|
JoystickConectado = null;
|
||||||
|
_estadoBotoes.Clear();
|
||||||
|
BotoesJoyPressionados.Clear();
|
||||||
|
if (antigo == null) return;
|
||||||
|
try { antigo.Unacquire(); } catch { }
|
||||||
|
try { antigo.Dispose(); } catch { }
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ComparaPosicaoAnalogico(int Atual, ComparativoAnalogico Comparativo)
|
private static bool ComparaPosicaoAnalogico(int Atual, ComparativoAnalogico Comparativo)
|
||||||
|
|
@ -173,6 +219,9 @@ namespace AgroBase
|
||||||
|
|
||||||
private static async Task tmrJoystick_Tick()
|
private static async Task tmrJoystick_Tick()
|
||||||
{
|
{
|
||||||
|
await ProcessarEstadoAtualJoystick();
|
||||||
|
return;
|
||||||
|
#pragma warning disable CS0162
|
||||||
if (JoystickConectado == null)
|
if (JoystickConectado == null)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
|
|
@ -529,6 +578,155 @@ namespace AgroBase
|
||||||
//Console.WriteLine($"Botao solto: {botao}");
|
//Console.WriteLine($"Botao solto: {botao}");
|
||||||
BotoesJoyPressionados.Remove(botao);
|
BotoesJoyPressionados.Remove(botao);
|
||||||
});
|
});
|
||||||
|
#pragma warning restore CS0162
|
||||||
|
}
|
||||||
|
|
||||||
|
private static async Task ProcessarEstadoAtualJoystick()
|
||||||
|
{
|
||||||
|
if (!await _tickGate.WaitAsync(0)) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Joystick joystick;
|
||||||
|
lock (_syncJoystick) joystick = JoystickConectado;
|
||||||
|
|
||||||
|
if (joystick == null)
|
||||||
|
{
|
||||||
|
TentarReconectar();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
JoystickState estado;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
joystick.Poll();
|
||||||
|
estado = joystick.GetCurrentState();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[GeneralJoystick] Controle desconectado/falha de leitura: {ex.GetType().Name}: {ex.Message}");
|
||||||
|
PararCarroControle("Perda de comunicação com o joystick");
|
||||||
|
lock (_syncJoystick) DesconectarJoystickInterno();
|
||||||
|
TentarReconectar();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AtualizarEstadoFisico(estado);
|
||||||
|
ResolverMovimento(estado);
|
||||||
|
ResolverDirecional(estado);
|
||||||
|
ResolverComandosDeBorda();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[GeneralJoystick] Erro inesperado no tick: {ex.GetType().Name}: {ex.Message}");
|
||||||
|
PararCarroControle("Falha interna no processamento do joystick");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_tickGate.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void TentarReconectar()
|
||||||
|
{
|
||||||
|
if ((DateTime.Now - _ultimaTentativaReconexao).TotalMilliseconds < IntervaloReconexaoMs) return;
|
||||||
|
_ultimaTentativaReconexao = DateTime.Now;
|
||||||
|
try { InicializarJoysticksConectados(); }
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog($"[GeneralJoystick] Reconexão falhou: {ex.GetType().Name}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AtualizarEstadoFisico(JoystickState s)
|
||||||
|
{
|
||||||
|
var anterior = new HashSet<BotoesJoystick>(_estadoBotoes);
|
||||||
|
_estadoBotoes.Clear();
|
||||||
|
var buttons = s.Buttons ?? new bool[0];
|
||||||
|
Action<int, BotoesJoystick> addButton = (i, b) => { if (i < buttons.Length && buttons[i]) _estadoBotoes.Add(b); };
|
||||||
|
addButton(0, BotoesJoystick.Quadrado); addButton(1, BotoesJoystick.Xis);
|
||||||
|
addButton(2, BotoesJoystick.Bolinha); addButton(3, BotoesJoystick.Triangulo);
|
||||||
|
addButton(4, BotoesJoystick.L1); addButton(5, BotoesJoystick.R1);
|
||||||
|
addButton(6, BotoesJoystick.L2); addButton(7, BotoesJoystick.R2);
|
||||||
|
addButton(8, BotoesJoystick.Share); addButton(9, BotoesJoystick.Options);
|
||||||
|
addButton(10, BotoesJoystick.L3); addButton(11, BotoesJoystick.R3);
|
||||||
|
addButton(12, BotoesJoystick.PS); addButton(13, BotoesJoystick.Touchpad);
|
||||||
|
|
||||||
|
if (s.RotationX > MargemAnalogico) _estadoBotoes.Add(BotoesJoystick.L2A);
|
||||||
|
if (s.RotationY > MargemAnalogico) _estadoBotoes.Add(BotoesJoystick.R2A);
|
||||||
|
if (s.X < MaximoAnalogico / 2 - 3000) _estadoBotoes.Add(BotoesJoystick.AEEsquerda);
|
||||||
|
if (s.X > MaximoAnalogico / 2 + 3000) _estadoBotoes.Add(BotoesJoystick.AEDireita);
|
||||||
|
|
||||||
|
int pov = s.PointOfViewControllers != null && s.PointOfViewControllers.Length > 0 ? s.PointOfViewControllers[0] : -1;
|
||||||
|
if (pov == 27000) _estadoBotoes.Add(BotoesJoystick.SEsquerda);
|
||||||
|
if (pov == 9000) _estadoBotoes.Add(BotoesJoystick.SDireita);
|
||||||
|
if (pov == 0) _estadoBotoes.Add(BotoesJoystick.SCima);
|
||||||
|
if (pov == 18000) _estadoBotoes.Add(BotoesJoystick.SBaixo);
|
||||||
|
|
||||||
|
_botoesPressionadosNoTick = new HashSet<BotoesJoystick>(_estadoBotoes.Except(anterior));
|
||||||
|
_botoesSoltosNoTick = new HashSet<BotoesJoystick>(anterior.Except(_estadoBotoes));
|
||||||
|
BotoesJoyPressionados = _estadoBotoes.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static HashSet<BotoesJoystick> _botoesPressionadosNoTick = new HashSet<BotoesJoystick>();
|
||||||
|
private static HashSet<BotoesJoystick> _botoesSoltosNoTick = new HashSet<BotoesJoystick>();
|
||||||
|
|
||||||
|
private static void ResolverMovimento(JoystickState s)
|
||||||
|
{
|
||||||
|
bool frenteDigital = _estadoBotoes.Contains(BotoesJoystick.Xis);
|
||||||
|
bool trasDigital = _estadoBotoes.Contains(BotoesJoystick.Quadrado);
|
||||||
|
double frenteAnalogico = s.RotationY > MargemAnalogico ? s.RotationY : 0;
|
||||||
|
double trasAnalogico = s.RotationX > MargemAnalogico ? s.RotationX : 0;
|
||||||
|
bool conflito = (frenteDigital || frenteAnalogico > 0) && (trasDigital || trasAnalogico > 0);
|
||||||
|
|
||||||
|
BotoesJoystick botao;
|
||||||
|
double valor;
|
||||||
|
Direcao direcao;
|
||||||
|
if (conflito)
|
||||||
|
{
|
||||||
|
botao = BotoesJoystick.Xis; valor = 0; direcao = Direcao.Parado;
|
||||||
|
RegistrarMotivoManual("Comandos simultâneos de frente e ré; movimento neutralizado");
|
||||||
|
}
|
||||||
|
else if (frenteAnalogico > 0) { botao = BotoesJoystick.R2A; valor = frenteAnalogico; direcao = Direcao.Frente; }
|
||||||
|
else if (trasAnalogico > 0) { botao = BotoesJoystick.L2A; valor = trasAnalogico; direcao = Direcao.Tras; }
|
||||||
|
else if (frenteDigital) { botao = BotoesJoystick.Xis; valor = MaximoAnalogico; direcao = Direcao.Frente; }
|
||||||
|
else if (trasDigital) { botao = BotoesJoystick.Quadrado; valor = MaximoAnalogico; direcao = Direcao.Tras; }
|
||||||
|
else { botao = BotoesJoystick.Xis; valor = 0; direcao = Direcao.Parado; }
|
||||||
|
|
||||||
|
var cmd = DeParaComandos.First(x => x.botaoJoy == botao).Clone();
|
||||||
|
cmd.CalcularValor(direcao == Direcao.Parado, valorJoy: valor);
|
||||||
|
bool mudou = direcao != _ultimaDirecaoMovEnviada || double.IsNaN(_ultimoValorMovEnviado) || Math.Abs(cmd.ValorAplicar - _ultimoValorMovEnviado) >= VariacaoMinimaMov;
|
||||||
|
bool heartbeat = (DateTime.Now - _ultimoEnvioMov).TotalMilliseconds >= HeartbeatComandoMs;
|
||||||
|
if (!mudou && !heartbeat) return;
|
||||||
|
EnviarComandoMotorAtualizado(cmd, ForcarComando: false);
|
||||||
|
_ultimaDirecaoMovEnviada = direcao; _ultimoValorMovEnviado = cmd.ValorAplicar; _ultimoEnvioMov = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ResolverDirecional(JoystickState s)
|
||||||
|
{
|
||||||
|
bool esqDigital = _estadoBotoes.Contains(BotoesJoystick.SEsquerda);
|
||||||
|
bool dirDigital = _estadoBotoes.Contains(BotoesJoystick.SDireita);
|
||||||
|
BotoesJoystick botao = BotoesJoystick.SEsquerda;
|
||||||
|
double valor = 0;
|
||||||
|
bool solto = true;
|
||||||
|
if (esqDigital ^ dirDigital) { botao = esqDigital ? BotoesJoystick.SEsquerda : BotoesJoystick.SDireita; solto = false; }
|
||||||
|
else if (_estadoBotoes.Contains(BotoesJoystick.AEEsquerda)) { botao = BotoesJoystick.AEEsquerda; valor = s.X; solto = false; }
|
||||||
|
else if (_estadoBotoes.Contains(BotoesJoystick.AEDireita)) { botao = BotoesJoystick.AEDireita; valor = s.X; solto = false; }
|
||||||
|
|
||||||
|
var cmd = DeParaComandos.First(x => x.botaoJoy == botao).Clone();
|
||||||
|
cmd.CalcularValor(solto, valorJoy: valor);
|
||||||
|
bool mudou = double.IsNaN(_ultimoAnguloDirEnviado) || Math.Abs(cmd.ValorAplicar - _ultimoAnguloDirEnviado) >= VariacaoMinimaDir;
|
||||||
|
bool heartbeat = (DateTime.Now - _ultimoEnvioDir).TotalMilliseconds >= HeartbeatComandoMs;
|
||||||
|
if (!mudou && !heartbeat) return;
|
||||||
|
EnviarComandoMotorAtualizado(cmd, ForcarComando: false);
|
||||||
|
_ultimoAnguloDirEnviado = cmd.ValorAplicar; _ultimoEnvioDir = DateTime.Now;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ResolverComandosDeBorda()
|
||||||
|
{
|
||||||
|
foreach (var b in _botoesPressionadosNoTick.Where(x => x == BotoesJoystick.L1 || x == BotoesJoystick.R1 || x == BotoesJoystick.R3 || x == BotoesJoystick.Bolinha))
|
||||||
|
ProcessarDadosControle(b, false, ForcarComando: false);
|
||||||
|
if (_botoesSoltosNoTick.Contains(BotoesJoystick.Bolinha))
|
||||||
|
ProcessarDadosControle(BotoesJoystick.Bolinha, true, ForcarComando: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ProcessarDadosControle(BotoesJoystick botao, bool solto, Keys? key = null, double? valorJoy = null, double? valorDesejado = null, string ID = "", bool ForcarComando = false)
|
public static void ProcessarDadosControle(BotoesJoystick botao, bool solto, Keys? key = null, double? valorJoy = null, double? valorDesejado = null, string ID = "", bool ForcarComando = false)
|
||||||
|
|
@ -590,12 +788,16 @@ namespace AgroBase
|
||||||
{
|
{
|
||||||
var op = Variaveis.OperacaoEmAndamento;
|
var op = Variaveis.OperacaoEmAndamento;
|
||||||
|
|
||||||
if (op?.Controle == null)
|
if (op?.Controle == null || op.Parametros?.Controle == null || op.Sensoriamento?.Operacao == null || Comando == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
bool solicitacaoAtiva = Comando.ValorAplicar != 0 || Comando.EstadoAplicar ||
|
||||||
|
Comando.direcao == Direcao.Referenciar;
|
||||||
|
|
||||||
bool bloqueioAutomatico =
|
bool bloqueioAutomatico =
|
||||||
(op.Parametros.Controle.MovimentoAutomatico && Comando.Dispositivo == T_Code.Mov) ||
|
!ForcarComando &&
|
||||||
(op.Parametros.Controle.DirecionalAutomatico && Comando.Dispositivo == T_Code.Dir);
|
((op.Parametros.Controle.MovimentoAutomatico && Comando.Dispositivo == T_Code.Mov) ||
|
||||||
|
(op.Parametros.Controle.DirecionalAutomatico && Comando.Dispositivo == T_Code.Dir));
|
||||||
|
|
||||||
bool bloqueioCalibragem =
|
bool bloqueioCalibragem =
|
||||||
op.CalibrandoRuntime;
|
op.CalibrandoRuntime;
|
||||||
|
|
@ -607,7 +809,7 @@ namespace AgroBase
|
||||||
bool comandoParada =
|
bool comandoParada =
|
||||||
Comando.ValorAplicar == 0;
|
Comando.ValorAplicar == 0;
|
||||||
|
|
||||||
bool bloqueioSonar = !VerificaComandoValidoSonar(Comando.key);
|
bool bloqueioSonar = !comandoParada && !VerificaComandoValidoSonar(Comando.key);
|
||||||
|
|
||||||
bool deveInterromper =
|
bool deveInterromper =
|
||||||
bloqueioAutomatico ||
|
bloqueioAutomatico ||
|
||||||
|
|
@ -619,6 +821,10 @@ namespace AgroBase
|
||||||
|
|
||||||
if (deveInterromper)
|
if (deveInterromper)
|
||||||
{
|
{
|
||||||
|
// MotivosManual descreve somente uma tentativa ativa barrada.
|
||||||
|
// Solturas, neutralizações e comandos de parada não sobrescrevem o diagnóstico.
|
||||||
|
if (!solicitacaoAtiva) return;
|
||||||
|
|
||||||
_Controle.MotivosManual.Clear();
|
_Controle.MotivosManual.Clear();
|
||||||
|
|
||||||
if (bloqueioAutomatico)
|
if (bloqueioAutomatico)
|
||||||
|
|
@ -642,6 +848,10 @@ namespace AgroBase
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Uma solicitação ativa aceita invalida imediatamente qualquer bloqueio antigo.
|
||||||
|
if (solicitacaoAtiva)
|
||||||
|
_Controle.MotivosManual.Clear();
|
||||||
|
|
||||||
if (Comando.direcao.HasValue && _Controle.TiposControle.Any(x => x.Tipo == Comando.Dispositivo))
|
if (Comando.direcao.HasValue && _Controle.TiposControle.Any(x => x.Tipo == Comando.Dispositivo))
|
||||||
_Controle.TiposControle.FirstOrDefault(x => x.Tipo == Comando.Dispositivo).DirecaoAtual = Comando.direcao.Value;
|
_Controle.TiposControle.FirstOrDefault(x => x.Tipo == Comando.Dispositivo).DirecaoAtual = Comando.direcao.Value;
|
||||||
|
|
||||||
|
|
@ -677,6 +887,15 @@ namespace AgroBase
|
||||||
AtualizaConsole(_Controle.TiposControle.FirstOrDefault(x => x.Tipo == Comando.Dispositivo).DirecaoAtual.ToString());
|
AtualizaConsole(_Controle.TiposControle.FirstOrDefault(x => x.Tipo == Comando.Dispositivo).DirecaoAtual.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void RegistrarMotivoManual(string motivo)
|
||||||
|
{
|
||||||
|
var motivos = Variaveis.OperacaoEmAndamento?.Controle?.MotivosManual;
|
||||||
|
if (motivos == null) return;
|
||||||
|
motivos.Clear();
|
||||||
|
motivos.Add(motivo);
|
||||||
|
Variaveis.MostrarLog($"[GeneralJoystick] Comando manual não aplicado. Motivo: {motivo}");
|
||||||
|
}
|
||||||
|
|
||||||
public static void EnviarComandoAtuadorAtualizado(DeParaComandos Comando, bool ForcarComando = false)
|
public static void EnviarComandoAtuadorAtualizado(DeParaComandos Comando, bool ForcarComando = false)
|
||||||
{
|
{
|
||||||
var op = Variaveis.OperacaoEmAndamento;
|
var op = Variaveis.OperacaoEmAndamento;
|
||||||
|
|
@ -1003,6 +1222,7 @@ namespace AgroBase
|
||||||
Dispositivo = Dispositivo,
|
Dispositivo = Dispositivo,
|
||||||
PararAoSoltar = PararAoSoltar,
|
PararAoSoltar = PararAoSoltar,
|
||||||
IDcontrolar = IDcontrolar,
|
IDcontrolar = IDcontrolar,
|
||||||
|
Comando = Comando,
|
||||||
direcao = direcao,
|
direcao = direcao,
|
||||||
ValorAplicar = ValorAplicar,
|
ValorAplicar = ValorAplicar,
|
||||||
EstadoAplicar = EstadoAplicar,
|
EstadoAplicar = EstadoAplicar,
|
||||||
|
|
|
||||||
|
|
@ -56,8 +56,8 @@ namespace AgroBase.Models.Operadores
|
||||||
public double fps { get; set; }
|
public double fps { get; set; }
|
||||||
public int stream_w { get; set; }
|
public int stream_w { get; set; }
|
||||||
public int stream_h { get; set; }
|
public int stream_h { get; set; }
|
||||||
public double stream_kbps { get; set; }
|
public double? stream_kbps { get; set; }
|
||||||
public double stream_last_frame { get; set; }
|
public double? stream_last_frame { get; set; }
|
||||||
public SaudeWorkerModel saude { get; set; }
|
public SaudeWorkerModel saude { get; set; }
|
||||||
public CameraWorkerItemPerformanceModel performance { get; set; }
|
public CameraWorkerItemPerformanceModel performance { get; set; }
|
||||||
public OAKImuDataModel imu { get; set; }
|
public OAKImuDataModel imu { get; set; }
|
||||||
|
|
|
||||||
|
|
@ -54,11 +54,11 @@ namespace AgroBase.Models
|
||||||
return version.ToString();
|
return version.ToString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public static string CaminhoLogsDispositivos { get; } = ResolverCaminhoLocal("Logs");
|
public static string CaminhoLogsDispositivos { get; } = ResolverCaminhoLocal("Logs/");
|
||||||
public static string CaminhoOperacoes { get; } = ResolverCaminhoLocal("Operacoes");
|
public static string CaminhoOperacoes { get; } = ResolverCaminhoLocal("Operacoes/");
|
||||||
public static string CaminhoOperacoesSalvas { get; } = ResolverCaminhoLocal("OperacoesSalvas");
|
public static string CaminhoOperacoesSalvas { get; } = ResolverCaminhoLocal("OperacoesSalvas/");
|
||||||
public static string CaminhoMapasConvertidos { get; } = ResolverCaminhoLocal("Mapas");
|
public static string CaminhoMapasConvertidos { get; } = ResolverCaminhoLocal("Mapas/");
|
||||||
public static string CaminhoParametros { get; } = ResolverCaminhoLocal("Parametros");
|
public static string CaminhoParametros { get; } = ResolverCaminhoLocal("Parametros/");
|
||||||
public static string CaminhoModelos { get; } = ResolverCaminhoLocal(@"C:\AgroBaseModels");
|
public static string CaminhoModelos { get; } = ResolverCaminhoLocal(@"C:\AgroBaseModels");
|
||||||
private static string ResolverCaminhoLocal(string caminho)
|
private static string ResolverCaminhoLocal(string caminho)
|
||||||
{
|
{
|
||||||
|
|
@ -821,8 +821,8 @@ namespace AgroBase.Models
|
||||||
public static string TopicoMqttComandos { get; } = $"agrobot/v1/rover/<id>/cmd";
|
public static string TopicoMqttComandos { get; } = $"agrobot/v1/rover/<id>/cmd";
|
||||||
public static string TopicoMqttTelemetria { get; } = $"agrobot/v1/rover/<id>/telemetry";
|
public static string TopicoMqttTelemetria { get; } = $"agrobot/v1/rover/<id>/telemetry";
|
||||||
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; } = 53.0; // 62
|
||||||
public static double LarguraDireita { get; } = 44.0; // 22
|
public static double LarguraDireita { get; } = 53.0; // 22
|
||||||
public static double ComprimentoFrente { get; } = 90.0; // 7
|
public static double ComprimentoFrente { get; } = 90.0; // 7
|
||||||
public static double ComprimentoTras { get; } = 40.0; // 107
|
public static double ComprimentoTras { get; } = 40.0; // 107
|
||||||
public static double DistanciaEntreEixosCm { get; set; } = 92.0;
|
public static double DistanciaEntreEixosCm { get; set; } = 92.0;
|
||||||
|
|
|
||||||
|
|
@ -323,6 +323,7 @@ namespace AgroBase.Services
|
||||||
bool Inicializar(int BitRate = 250);
|
bool Inicializar(int BitRate = 250);
|
||||||
void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] idTx, byte[] idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false);
|
void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] idTx, byte[] idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false);
|
||||||
void Fechar();
|
void Fechar();
|
||||||
|
bool PossuiMensagemPendente(T_Code dispositivo);
|
||||||
|
|
||||||
string _portName { get; set; }
|
string _portName { get; set; }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -90,6 +90,16 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool PossuiMensagemPendente(T_Code dispositivo)
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
return MensagensPendentes.Any(x =>
|
||||||
|
x.Dispositivo == dispositivo &&
|
||||||
|
!x.Respondido);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long UltimoTxUnixMs { get; private set; } = 0;
|
public long UltimoTxUnixMs { get; private set; } = 0;
|
||||||
public long UltimoRxUnixMs { get; private set; } = 0;
|
public long UltimoRxUnixMs { get; private set; } = 0;
|
||||||
public long UltimoErroCriticoUnixMs { get; private set; } = 0;
|
public long UltimoErroCriticoUnixMs { get; private set; } = 0;
|
||||||
|
|
|
||||||
|
|
@ -86,6 +86,16 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool PossuiMensagemPendente(T_Code dispositivo)
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
return MensagensPendentes.Any(x =>
|
||||||
|
x.Dispositivo == dispositivo &&
|
||||||
|
!x.Respondido);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long UltimoTxUnixMs { get; private set; } = 0;
|
public long UltimoTxUnixMs { get; private set; } = 0;
|
||||||
public long UltimoRxUnixMs { get; private set; } = 0;
|
public long UltimoRxUnixMs { get; private set; } = 0;
|
||||||
public long UltimoErroCriticoUnixMs { get; private set; } = 0;
|
public long UltimoErroCriticoUnixMs { get; private set; } = 0;
|
||||||
|
|
|
||||||
|
|
@ -90,14 +90,9 @@ namespace AgroBase.Services
|
||||||
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorDispositivo = new ConcurrentDictionary<string, long>();
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorDispositivo = new ConcurrentDictionary<string, long>();
|
||||||
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorChave = new ConcurrentDictionary<string, long>();
|
private readonly ConcurrentDictionary<string, long> _timeoutsRespostaPorChave = new ConcurrentDictionary<string, long>();
|
||||||
|
|
||||||
public Dictionary<string, long> TimeoutsRespostaPorId =>
|
public Dictionary<string, long> TimeoutsRespostaPorId => _timeoutsRespostaPorId.ToDictionary(x => x.Key, x => x.Value);
|
||||||
_timeoutsRespostaPorId.ToDictionary(x => x.Key, x => x.Value);
|
public Dictionary<string, long> TimeoutsRespostaPorDispositivo => _timeoutsRespostaPorDispositivo.ToDictionary(x => x.Key, x => x.Value);
|
||||||
|
public Dictionary<string, long> TimeoutsRespostaPorChave => _timeoutsRespostaPorChave.ToDictionary(x => x.Key, x => x.Value);
|
||||||
public Dictionary<string, long> TimeoutsRespostaPorDispositivo =>
|
|
||||||
_timeoutsRespostaPorDispositivo.ToDictionary(x => x.Key, x => x.Value);
|
|
||||||
|
|
||||||
public Dictionary<string, long> TimeoutsRespostaPorChave =>
|
|
||||||
_timeoutsRespostaPorChave.ToDictionary(x => x.Key, x => x.Value);
|
|
||||||
|
|
||||||
public Dictionary<string, int> PendentesAguardandoRespostaPorId
|
public Dictionary<string, int> PendentesAguardandoRespostaPorId
|
||||||
{
|
{
|
||||||
|
|
@ -113,6 +108,16 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool PossuiMensagemPendente(T_Code dispositivo)
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
return MensagensPendentes.Any(x =>
|
||||||
|
x.Dispositivo == dispositivo &&
|
||||||
|
!x.Respondido);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public long UltimoTxUnixMs { get; private set; } = 0;
|
public long UltimoTxUnixMs { get; private set; } = 0;
|
||||||
public long UltimoRxUnixMs { get; private set; } = 0;
|
public long UltimoRxUnixMs { get; private set; } = 0;
|
||||||
public long UltimoErroCriticoUnixMs { get; private set; } = 0;
|
public long UltimoErroCriticoUnixMs { get; private set; } = 0;
|
||||||
|
|
@ -139,32 +144,17 @@ namespace AgroBase.Services
|
||||||
private DateTime _inicioJanelaLatencia = DateTime.UtcNow;
|
private DateTime _inicioJanelaLatencia = DateTime.UtcNow;
|
||||||
private double _latenciaMaxJanelaMs = 0;
|
private double _latenciaMaxJanelaMs = 0;
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, DateTime> _cooldownPorCanal =
|
private readonly ConcurrentDictionary<string, DateTime> _cooldownPorCanal = new ConcurrentDictionary<string, DateTime>();
|
||||||
new ConcurrentDictionary<string, DateTime>();
|
|
||||||
|
|
||||||
private readonly ConcurrentDictionary<string, DateTime> _ultimaFilaPorChave =
|
private readonly ConcurrentDictionary<string, DateTime> _ultimaFilaPorChave = new ConcurrentDictionary<string, DateTime>();
|
||||||
new ConcurrentDictionary<string, DateTime>();
|
|
||||||
|
|
||||||
private static readonly TimeSpan COOLDOWN_DEFAULT =
|
private static readonly TimeSpan COOLDOWN_DEFAULT = TimeSpan.FromMilliseconds(0);
|
||||||
TimeSpan.FromMilliseconds(0);
|
private static readonly TimeSpan COOLDOWN_MKS = TimeSpan.FromMilliseconds(20);
|
||||||
|
private static readonly TimeSpan COOLDOWN_OID = TimeSpan.FromMilliseconds(8);
|
||||||
private static readonly TimeSpan COOLDOWN_MKS =
|
private static readonly TimeSpan COOLDOWN_BAT = TimeSpan.FromMilliseconds(80);
|
||||||
TimeSpan.FromMilliseconds(20);
|
private static readonly TimeSpan COOLDOWN_ATU_SEN = TimeSpan.FromMilliseconds(4);
|
||||||
|
private static readonly TimeSpan DEDUPE_JANELA_GET = TimeSpan.FromMilliseconds(250);
|
||||||
private static readonly TimeSpan COOLDOWN_OID =
|
private static readonly TimeSpan DEDUPE_JANELA_SET = TimeSpan.FromMilliseconds(60);
|
||||||
TimeSpan.FromMilliseconds(8);
|
|
||||||
|
|
||||||
private static readonly TimeSpan COOLDOWN_BAT =
|
|
||||||
TimeSpan.FromMilliseconds(80);
|
|
||||||
|
|
||||||
private static readonly TimeSpan COOLDOWN_ATU_SEN =
|
|
||||||
TimeSpan.FromMilliseconds(4);
|
|
||||||
|
|
||||||
private static readonly TimeSpan DEDUPE_JANELA_GET =
|
|
||||||
TimeSpan.FromMilliseconds(250);
|
|
||||||
|
|
||||||
private static readonly TimeSpan DEDUPE_JANELA_SET =
|
|
||||||
TimeSpan.FromMilliseconds(60);
|
|
||||||
|
|
||||||
private static long NowUnixMs()
|
private static long NowUnixMs()
|
||||||
{
|
{
|
||||||
|
|
@ -394,8 +384,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
{
|
{
|
||||||
DateTime agora =
|
DateTime agora = DateTime.Now;
|
||||||
DateTime.Now;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Para request com resposta, não deixa duas pendências iguais
|
* Para request com resposta, não deixa duas pendências iguais
|
||||||
|
|
@ -430,15 +419,9 @@ namespace AgroBase.Services
|
||||||
* Dedupe temporal extra, útil para comandos de controle que chegam
|
* Dedupe temporal extra, útil para comandos de controle que chegam
|
||||||
* repetidos por loop rápido.
|
* repetidos por loop rápido.
|
||||||
*/
|
*/
|
||||||
TimeSpan janela =
|
TimeSpan janela = get ? DEDUPE_JANELA_GET : DEDUPE_JANELA_SET;
|
||||||
get
|
|
||||||
? DEDUPE_JANELA_GET
|
|
||||||
: DEDUPE_JANELA_SET;
|
|
||||||
|
|
||||||
if (_ultimaFilaPorChave.TryGetValue(
|
if (_ultimaFilaPorChave.TryGetValue(chaveCorrelacao, out DateTime ultima) && agora - ultima < janela)
|
||||||
chaveCorrelacao,
|
|
||||||
out DateTime ultima) &&
|
|
||||||
agora - ultima < janela)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -915,11 +898,7 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool MensagemCorrespondeResposta(
|
private bool MensagemCorrespondeResposta(CanMessage pendente, T_Code dispositivoResposta, uint idResposta, byte[] dataResposta)
|
||||||
CanMessage pendente,
|
|
||||||
T_Code dispositivoResposta,
|
|
||||||
uint idResposta,
|
|
||||||
byte[] dataResposta)
|
|
||||||
{
|
{
|
||||||
if (pendente == null)
|
if (pendente == null)
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,9 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
private static DateTime _ultimoEnvioCan = DateTime.MinValue;
|
private static DateTime _ultimoEnvioCan = DateTime.MinValue;
|
||||||
|
|
||||||
|
private static readonly TimeSpan RespiroAposResposta = TimeSpan.FromMilliseconds(100);
|
||||||
|
private static DateTime _ultimaRespostaCan = DateTime.MinValue;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Mantém prioridade determinística.
|
* Mantém prioridade determinística.
|
||||||
* O método EscolherProximoDadoParaRequisitar() varre essa lista e
|
* O método EscolherProximoDadoParaRequisitar() varre essa lista e
|
||||||
|
|
@ -149,6 +152,21 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AtualizarDados()
|
public static void AtualizarDados()
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
AtualizarDadosInterno();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog("[DalyBMSService.AtualizarDados] Erro: " + ex.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void AtualizarDadosInterno()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -195,6 +213,12 @@ namespace AgroBase.Services
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CanManager.CanService.PossuiMensagemPendente(T_Code.Bat))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (DateTime.UtcNow - _ultimaRespostaCan < RespiroAposResposta)
|
||||||
|
return;
|
||||||
|
|
||||||
BatFuncCode? proximo = EscolherProximoDadoParaRequisitar(agora);
|
BatFuncCode? proximo = EscolherProximoDadoParaRequisitar(agora);
|
||||||
|
|
||||||
if (!proximo.HasValue)
|
if (!proximo.HasValue)
|
||||||
|
|
@ -310,17 +334,9 @@ namespace AgroBase.Services
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EnviarComando(
|
private static void EnviarComando(T_Code dispositivo, byte[] idTx, byte[] idRx, byte funcCode, byte[] payload = null, bool contabilizarTimeoutHealth = true)
|
||||||
T_Code dispositivo,
|
|
||||||
byte[] idTx,
|
|
||||||
byte[] idRx,
|
|
||||||
byte funcCode,
|
|
||||||
byte[] payload = null,
|
|
||||||
bool contabilizarTimeoutHealth = true)
|
|
||||||
{
|
{
|
||||||
CanManager
|
CanManager.CanService.AdicionarMensagemNaFila(
|
||||||
.CanService
|
|
||||||
.AdicionarMensagemNaFila(
|
|
||||||
dispositivo,
|
dispositivo,
|
||||||
idTx,
|
idTx,
|
||||||
idRx,
|
idRx,
|
||||||
|
|
@ -328,8 +344,7 @@ namespace AgroBase.Services
|
||||||
funcCode,
|
funcCode,
|
||||||
payload ?? new byte[8],
|
payload ?? new byte[8],
|
||||||
get: true,
|
get: true,
|
||||||
contabilizarTimeoutHealth:
|
contabilizarTimeoutHealth: contabilizarTimeoutHealth
|
||||||
contabilizarTimeoutHealth
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -367,9 +382,7 @@ namespace AgroBase.Services
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RequisitarDado(
|
private static void RequisitarDado(BatFuncCode dado, bool contabilizarTimeoutHealth)
|
||||||
BatFuncCode dado,
|
|
||||||
bool contabilizarTimeoutHealth)
|
|
||||||
{
|
{
|
||||||
EnviarComando(
|
EnviarComando(
|
||||||
Dispositivo,
|
Dispositivo,
|
||||||
|
|
@ -377,8 +390,7 @@ namespace AgroBase.Services
|
||||||
MakeResponseID(dado),
|
MakeResponseID(dado),
|
||||||
(byte)dado,
|
(byte)dado,
|
||||||
payload: new byte[8],
|
payload: new byte[8],
|
||||||
contabilizarTimeoutHealth:
|
contabilizarTimeoutHealth: contabilizarTimeoutHealth
|
||||||
contabilizarTimeoutHealth
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,33 +431,25 @@ namespace AgroBase.Services
|
||||||
if (mensagem == null)
|
if (mensagem == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (mensagem.DataRx == null ||
|
if (mensagem.DataRx == null || mensagem.DataRx.Length < 8)
|
||||||
mensagem.DataRx.Length < 8)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint canId =
|
uint canId = mensagem.IdRx;
|
||||||
mensagem.IdRx;
|
|
||||||
|
|
||||||
BatFuncCode dataId =
|
BatFuncCode dataId = GetDataId(canId);
|
||||||
GetDataId(canId);
|
|
||||||
|
|
||||||
byte[] data =
|
byte[] data = mensagem.DataRx.Take(8).ToArray();
|
||||||
mensagem
|
|
||||||
.DataRx
|
|
||||||
.Take(8)
|
|
||||||
.ToArray();
|
|
||||||
|
|
||||||
lock (DadosLeitura)
|
lock (DadosLeitura)
|
||||||
{
|
{
|
||||||
DadosLeitura.Momento = DateTime.Now;
|
DadosLeitura.Momento = DateTime.Now;
|
||||||
DadosLeitura.Iniciado = true;
|
DadosLeitura.Iniciado = true;
|
||||||
DadosLeitura.UltimoComandoRecebido =
|
DadosLeitura.UltimoComandoRecebido = DateTime.Now;
|
||||||
DateTime.Now;
|
_ultimaRespostaCan = DateTime.UtcNow;
|
||||||
|
|
||||||
DadosLeitura.LeiturasBrutas[(byte)dataId] =
|
DadosLeitura.LeiturasBrutas[(byte)dataId] = data.ToArray();
|
||||||
data.ToArray();
|
|
||||||
|
|
||||||
switch (dataId)
|
switch (dataId)
|
||||||
{
|
{
|
||||||
|
|
@ -881,5 +885,6 @@ namespace AgroBase.Services
|
||||||
d.Take(7).ToArray();
|
d.Take(7).ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -415,15 +415,27 @@ namespace AgroBase.Services.Operadores
|
||||||
Dictionary<string, bool> statusWorkers = AtualizarSaudeWorkers();
|
Dictionary<string, bool> statusWorkers = AtualizarSaudeWorkers();
|
||||||
|
|
||||||
bool existemWorkers = statusWorkers.Count > 0;
|
bool existemWorkers = statusWorkers.Count > 0;
|
||||||
bool todosWorkersOk = existemWorkers && statusWorkers.All(x => x.Value);
|
bool todosWorkersOk =
|
||||||
|
existemWorkers &&
|
||||||
|
statusWorkers.All(x => x.Value);
|
||||||
|
|
||||||
|
bool workersCriticosOk = statusWorkers
|
||||||
|
.Where(x => x.Key != nameof(WeedWorkerService) && x.Key != nameof(VisualWorkerService))
|
||||||
|
.Any()
|
||||||
|
&&
|
||||||
|
statusWorkers
|
||||||
|
.Where(x => x.Key != nameof(WeedWorkerService) && x.Key != nameof(VisualWorkerService))
|
||||||
|
.All(x => x.Value);
|
||||||
|
|
||||||
bool pythonOk = !iniciarScript || PythonRodando;
|
bool pythonOk = !iniciarScript || PythonRodando;
|
||||||
|
bool nucleoCriticoOk = pythonOk && workersCriticosOk;
|
||||||
bool nucleoCriticoOk = pythonOk && todosWorkersOk;
|
|
||||||
|
|
||||||
lock (_estadoLock)
|
lock (_estadoLock)
|
||||||
{
|
{
|
||||||
|
// Mantém a informação real: há um worker degradado.
|
||||||
TodosWorkersConectados = todosWorkersOk;
|
TodosWorkersConectados = todosWorkersOk;
|
||||||
|
|
||||||
|
// Mas o núcleo continua vivo e não mata os demais.
|
||||||
Conectado = nucleoCriticoOk;
|
Conectado = nucleoCriticoOk;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
return ArquivosPorTipo(
|
return ArquivosPorTipo(
|
||||||
TipoArquivoVersionado.ModeloIA_StreetDetector
|
TipoArquivoVersionado.ModeloIA_StreetDetector
|
||||||
).FirstOrDefault();
|
).Skip(1).FirstOrDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -62,7 +62,7 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
return ArquivosPorTipo(
|
return ArquivosPorTipo(
|
||||||
TipoArquivoVersionado.ModeloIA_StreetDetector
|
TipoArquivoVersionado.ModeloIA_StreetDetector
|
||||||
).Skip(1).FirstOrDefault();
|
).Skip(2).FirstOrDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -75,7 +75,7 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
return ArquivosPorTipo(
|
return ArquivosPorTipo(
|
||||||
TipoArquivoVersionado.ModeloIA_StreetDetector
|
TipoArquivoVersionado.ModeloIA_StreetDetector
|
||||||
).Skip(2).FirstOrDefault();
|
).Skip(3).FirstOrDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -88,7 +88,7 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
return ArquivosPorTipo(
|
return ArquivosPorTipo(
|
||||||
TipoArquivoVersionado.ModeloIA_StreetDetector
|
TipoArquivoVersionado.ModeloIA_StreetDetector
|
||||||
).Skip(3).FirstOrDefault();
|
).Skip(0).FirstOrDefault();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
gyro_unidade="deg_s",
|
gyro_unidade="deg_s",
|
||||||
raw_acc_alpha=0.04,
|
raw_acc_alpha=0.04,
|
||||||
raw_max_acc_erro_g=0.18,
|
raw_max_acc_erro_g=0.18,
|
||||||
|
referencial_robo="r,p,y",
|
||||||
):
|
):
|
||||||
self.mx_id = mx_id
|
self.mx_id = mx_id
|
||||||
self.imu_queue = queue
|
self.imu_queue = queue
|
||||||
|
|
@ -132,6 +133,9 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
self._calib_roll_hist = deque(maxlen=200)
|
self._calib_roll_hist = deque(maxlen=200)
|
||||||
self._calib_pitch_hist = deque(maxlen=200)
|
self._calib_pitch_hist = deque(maxlen=200)
|
||||||
|
|
||||||
|
self.referencial_robo = str(referencial_robo or "r,p,y").lower().replace(" ", "")
|
||||||
|
self.matriz_sensor_para_robo = (self._criar_matriz_sensor_para_robo(self.referencial_robo))
|
||||||
|
|
||||||
if queue is None:
|
if queue is None:
|
||||||
self.mostrar_log("Inicializado sem queue. Classe ficará inativa.")
|
self.mostrar_log("Inicializado sem queue. Classe ficará inativa.")
|
||||||
return
|
return
|
||||||
|
|
@ -145,7 +149,9 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
self.imu_thread.start()
|
self.imu_thread.start()
|
||||||
|
|
||||||
self.mostrar_log(
|
self.mostrar_log(
|
||||||
f"Task iniciada | modo={self.modo} | freq alvo={self.freq:.0f}Hz | "
|
f"Task iniciada | modo={self.modo} | "
|
||||||
|
f"referencial={self.referencial_robo} | "
|
||||||
|
f"freq alvo={self.freq:.0f}Hz | "
|
||||||
f"publish={1.0/self.publish_period:.0f}Hz"
|
f"publish={1.0/self.publish_period:.0f}Hz"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -166,6 +172,8 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def iniciar_calibracao(self):
|
def iniciar_calibracao(self):
|
||||||
|
self._resetar_estado_angular()
|
||||||
|
|
||||||
self.calibrado = False
|
self.calibrado = False
|
||||||
self.calibrando = True
|
self.calibrando = True
|
||||||
self.calib_inicio_ts = time.perf_counter()
|
self.calib_inicio_ts = time.perf_counter()
|
||||||
|
|
@ -336,6 +344,10 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
if self.gyro_unidade in ["rad_s", "rad/s", "rads"]:
|
if self.gyro_unidade in ["rad_s", "rad/s", "rads"]:
|
||||||
g = np.rad2deg(g)
|
g = np.rad2deg(g)
|
||||||
|
|
||||||
|
# Normaliza acelerômetro e giroscópio para o referencial do rover.
|
||||||
|
a = self._vetor_sensor_para_robo(a)
|
||||||
|
g = self._vetor_sensor_para_robo(g)
|
||||||
|
|
||||||
sensor_ts = None
|
sensor_ts = None
|
||||||
try:
|
try:
|
||||||
sensor_ts = packet.timestamp.get()
|
sensor_ts = packet.timestamp.get()
|
||||||
|
|
@ -381,14 +393,19 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
q_delta = q_ref_inv * q_atual
|
q_delta = q_ref_inv * q_atual
|
||||||
"""
|
"""
|
||||||
r_atual = R.from_quat(q_xyzw)
|
r_atual_sensor = R.from_quat(q_xyzw)
|
||||||
|
|
||||||
if self.calibrado and self.q_ref_inv is not None:
|
if self.calibrado and self.q_ref_inv is not None:
|
||||||
r_delta = self.q_ref_inv * r_atual
|
r_delta_sensor = self.q_ref_inv * r_atual_sensor
|
||||||
else:
|
else:
|
||||||
r_delta = r_atual
|
r_delta_sensor = r_atual_sensor
|
||||||
|
|
||||||
roll, pitch, yaw = r_delta.as_euler("xyz", degrees=True)
|
r_delta_robo = self._rotacao_sensor_para_robo(r_delta_sensor)
|
||||||
|
|
||||||
|
roll, pitch, yaw = r_delta_robo.as_euler(
|
||||||
|
"xyz",
|
||||||
|
degrees=True,
|
||||||
|
)
|
||||||
|
|
||||||
return float(roll), float(pitch), float(yaw)
|
return float(roll), float(pitch), float(yaw)
|
||||||
|
|
||||||
|
|
@ -400,11 +417,22 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
self._last_yaw_deg = yaw
|
self._last_yaw_deg = yaw
|
||||||
return
|
return
|
||||||
|
|
||||||
dt = max(float(ts - self._last_angle_ts), 1e-4)
|
dt = float(ts - self._last_angle_ts)
|
||||||
|
|
||||||
self.roll_rate_dps = self._wrap_angle_delta_deg(roll, self._last_roll_deg) / dt
|
if dt <= 0.0 or dt > 0.2:
|
||||||
self.pitch_rate_dps = self._wrap_angle_delta_deg(pitch, self._last_pitch_deg) / dt
|
self._last_angle_ts = ts
|
||||||
self.yaw_rate_dps = self._wrap_angle_delta_deg(yaw, self._last_yaw_deg) / dt
|
self._last_roll_deg = roll
|
||||||
|
self._last_pitch_deg = pitch
|
||||||
|
self._last_yaw_deg = yaw
|
||||||
|
return
|
||||||
|
|
||||||
|
self.roll_rate_dps = (self._wrap_angle_delta_deg(roll, self._last_roll_deg) / dt)
|
||||||
|
self.pitch_rate_dps = (self._wrap_angle_delta_deg(pitch, self._last_pitch_deg) / dt)
|
||||||
|
self.yaw_rate_dps = (self._wrap_angle_delta_deg(yaw, self._last_yaw_deg) / dt)
|
||||||
|
|
||||||
|
self.roll_rate_dps = float(np.clip(self.roll_rate_dps, -500.0, 500.0))
|
||||||
|
self.pitch_rate_dps = float(np.clip(self.pitch_rate_dps, -500.0, 500.0))
|
||||||
|
self.yaw_rate_dps = float(np.clip(self.yaw_rate_dps, -500.0, 500.0))
|
||||||
|
|
||||||
self._last_angle_ts = ts
|
self._last_angle_ts = ts
|
||||||
self._last_roll_deg = roll
|
self._last_roll_deg = roll
|
||||||
|
|
@ -428,6 +456,118 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
self.yaw_filtrado_deg = self.yaw_filtrado_deg + a * dyaw
|
self.yaw_filtrado_deg = self.yaw_filtrado_deg + a * dyaw
|
||||||
self.yaw_filtrado_deg = (self.yaw_filtrado_deg + 180.0) % 360.0 - 180.0
|
self.yaw_filtrado_deg = (self.yaw_filtrado_deg + 180.0) % 360.0 - 180.0
|
||||||
|
|
||||||
|
def _sensor_ts_para_segundos(self, sensor_ts):
|
||||||
|
if sensor_ts is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if hasattr(sensor_ts, "total_seconds"):
|
||||||
|
return float(sensor_ts.total_seconds())
|
||||||
|
|
||||||
|
try:
|
||||||
|
return float(sensor_ts)
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _criar_matriz_sensor_para_robo(self, configuracao):
|
||||||
|
"""
|
||||||
|
Define como os eixos do sensor correspondem aos eixos do rover.
|
||||||
|
|
||||||
|
Convenção:
|
||||||
|
r = roll = eixo X
|
||||||
|
p = pitch = eixo Y
|
||||||
|
y = yaw = eixo Z
|
||||||
|
|
||||||
|
Exemplos:
|
||||||
|
"r,p,y" -> mesma orientação
|
||||||
|
"-r,p,-y" -> inverte roll e yaw
|
||||||
|
"p,r,-y" -> troca roll/pitch e inverte yaw
|
||||||
|
|
||||||
|
Cada posição representa a saída no referencial do rover:
|
||||||
|
|
||||||
|
posição 0 -> roll/X do rover
|
||||||
|
posição 1 -> pitch/Y do rover
|
||||||
|
posição 2 -> yaw/Z do rover
|
||||||
|
"""
|
||||||
|
texto = str(configuracao or "r,p,y").lower().replace(" ", "")
|
||||||
|
tokens = texto.split(",")
|
||||||
|
|
||||||
|
if len(tokens) != 3:
|
||||||
|
raise ValueError(
|
||||||
|
f"Referencial IMU inválido: '{configuracao}'. "
|
||||||
|
f"Use, por exemplo: 'r,p,y', '-r,p,-y' ou 'p,r,-y'."
|
||||||
|
)
|
||||||
|
|
||||||
|
indices = {
|
||||||
|
"r": 0,
|
||||||
|
"roll": 0,
|
||||||
|
"x": 0,
|
||||||
|
|
||||||
|
"p": 1,
|
||||||
|
"pitch": 1,
|
||||||
|
|
||||||
|
"y": 2,
|
||||||
|
"yaw": 2,
|
||||||
|
"z": 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
matriz = np.zeros((3, 3), dtype=np.float64)
|
||||||
|
eixos_usados = []
|
||||||
|
|
||||||
|
for eixo_robo, token in enumerate(tokens):
|
||||||
|
sinal = -1.0 if token.startswith("-") else 1.0
|
||||||
|
nome = token.lstrip("+-")
|
||||||
|
|
||||||
|
if nome not in indices:
|
||||||
|
raise ValueError(
|
||||||
|
f"Eixo IMU inválido: '{token}' em '{configuracao}'."
|
||||||
|
)
|
||||||
|
|
||||||
|
eixo_sensor = indices[nome]
|
||||||
|
|
||||||
|
if eixo_sensor in eixos_usados:
|
||||||
|
raise ValueError(
|
||||||
|
f"Eixo repetido em referencial IMU: '{configuracao}'."
|
||||||
|
)
|
||||||
|
|
||||||
|
eixos_usados.append(eixo_sensor)
|
||||||
|
matriz[eixo_robo, eixo_sensor] = sinal
|
||||||
|
|
||||||
|
# Uma orientação física deve manter um sistema destro.
|
||||||
|
determinante = float(np.linalg.det(matriz))
|
||||||
|
|
||||||
|
if not np.isclose(determinante, 1.0, atol=1e-6):
|
||||||
|
raise ValueError(
|
||||||
|
f"Referencial IMU '{configuracao}' resulta em sistema invertido "
|
||||||
|
f"(det={determinante:.0f}). Ao trocar dois eixos, normalmente é "
|
||||||
|
f"necessário inverter também um deles. Exemplo: 'p,r,-y'."
|
||||||
|
)
|
||||||
|
|
||||||
|
return matriz
|
||||||
|
|
||||||
|
def _vetor_sensor_para_robo(self, vetor):
|
||||||
|
vetor = np.asarray(vetor, dtype=np.float64)
|
||||||
|
|
||||||
|
if vetor.shape != (3,):
|
||||||
|
raise ValueError(
|
||||||
|
f"Vetor IMU deve possuir shape (3,), recebido {vetor.shape}."
|
||||||
|
)
|
||||||
|
|
||||||
|
return self.matriz_sensor_para_robo @ vetor
|
||||||
|
|
||||||
|
def _rotacao_sensor_para_robo(self, rotacao_sensor):
|
||||||
|
"""
|
||||||
|
Expressa uma rotação medida no referencial do sensor
|
||||||
|
usando os eixos configurados do rover.
|
||||||
|
|
||||||
|
R_robo = M * R_sensor * M^T
|
||||||
|
"""
|
||||||
|
matriz_sensor = rotacao_sensor.as_matrix()
|
||||||
|
m = self.matriz_sensor_para_robo
|
||||||
|
|
||||||
|
matriz_robo = m @ matriz_sensor @ m.T
|
||||||
|
|
||||||
|
return R.from_matrix(matriz_robo)
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Calibração
|
# Calibração
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
@ -475,6 +615,8 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
f"std_roll={roll_std:.3f}° | std_pitch={pitch_std:.3f}°"
|
f"std_roll={roll_std:.3f}° | std_pitch={pitch_std:.3f}°"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._resetar_estado_angular()
|
||||||
|
|
||||||
def _processar_calibracao_raw(self, a, gyro_dps):
|
def _processar_calibracao_raw(self, a, gyro_dps):
|
||||||
if not self.calibrando:
|
if not self.calibrando:
|
||||||
return
|
return
|
||||||
|
|
@ -564,6 +706,24 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
f"gyro_std_medio={gyro_std:.4f}°/s"
|
f"gyro_std_medio={gyro_std:.4f}°/s"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _resetar_estado_angular(self):
|
||||||
|
self.roll_deg = 0.0
|
||||||
|
self.pitch_deg = 0.0
|
||||||
|
self.yaw_deg = 0.0
|
||||||
|
|
||||||
|
self.roll_filtrado_deg = 0.0
|
||||||
|
self.pitch_filtrado_deg = 0.0
|
||||||
|
self.yaw_filtrado_deg = 0.0
|
||||||
|
|
||||||
|
self.roll_rate_dps = 0.0
|
||||||
|
self.pitch_rate_dps = 0.0
|
||||||
|
self.yaw_rate_dps = 0.0
|
||||||
|
|
||||||
|
self._last_roll_deg = None
|
||||||
|
self._last_pitch_deg = None
|
||||||
|
self._last_yaw_deg = None
|
||||||
|
self._last_angle_ts = None
|
||||||
|
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
# Consumo da queue
|
# Consumo da queue
|
||||||
# ==========================================================
|
# ==========================================================
|
||||||
|
|
@ -616,7 +776,13 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
self.pitch_deg = pitch
|
self.pitch_deg = pitch
|
||||||
self.yaw_deg = yaw
|
self.yaw_deg = yaw
|
||||||
|
|
||||||
self._atualizar_rates(roll, pitch, yaw, agora)
|
sensor_ts_s = self._sensor_ts_para_segundos(sensor_ts)
|
||||||
|
if sensor_ts_s is not None:
|
||||||
|
ts_rate = sensor_ts_s
|
||||||
|
else:
|
||||||
|
ts_rate = time.perf_counter()
|
||||||
|
|
||||||
|
self._atualizar_rates(roll, pitch, yaw, ts_rate)
|
||||||
self._atualizar_filtro_leve(roll, pitch, yaw)
|
self._atualizar_filtro_leve(roll, pitch, yaw)
|
||||||
|
|
||||||
self.last_imu_ts = sensor_ts
|
self.last_imu_ts = sensor_ts
|
||||||
|
|
@ -624,6 +790,7 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
self.last_data = {
|
self.last_data = {
|
||||||
"valido": bool(self.calibrado and not self.calibrando),
|
"valido": bool(self.calibrado and not self.calibrando),
|
||||||
"tem_leitura": True,
|
"tem_leitura": True,
|
||||||
|
"referencial_robo": self.referencial_robo,
|
||||||
|
|
||||||
"mx_id": self.mx_id,
|
"mx_id": self.mx_id,
|
||||||
"sensor": self.nome_sensor,
|
"sensor": self.nome_sensor,
|
||||||
|
|
@ -692,11 +859,18 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
roll_acc_abs, pitch_acc_abs = self._accel_para_roll_pitch_deg(a)
|
roll_acc_abs, pitch_acc_abs = self._accel_para_roll_pitch_deg(a)
|
||||||
|
|
||||||
|
sensor_ts_s = self._sensor_ts_para_segundos(sensor_ts)
|
||||||
|
ts_integracao = sensor_ts_s if sensor_ts_s is not None else agora
|
||||||
|
|
||||||
if self._last_angle_ts is None:
|
if self._last_angle_ts is None:
|
||||||
dt = 1.0 / max(self.freq, 1.0)
|
dt = 1.0 / max(self.freq, 1.0)
|
||||||
else:
|
else:
|
||||||
dt = max(agora - self._last_angle_ts, 1e-4)
|
dt = ts_integracao - self._last_angle_ts
|
||||||
dt = min(dt, 0.05)
|
|
||||||
|
if dt <= 0.0 or dt > 0.2:
|
||||||
|
dt = 1.0 / max(self.freq, 1.0)
|
||||||
|
|
||||||
|
dt = min(max(dt, 1e-4), 0.05)
|
||||||
|
|
||||||
if not self.raw_inicializado:
|
if not self.raw_inicializado:
|
||||||
self.raw_roll_abs_deg = roll_acc_abs
|
self.raw_roll_abs_deg = roll_acc_abs
|
||||||
|
|
@ -742,7 +916,7 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
self.pitch_rate_dps = gy
|
self.pitch_rate_dps = gy
|
||||||
self.yaw_rate_dps = gz
|
self.yaw_rate_dps = gz
|
||||||
|
|
||||||
self._last_angle_ts = agora
|
self._last_angle_ts = ts_integracao
|
||||||
self._last_roll_deg = roll
|
self._last_roll_deg = roll
|
||||||
self._last_pitch_deg = pitch
|
self._last_pitch_deg = pitch
|
||||||
self._last_yaw_deg = yaw
|
self._last_yaw_deg = yaw
|
||||||
|
|
@ -757,6 +931,7 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
self.last_data = {
|
self.last_data = {
|
||||||
"valido": bool(self.calibrado and not self.calibrando),
|
"valido": bool(self.calibrado and not self.calibrando),
|
||||||
"tem_leitura": True,
|
"tem_leitura": True,
|
||||||
|
"referencial_robo": self.referencial_robo,
|
||||||
|
|
||||||
"mx_id": self.mx_id,
|
"mx_id": self.mx_id,
|
||||||
"sensor": self.nome_sensor,
|
"sensor": self.nome_sensor,
|
||||||
|
|
@ -989,6 +1164,8 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
return {
|
return {
|
||||||
"valido": False,
|
"valido": False,
|
||||||
"mx_id": self.mx_id,
|
"mx_id": self.mx_id,
|
||||||
|
"referencial_robo": self.referencial_robo,
|
||||||
|
|
||||||
"sensor": self.nome_sensor,
|
"sensor": self.nome_sensor,
|
||||||
"tipo": tipo,
|
"tipo": tipo,
|
||||||
"modo": self.modo,
|
"modo": self.modo,
|
||||||
|
|
@ -1030,7 +1207,21 @@ class IMUCamera(ModuloDiagnosticoBase):
|
||||||
"gyro_bias_dps": [0.0, 0.0, 0.0],
|
"gyro_bias_dps": [0.0, 0.0, 0.0],
|
||||||
}
|
}
|
||||||
|
|
||||||
return dict(self.last_data)
|
data = dict(self.last_data)
|
||||||
|
|
||||||
|
agora = time.time()
|
||||||
|
last_packet_ts = float(data.get("last_packet_ts", 0.0) or 0.0)
|
||||||
|
packet_age_ms = ((agora - last_packet_ts) * 1000.0 if last_packet_ts > 0.0 else float("inf"))
|
||||||
|
|
||||||
|
data["packet_age_ms"] = round(packet_age_ms, 1)
|
||||||
|
data["valido"] = bool(
|
||||||
|
self.ativo
|
||||||
|
and self.calibrado
|
||||||
|
and not self.calibrando
|
||||||
|
and packet_age_ms <= 500.0
|
||||||
|
)
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
def mostrar_log(self, mensagem):
|
def mostrar_log(self, mensagem):
|
||||||
print(f"{time.time()} - [IMUCamera][mx_id={self.mx_id}][id={id(self)}] {mensagem}")
|
print(f"{time.time()} - [IMUCamera][mx_id={self.mx_id}][id={id(self)}] {mensagem}")
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,9 @@ class CameraMultispectral:
|
||||||
filtro_alpha=0.35,
|
filtro_alpha=0.35,
|
||||||
max_queue_drain=20,
|
max_queue_drain=20,
|
||||||
nome_sensor=f"{self.modelo}_imu",
|
nome_sensor=f"{self.modelo}_imu",
|
||||||
modo=self.imu_modo
|
modo=self.imu_modo,
|
||||||
|
gyro_unidade="deg_s",
|
||||||
|
referencial_robo="p,-y,-r",
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.tem_imu = False
|
self.tem_imu = False
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
import hashlib
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import depthai as dai
|
import depthai as dai
|
||||||
import time
|
import time
|
||||||
|
|
@ -133,7 +136,9 @@ class CameraOak:
|
||||||
filtro_alpha=0.35,
|
filtro_alpha=0.35,
|
||||||
max_queue_drain=20,
|
max_queue_drain=20,
|
||||||
nome_sensor=f"{self.modelo}_imu",
|
nome_sensor=f"{self.modelo}_imu",
|
||||||
modo=self.imu_modo
|
modo=self.imu_modo,
|
||||||
|
gyro_unidade="rad_s",
|
||||||
|
referencial_robo="r,p,y",
|
||||||
)
|
)
|
||||||
|
|
||||||
if self.modelo_ia_seg is not None:
|
if self.modelo_ia_seg is not None:
|
||||||
|
|
@ -887,6 +892,9 @@ class CameraOak:
|
||||||
y2 = 1.0 - ROI_INICIO
|
y2 = 1.0 - ROI_INICIO
|
||||||
|
|
||||||
if blob_path:
|
if blob_path:
|
||||||
|
try:
|
||||||
|
dai.OpenVINO.Blob(blob_path)
|
||||||
|
|
||||||
manip_det = pipeline.createImageManip()
|
manip_det = pipeline.createImageManip()
|
||||||
manip_det.initialConfig.setCropRect(0.0, y1, 1.0, y2)
|
manip_det.initialConfig.setCropRect(0.0, y1, 1.0, y2)
|
||||||
manip_det.initialConfig.setResize(RESOLUCAO[0], RESOLUCAO[1])
|
manip_det.initialConfig.setResize(RESOLUCAO[0], RESOLUCAO[1])
|
||||||
|
|
@ -895,7 +903,23 @@ class CameraOak:
|
||||||
|
|
||||||
det = pipeline.createMobileNetDetectionNetwork()
|
det = pipeline.createMobileNetDetectionNetwork()
|
||||||
#det = pipeline.createMobileNetSpatialDetectionNetwork()
|
#det = pipeline.createMobileNetSpatialDetectionNetwork()
|
||||||
det.setBlobPath(blob_path)
|
|
||||||
|
blob_resolvido = Path(blob_path).resolve()
|
||||||
|
|
||||||
|
self.mostrar_log(
|
||||||
|
"[BLOB_DEBUG] "
|
||||||
|
f"original={blob_path!r}; "
|
||||||
|
f"resolvido={str(blob_resolvido)!r}; "
|
||||||
|
f"existe={blob_resolvido.is_file()}; "
|
||||||
|
f"tamanho={blob_resolvido.stat().st_size if blob_resolvido.is_file() else None}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if blob_resolvido.is_file():
|
||||||
|
sha256 = hashlib.sha256(blob_resolvido.read_bytes()).hexdigest()
|
||||||
|
self.mostrar_log(f"[BLOB_DEBUG] sha256={sha256}")
|
||||||
|
|
||||||
|
det.setBlobPath(str(blob_resolvido))
|
||||||
|
|
||||||
det.setConfidenceThreshold(CONF)
|
det.setConfidenceThreshold(CONF)
|
||||||
det.setNumInferenceThreads(2)
|
det.setNumInferenceThreads(2)
|
||||||
det.input.setBlocking(False)
|
det.input.setBlocking(False)
|
||||||
|
|
@ -941,6 +965,13 @@ class CameraOak:
|
||||||
# det.passthrough.link(xout_det_img.input)
|
# det.passthrough.link(xout_det_img.input)
|
||||||
|
|
||||||
self.mostrar_log("Pipeline de detecção leve (MobileNet-SSD) criado")
|
self.mostrar_log("Pipeline de detecção leve (MobileNet-SSD) criado")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
raise RuntimeError(
|
||||||
|
f"Falha ao carregar detector MobileNet: "
|
||||||
|
f"path={blob_path!r}; erro={e}"
|
||||||
|
) from e
|
||||||
|
|
||||||
else:
|
else:
|
||||||
self.mostrar_log("[INFO] Detector não configurado (detector_blob_path ausente). Pulando.")
|
self.mostrar_log("[INFO] Detector não configurado (detector_blob_path ausente). Pulando.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ def main():
|
||||||
T_Code.Sen: ModuloSensoriamento(),
|
T_Code.Sen: ModuloSensoriamento(),
|
||||||
T_Code.Atu: ModuloAtuador(),
|
T_Code.Atu: ModuloAtuador(),
|
||||||
#T_Code.Lra: ModuloLoRa(),
|
#T_Code.Lra: ModuloLoRa(),
|
||||||
T_Code.Imu: CameraIMU(modulos_imu=[T_Code.Snr]),
|
T_Code.Imu: CameraIMU(modulos_imu=[T_Code.Snr, T_Code.Cam]),
|
||||||
T_Code.Npc: ModuloPC(),
|
T_Code.Npc: ModuloPC(),
|
||||||
T_Code.Lvx: ModuloLivox(),
|
T_Code.Lvx: ModuloLivox(),
|
||||||
T_Code.Ipb: ModuloIPBribge(),
|
T_Code.Ipb: ModuloIPBribge(),
|
||||||
|
|
|
||||||
|
|
@ -146,6 +146,8 @@ class CameraIMU(ModuloDiagnosticoBase):
|
||||||
self.pitch_rate_hist = deque(maxlen=historico_len)
|
self.pitch_rate_hist = deque(maxlen=historico_len)
|
||||||
self.risk_hist = deque(maxlen=historico_len)
|
self.risk_hist = deque(maxlen=historico_len)
|
||||||
|
|
||||||
|
self._assinatura_fontes_anterior = None
|
||||||
|
|
||||||
self.thread = threading.Thread(
|
self.thread = threading.Thread(
|
||||||
target=self.imu_task_loop,
|
target=self.imu_task_loop,
|
||||||
name="CameraIMUFinalWorker",
|
name="CameraIMUFinalWorker",
|
||||||
|
|
@ -189,10 +191,18 @@ class CameraIMU(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
def _safe_float(self, data, key, default=0.0):
|
def _safe_float(self, data, key, default=0.0):
|
||||||
try:
|
try:
|
||||||
v = data.get(key, default)
|
valor = data.get(key, default)
|
||||||
if v is None:
|
|
||||||
|
if valor is None:
|
||||||
return float(default)
|
return float(default)
|
||||||
return float(v)
|
|
||||||
|
valor = float(valor)
|
||||||
|
|
||||||
|
if not math.isfinite(valor):
|
||||||
|
return float(default)
|
||||||
|
|
||||||
|
return valor
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return float(default)
|
return float(default)
|
||||||
|
|
||||||
|
|
@ -503,6 +513,7 @@ class CameraIMU(ModuloDiagnosticoBase):
|
||||||
"modelo": cam_data.get("modelo"),
|
"modelo": cam_data.get("modelo"),
|
||||||
"dispositivo": cam_data.get("dispositivo"),
|
"dispositivo": cam_data.get("dispositivo"),
|
||||||
"t_code": codigo_normalizado,
|
"t_code": codigo_normalizado,
|
||||||
|
"referencial_robo": imu.get("referencial_robo", cam_data.get("referencial_robo")),
|
||||||
|
|
||||||
"valido": valido,
|
"valido": valido,
|
||||||
"calibrado": calibrado,
|
"calibrado": calibrado,
|
||||||
|
|
@ -806,13 +817,24 @@ class CameraIMU(ModuloDiagnosticoBase):
|
||||||
"divergentes": divergentes,
|
"divergentes": divergentes,
|
||||||
}
|
}
|
||||||
|
|
||||||
def _fundir_fontes(self, fontes):
|
def _valor_mais_conservador(self, fontes, key):
|
||||||
|
fonte = max(
|
||||||
|
fontes,
|
||||||
|
key=lambda f: abs(float(f.get(key, 0.0))),
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
float(fonte.get(key, 0.0)),
|
||||||
|
fonte["mx_id"],
|
||||||
|
)
|
||||||
|
|
||||||
|
def _fundir_fontes(self, fontes, sensores_agree=True):
|
||||||
if not fontes:
|
if not fontes:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# Se só uma fonte existe, usa ela diretamente.
|
|
||||||
if len(fontes) == 1:
|
if len(fontes) == 1:
|
||||||
f = fontes[0]
|
f = fontes[0]
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"roll": f["roll"],
|
"roll": f["roll"],
|
||||||
"pitch": f["pitch"],
|
"pitch": f["pitch"],
|
||||||
|
|
@ -820,24 +842,78 @@ class CameraIMU(ModuloDiagnosticoBase):
|
||||||
"roll_rate_dps": f["roll_rate_dps"],
|
"roll_rate_dps": f["roll_rate_dps"],
|
||||||
"pitch_rate_dps": f["pitch_rate_dps"],
|
"pitch_rate_dps": f["pitch_rate_dps"],
|
||||||
"yaw_rate_dps": f["yaw_rate_dps"],
|
"yaw_rate_dps": f["yaw_rate_dps"],
|
||||||
"confidence": f["confidence"],
|
"confidence": min(float(f["confidence"]), 1.0),
|
||||||
"fonte_principal": f["mx_id"],
|
"fonte_principal": f["mx_id"],
|
||||||
"single_source": True,
|
"single_source": True,
|
||||||
|
"estrategia_fusao": "single_source",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Com duas ou mais, média ponderada por confiança.
|
fonte_principal = max(
|
||||||
fonte_principal = max(fontes, key=lambda x: x.get("confidence", 0.0))
|
fontes,
|
||||||
|
key=lambda f: f.get("confidence", 0.0),
|
||||||
|
)
|
||||||
|
|
||||||
|
if sensores_agree:
|
||||||
return {
|
return {
|
||||||
"roll": self._weighted_mean(fontes, "roll"),
|
"roll": self._weighted_mean(fontes, "roll"),
|
||||||
"pitch": self._weighted_mean(fontes, "pitch"),
|
"pitch": self._weighted_mean(fontes, "pitch"),
|
||||||
"yaw": self._fused_yaw(fontes),
|
"yaw": self._fused_yaw(fontes),
|
||||||
"roll_rate_dps": self._weighted_mean(fontes, "roll_rate_dps"),
|
"roll_rate_dps": self._weighted_mean(
|
||||||
"pitch_rate_dps": self._weighted_mean(fontes, "pitch_rate_dps"),
|
fontes,
|
||||||
"yaw_rate_dps": self._weighted_mean(fontes, "yaw_rate_dps"),
|
"roll_rate_dps",
|
||||||
"confidence": sum(float(f.get("confidence", 0.0)) for f in fontes),
|
),
|
||||||
|
"pitch_rate_dps": self._weighted_mean(
|
||||||
|
fontes,
|
||||||
|
"pitch_rate_dps",
|
||||||
|
),
|
||||||
|
"yaw_rate_dps": self._weighted_mean(
|
||||||
|
fontes,
|
||||||
|
"yaw_rate_dps",
|
||||||
|
),
|
||||||
|
"confidence": min(
|
||||||
|
max(float(f["confidence"]) for f in fontes),
|
||||||
|
1.0,
|
||||||
|
),
|
||||||
"fonte_principal": fonte_principal["mx_id"],
|
"fonte_principal": fonte_principal["mx_id"],
|
||||||
"single_source": False,
|
"single_source": False,
|
||||||
|
"estrategia_fusao": "weighted_agreement",
|
||||||
|
}
|
||||||
|
|
||||||
|
roll, fonte_roll = self._valor_mais_conservador(
|
||||||
|
fontes,
|
||||||
|
"roll",
|
||||||
|
)
|
||||||
|
pitch, fonte_pitch = self._valor_mais_conservador(
|
||||||
|
fontes,
|
||||||
|
"pitch",
|
||||||
|
)
|
||||||
|
roll_rate, fonte_roll_rate = self._valor_mais_conservador(
|
||||||
|
fontes,
|
||||||
|
"roll_rate_dps",
|
||||||
|
)
|
||||||
|
pitch_rate, fonte_pitch_rate = self._valor_mais_conservador(
|
||||||
|
fontes,
|
||||||
|
"pitch_rate_dps",
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
"roll": roll,
|
||||||
|
"pitch": pitch,
|
||||||
|
"yaw": fonte_principal["yaw"],
|
||||||
|
"roll_rate_dps": roll_rate,
|
||||||
|
"pitch_rate_dps": pitch_rate,
|
||||||
|
"yaw_rate_dps": fonte_principal["yaw_rate_dps"],
|
||||||
|
"confidence": min(
|
||||||
|
float(fonte_principal["confidence"]),
|
||||||
|
1.0,
|
||||||
|
),
|
||||||
|
"fonte_principal": fonte_principal["mx_id"],
|
||||||
|
"fonte_roll": fonte_roll,
|
||||||
|
"fonte_pitch": fonte_pitch,
|
||||||
|
"fonte_roll_rate": fonte_roll_rate,
|
||||||
|
"fonte_pitch_rate": fonte_pitch_rate,
|
||||||
|
"single_source": False,
|
||||||
|
"estrategia_fusao": "conservative_disagreement",
|
||||||
}
|
}
|
||||||
|
|
||||||
def _atualizar_filtro_fusao(self, fusao):
|
def _atualizar_filtro_fusao(self, fusao):
|
||||||
|
|
@ -1272,6 +1348,11 @@ class CameraIMU(ModuloDiagnosticoBase):
|
||||||
"confidence": round(float(fusao.get("confidence", 0.0)), 3),
|
"confidence": round(float(fusao.get("confidence", 0.0)), 3),
|
||||||
"fonte_principal": fonte_principal,
|
"fonte_principal": fonte_principal,
|
||||||
"single_source": bool(fusao.get("single_source", False)),
|
"single_source": bool(fusao.get("single_source", False)),
|
||||||
|
"estrategia_fusao": fusao.get("estrategia_fusao"),
|
||||||
|
"fonte_roll": fusao.get("fonte_roll"),
|
||||||
|
"fonte_pitch": fusao.get("fonte_pitch"),
|
||||||
|
"fonte_roll_rate": fusao.get("fonte_roll_rate"),
|
||||||
|
"fonte_pitch_rate": fusao.get("fonte_pitch_rate"),
|
||||||
|
|
||||||
"modulos_imu_configurados": [
|
"modulos_imu_configurados": [
|
||||||
self._nome_codigo(modulo) for modulo in self.modulos_imu
|
self._nome_codigo(modulo) for modulo in self.modulos_imu
|
||||||
|
|
@ -1294,6 +1375,7 @@ class CameraIMU(ModuloDiagnosticoBase):
|
||||||
{
|
{
|
||||||
"mx_id": f["mx_id"],
|
"mx_id": f["mx_id"],
|
||||||
"modelo": f.get("modelo"),
|
"modelo": f.get("modelo"),
|
||||||
|
"referencial_robo": f.get("referencial_robo"),
|
||||||
"t_code": f.get("t_code"),
|
"t_code": f.get("t_code"),
|
||||||
"roll": round(f["roll"], 3),
|
"roll": round(f["roll"], 3),
|
||||||
"pitch": round(f["pitch"], 3),
|
"pitch": round(f["pitch"], 3),
|
||||||
|
|
@ -1368,6 +1450,11 @@ class CameraIMU(ModuloDiagnosticoBase):
|
||||||
and self._modulos_imu_configurados.issubset(modulos_validos)
|
and self._modulos_imu_configurados.issubset(modulos_validos)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
assinatura_fontes = tuple(sorted(f["mx_id"] for f in fontes))
|
||||||
|
if assinatura_fontes != self._assinatura_fontes_anterior:
|
||||||
|
self._resetar_filtro_fusao()
|
||||||
|
self._assinatura_fontes_anterior = assinatura_fontes
|
||||||
|
|
||||||
if len(fontes) <= 0:
|
if len(fontes) <= 0:
|
||||||
self._resetar_filtro_fusao()
|
self._resetar_filtro_fusao()
|
||||||
data = self._montar_saida_sem_imu(
|
data = self._montar_saida_sem_imu(
|
||||||
|
|
@ -1376,9 +1463,10 @@ class CameraIMU(ModuloDiagnosticoBase):
|
||||||
agora,
|
agora,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
fusao = self._fundir_fontes(fontes)
|
|
||||||
divergencia = self._calcular_divergencia(fontes)
|
divergencia = self._calcular_divergencia(fontes)
|
||||||
|
|
||||||
|
fusao = self._fundir_fontes(fontes, sensores_agree=divergencia["sensors_agree"])
|
||||||
|
|
||||||
# Atualiza o filtro antes de avaliar o risco.
|
# Atualiza o filtro antes de avaliar o risco.
|
||||||
atitude_filtrada = self._atualizar_filtro_fusao(fusao)
|
atitude_filtrada = self._atualizar_filtro_fusao(fusao)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -245,7 +245,7 @@ namespace OperationControl.ViewModels
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var interfaces = EthernetService.ObterNomesInterfaces()
|
var interfaces = EthernetService.ObterNomesInterfaces()
|
||||||
.Where(x => x.Contains(AppShell.Mock ? "Ativa" : "Ethernet"))
|
.Where(x => x.Contains("AG"))
|
||||||
.Select(x => x.Split('(')[0].Trim())
|
.Select(x => x.Split('(')[0].Trim())
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue