saude do atuador, telemetria recente sensoriamento, robustez atuador, robustez sensoriamento e diagnostico i2c, ciclo de vida do can service ethernet, firmware sensoriamento com barramento i2c melhor gerenciado
This commit is contained in:
parent
8054055ec8
commit
9410933079
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -4699,6 +4699,7 @@ namespace AgroBase.Models
|
||||||
model.RequisitarDados = dados.RequisitarDados;
|
model.RequisitarDados = dados.RequisitarDados;
|
||||||
|
|
||||||
model.LatenciaLoopMs = leitura?.LatenciaLoop ?? 0;
|
model.LatenciaLoopMs = leitura?.LatenciaLoop ?? 0;
|
||||||
|
model.StatusI2C = new Dictionary<CanMessagePosicaoDados, byte[]>(leitura?.StatusI2C ?? new Dictionary<CanMessagePosicaoDados, byte[]>());
|
||||||
|
|
||||||
model.UltimoComandoRespondido = leitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
model.UltimoComandoRespondido = leitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
||||||
model.IdadeUltimoComandoMs = IdadeMs(model.UltimoComandoRespondido);
|
model.IdadeUltimoComandoMs = IdadeMs(model.UltimoComandoRespondido);
|
||||||
|
|
@ -6132,6 +6133,7 @@ namespace AgroBase.Models
|
||||||
public bool RequisitarDados { get; set; }
|
public bool RequisitarDados { get; set; }
|
||||||
|
|
||||||
public int LatenciaLoopMs { get; set; }
|
public int LatenciaLoopMs { get; set; }
|
||||||
|
public Dictionary<CanMessagePosicaoDados, byte[]> StatusI2C { get; set; }
|
||||||
|
|
||||||
public DateTime UltimoComandoRespondido { get; set; } = DateTime.MinValue;
|
public DateTime UltimoComandoRespondido { get; set; } = DateTime.MinValue;
|
||||||
public double IdadeUltimoComandoMs { get; set; }
|
public double IdadeUltimoComandoMs { get; set; }
|
||||||
|
|
@ -6196,6 +6198,7 @@ namespace AgroBase.Models
|
||||||
RequisitarDados = RequisitarDados,
|
RequisitarDados = RequisitarDados,
|
||||||
|
|
||||||
LatenciaLoopMs = LatenciaLoopMs,
|
LatenciaLoopMs = LatenciaLoopMs,
|
||||||
|
StatusI2C = new Dictionary<CanMessagePosicaoDados, byte[]>(StatusI2C ?? new Dictionary<CanMessagePosicaoDados, byte[]>()),
|
||||||
|
|
||||||
UltimoComandoRespondido = UltimoComandoRespondido,
|
UltimoComandoRespondido = UltimoComandoRespondido,
|
||||||
IdadeUltimoComandoMs = IdadeUltimoComandoMs,
|
IdadeUltimoComandoMs = IdadeUltimoComandoMs,
|
||||||
|
|
|
||||||
|
|
@ -315,8 +315,7 @@ namespace AgroBase.Services
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MostrarLog($"Erro ao inicializar: {ex.Message}");
|
MostrarLog($"Erro ao inicializar: {ex.Message}");
|
||||||
IsConnected = false;
|
MarcarTransporteIndisponivel();
|
||||||
Iniciado = false;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -326,23 +325,41 @@ namespace AgroBase.Services
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_tcpClient = new TcpClient();
|
_tcpClient = new TcpClient();
|
||||||
|
|
||||||
_tcpClient.NoDelay = true;
|
_tcpClient.NoDelay = true;
|
||||||
_tcpClient.ReceiveBufferSize = 64 * 1024;
|
_tcpClient.ReceiveBufferSize = 64 * 1024;
|
||||||
_tcpClient.SendBufferSize = 64 * 1024;
|
_tcpClient.SendBufferSize = 64 * 1024;
|
||||||
|
|
||||||
var task = _tcpClient.ConnectAsync(IpAddress, Port);
|
var task = _tcpClient.ConnectAsync(IpAddress, Port);
|
||||||
if (!task.Wait(1200))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (!_tcpClient.Connected)
|
if (!task.Wait(1200) || !_tcpClient.Connected)
|
||||||
|
{
|
||||||
|
try { _tcpClient.Close(); }
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
_tcpClient = null;
|
||||||
|
_tcpStream = null;
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
_tcpStream = _tcpClient.GetStream();
|
_tcpStream = _tcpClient.GetStream();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
try { _tcpStream?.Close(); }
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
try { _tcpClient?.Close(); }
|
||||||
|
catch { }
|
||||||
|
|
||||||
|
_tcpStream = null;
|
||||||
|
_tcpClient = null;
|
||||||
|
|
||||||
MostrarLog($"Erro TCP: {ex.Message}");
|
MostrarLog($"Erro TCP: {ex.Message}");
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -368,18 +385,15 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
private void DefinirDispositivo()
|
private void DefinirDispositivo()
|
||||||
{
|
{
|
||||||
if (!SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Can))
|
if (!IsConnected || !Iniciado)
|
||||||
|
return;
|
||||||
|
|
||||||
|
SerialService.AdicionarOuAtualizarDispositivoMapeado(new DispositivoDetalhesModel
|
||||||
{
|
{
|
||||||
if (IsConnected && Iniciado)
|
Dispositivo = T_Code.Can,
|
||||||
{
|
Endereco = IpAddress,
|
||||||
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
|
Versao = "1"
|
||||||
{
|
});
|
||||||
Dispositivo = T_Code.Can,
|
|
||||||
Endereco = IpAddress,
|
|
||||||
Versao = ("1").ToString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
public bool AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
||||||
|
|
@ -599,7 +613,7 @@ namespace AgroBase.Services
|
||||||
if (!EnviarPacote(packet, msg.IdTx))
|
if (!EnviarPacote(packet, msg.IdTx))
|
||||||
{
|
{
|
||||||
MostrarLog($"Erro ao enviar frame ID=0x{msg.IdTx:X}", msg.IdTx);
|
MostrarLog($"Erro ao enviar frame ID=0x{msg.IdTx:X}", msg.IdTx);
|
||||||
IsConnected = false;
|
MarcarTransporteIndisponivel();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -695,7 +709,7 @@ namespace AgroBase.Services
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
MostrarLog($"Erro RX: {ex.Message}");
|
MostrarLog($"Erro RX: {ex.Message}");
|
||||||
IsConnected = false;
|
MarcarTransporteIndisponivel();
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
|
|
@ -703,54 +717,71 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
private void OuvirTcp()
|
private void OuvirTcp()
|
||||||
{
|
{
|
||||||
if (_tcpClient == null || _tcpStream == null || !_tcpClient.Connected)
|
lock (_socketLock)
|
||||||
{
|
{
|
||||||
IsConnected = false;
|
if (_tcpClient == null || _tcpStream == null || !_tcpClient.Connected)
|
||||||
return;
|
{
|
||||||
}
|
MarcarTransporteIndisponivel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
while (_tcpStream.DataAvailable)
|
while (_tcpStream.DataAvailable)
|
||||||
{
|
{
|
||||||
byte[] buffer = new byte[4096];
|
byte[] buffer = new byte[4096];
|
||||||
int lidos = _tcpStream.Read(buffer, 0, buffer.Length);
|
|
||||||
if (lidos <= 0)
|
|
||||||
break;
|
|
||||||
|
|
||||||
for (int i = 0; i < lidos; i++)
|
int lidos = _tcpStream.Read(buffer, 0, buffer.Length);
|
||||||
TcpRxBuffer.Add(buffer[i]);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (TcpRxBuffer.Count >= USR_FRAME_SIZE)
|
if (lidos <= 0)
|
||||||
{
|
break;
|
||||||
byte[] packet = TcpRxBuffer.Take(USR_FRAME_SIZE).ToArray();
|
|
||||||
TcpRxBuffer.RemoveRange(0, USR_FRAME_SIZE);
|
for (int i = 0; i < lidos; i++)
|
||||||
ProcessarPacoteUsrStandard(packet);
|
TcpRxBuffer.Add(buffer[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (TcpRxBuffer.Count >= USR_FRAME_SIZE)
|
||||||
|
{
|
||||||
|
byte[] packet = TcpRxBuffer.Take(USR_FRAME_SIZE).ToArray();
|
||||||
|
|
||||||
|
TcpRxBuffer.RemoveRange(0, USR_FRAME_SIZE);
|
||||||
|
|
||||||
|
ProcessarPacoteUsrStandard(packet);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OuvirUdp()
|
private void OuvirUdp()
|
||||||
{
|
{
|
||||||
if (_udpClient == null)
|
lock (_socketLock)
|
||||||
{
|
{
|
||||||
IsConnected = false;
|
if (_udpClient == null)
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (_udpClient.Available > 0)
|
|
||||||
{
|
|
||||||
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
|
|
||||||
byte[] pacote = _udpClient.Receive(ref ep);
|
|
||||||
|
|
||||||
if (pacote == null || pacote.Length == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
int offset = 0;
|
|
||||||
while (offset + USR_FRAME_SIZE <= pacote.Length)
|
|
||||||
{
|
{
|
||||||
byte[] frame = new byte[USR_FRAME_SIZE];
|
MarcarTransporteIndisponivel();
|
||||||
Buffer.BlockCopy(pacote, offset, frame, 0, USR_FRAME_SIZE);
|
return;
|
||||||
ProcessarPacoteUsrStandard(frame);
|
}
|
||||||
offset += USR_FRAME_SIZE;
|
|
||||||
|
while (_udpClient.Available > 0)
|
||||||
|
{
|
||||||
|
IPEndPoint ep = new IPEndPoint(IPAddress.Any, 0);
|
||||||
|
|
||||||
|
byte[] pacote = _udpClient.Receive(ref ep);
|
||||||
|
|
||||||
|
if (pacote == null || pacote.Length == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int offset = 0;
|
||||||
|
|
||||||
|
while (offset + USR_FRAME_SIZE <= pacote.Length)
|
||||||
|
{
|
||||||
|
byte[] frame = new byte[USR_FRAME_SIZE];
|
||||||
|
|
||||||
|
Buffer.BlockCopy(pacote, offset, frame, 0, USR_FRAME_SIZE);
|
||||||
|
|
||||||
|
ProcessarPacoteUsrStandard(frame);
|
||||||
|
|
||||||
|
offset += USR_FRAME_SIZE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1142,8 +1173,8 @@ namespace AgroBase.Services
|
||||||
DateTime agora = DateTime.Now;
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
bool socketOk = TransportMode == CanEthernetTransportMode.Tcp
|
bool socketOk = TransportMode == CanEthernetTransportMode.Tcp
|
||||||
? (_tcpClient != null && _tcpClient.Connected && _tcpStream != null)
|
? TcpSocketSaudavel()
|
||||||
: (_udpClient != null);
|
: _udpClient != null;
|
||||||
|
|
||||||
double ageTx = _ultimoTx == DateTime.MinValue
|
double ageTx = _ultimoTx == DateTime.MinValue
|
||||||
? 999999
|
? 999999
|
||||||
|
|
@ -1159,13 +1190,11 @@ namespace AgroBase.Services
|
||||||
_socketIndisponivelDesde = agora;
|
_socketIndisponivelDesde = agora;
|
||||||
|
|
||||||
double tempoSuspeito = (agora - _socketIndisponivelDesde).TotalSeconds;
|
double tempoSuspeito = (agora - _socketIndisponivelDesde).TotalSeconds;
|
||||||
|
if (tempoSuspeito >= 1.0 && (IsConnected || Iniciado))
|
||||||
bool semTrafegoRecente = ageTx > 2.0 && ageRx > 2.0;
|
|
||||||
|
|
||||||
if (tempoSuspeito >= 1.0 && semTrafegoRecente)
|
|
||||||
{
|
{
|
||||||
IsConnected = false;
|
MarcarTransporteIndisponivel();
|
||||||
RegistrarErroCritico("socket_indisponivel_confirmado");
|
RegistrarErroCritico("socket_indisponivel_confirmado");
|
||||||
|
MostrarLog("Socket CAN-ETH indisponível; aguardando reinicialização do transporte...");
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -1174,25 +1203,50 @@ namespace AgroBase.Services
|
||||||
_socketIndisponivelDesde = DateTime.MinValue;
|
_socketIndisponivelDesde = DateTime.MinValue;
|
||||||
|
|
||||||
bool existeDemanda = ExisteDemandaCan();
|
bool existeDemanda = ExisteDemandaCan();
|
||||||
|
bool txAtivo = ageTx <= 2.0 || FreqTxJanela > 0.5;
|
||||||
// Se existe demanda, mas houve TX recente e não houve RX,
|
if (IsConnected && existeDemanda && txAtivo && ageRx > 8.0)
|
||||||
// isso parece mais "dispositivo CAN não respondeu" do que "socket morreu".
|
|
||||||
// Deixa o timeout da fila e o health tratarem.
|
|
||||||
if (IsConnected && existeDemanda && ageTx > 10.0 && ageRx > 10.0)
|
|
||||||
{
|
{
|
||||||
RegistrarErroCritico("sem_trafego_prolongado_com_demanda");
|
RegistrarErroCritico("tx_ativo_sem_rx_prolongado");
|
||||||
MostrarLog("Sem tráfego prolongado no CAN-ETH com demanda, reiniciando conexão...");
|
MostrarLog("TX ativo sem RX prolongado; reiniciando conexão CAN-ETH...");
|
||||||
ReabrirConexao();
|
ReabrirConexao();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_ultimoErroCritico != DateTime.MinValue &&
|
if (_ultimoErroCritico != DateTime.MinValue && (agora - _ultimoErroCritico).TotalMinutes > 10)
|
||||||
(agora - _ultimoErroCritico).TotalMinutes > 10)
|
|
||||||
{
|
{
|
||||||
ErrosCriticos = 0;
|
ErrosCriticos = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ReabrirConexao()
|
private bool TcpSocketSaudavel()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
if (_tcpClient == null ||
|
||||||
|
_tcpClient.Client == null ||
|
||||||
|
_tcpStream == null ||
|
||||||
|
!_tcpClient.Connected)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket socket = _tcpClient.Client;
|
||||||
|
|
||||||
|
bool remotoFechou =
|
||||||
|
socket.Poll(
|
||||||
|
0,
|
||||||
|
SelectMode.SelectRead
|
||||||
|
) &&
|
||||||
|
socket.Available == 0;
|
||||||
|
|
||||||
|
return !remotoFechou;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ReabrirConexao()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -1200,15 +1254,23 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
FecharConexaoSemLimparHandlers();
|
FecharConexaoSemLimparHandlers();
|
||||||
|
|
||||||
if (TransportMode == CanEthernetTransportMode.Tcp)
|
bool conectado = TransportMode == CanEthernetTransportMode.Tcp
|
||||||
IsConnected = ConectarTcp();
|
? ConectarTcp()
|
||||||
else
|
: ConectarUdp();
|
||||||
IsConnected = ConectarUdp();
|
|
||||||
|
|
||||||
Iniciado = IsConnected;
|
IsConnected = conectado;
|
||||||
|
Iniciado = conectado;
|
||||||
|
|
||||||
|
if (!conectado)
|
||||||
|
{
|
||||||
|
MostrarLog($"Reconexão CAN-ETH falhou em {IpAddress}:{Port}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
ReconexoesTotal++;
|
ReconexoesTotal++;
|
||||||
|
|
||||||
|
_socketIndisponivelDesde = DateTime.MinValue;
|
||||||
|
|
||||||
_ultimoRx = DateTime.Now;
|
_ultimoRx = DateTime.Now;
|
||||||
_ultimoTx = DateTime.Now;
|
_ultimoTx = DateTime.Now;
|
||||||
|
|
||||||
|
|
@ -1217,11 +1279,16 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
UltimoRxUnixMs = NowUnixMs();
|
UltimoRxUnixMs = NowUnixMs();
|
||||||
UltimoTxUnixMs = NowUnixMs();
|
UltimoTxUnixMs = NowUnixMs();
|
||||||
|
|
||||||
|
MostrarLog($"CAN-ETH reconectado em {IpAddress}:{Port}");
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
catch
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
IsConnected = false;
|
MarcarTransporteIndisponivel();
|
||||||
Iniciado = false;
|
MostrarLog($"Erro ao reabrir CAN-ETH: {ex.Message}");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1260,6 +1327,12 @@ namespace AgroBase.Services
|
||||||
Iniciado = false;
|
Iniciado = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void MarcarTransporteIndisponivel()
|
||||||
|
{
|
||||||
|
IsConnected = false;
|
||||||
|
Iniciado = false;
|
||||||
|
}
|
||||||
|
|
||||||
public void Fechar()
|
public void Fechar()
|
||||||
{
|
{
|
||||||
tmrOuvirCAN?.Dispose();
|
tmrOuvirCAN?.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -445,14 +445,14 @@ namespace AgroBase.Services
|
||||||
if (leitura == null)
|
if (leitura == null)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
|
SerialService.AdicionarOuAtualizarDispositivoMapeado(new DispositivoDetalhesModel()
|
||||||
{
|
{
|
||||||
Dispositivo = T_Code.Dir,
|
Dispositivo = T_Code.Dir,
|
||||||
Endereco = CanManager.CanService._portName,
|
Endereco = CanManager.CanService._portName,
|
||||||
Versao = (leitura.FV ?? "").ToString(),
|
Versao = (leitura.FV ?? "").ToString(),
|
||||||
Mod_ID = modId,
|
Mod_ID = modId,
|
||||||
});
|
});
|
||||||
|
|
||||||
op.Sensoriamento.Operacao.ModsCalibragem.TryGetValue(T_Code.Dir, out var m);
|
op.Sensoriamento.Operacao.ModsCalibragem.TryGetValue(T_Code.Dir, out var m);
|
||||||
|
|
||||||
if (m == null)
|
if (m == null)
|
||||||
|
|
|
||||||
|
|
@ -456,7 +456,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
if (!SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Mov && x.Mod_ID == modId))
|
if (!SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Mov && x.Mod_ID == modId))
|
||||||
{
|
{
|
||||||
SerialService.DispositivosMapeados.Add(
|
SerialService.AdicionarOuAtualizarDispositivoMapeado(
|
||||||
new DispositivoDetalhesModel
|
new DispositivoDetalhesModel
|
||||||
{
|
{
|
||||||
Dispositivo = T_Code.Mov,
|
Dispositivo = T_Code.Mov,
|
||||||
|
|
|
||||||
|
|
@ -164,7 +164,7 @@ namespace AgroBase.Services.Operadores
|
||||||
double pressaoAlvo = bombaLinha?._PressaoSPControle > 0 ? bombaLinha._PressaoSPControle : op.Parametros?.Controle?.AtuPressaoLinha ?? 0;
|
double pressaoAlvo = bombaLinha?._PressaoSPControle > 0 ? bombaLinha._PressaoSPControle : op.Parametros?.Controle?.AtuPressaoLinha ?? 0;
|
||||||
double pressaoLinha = bombaLinha?._PressaoAtual ?? _Sensoriamento?.Atuador?.PressaoLinha ?? 0;
|
double pressaoLinha = bombaLinha?._PressaoAtual ?? _Sensoriamento?.Atuador?.PressaoLinha ?? 0;
|
||||||
double toleranciaPressao = Math.Max(2.0, pressaoAlvo * 0.15);
|
double toleranciaPressao = Math.Max(2.0, pressaoAlvo * 0.15);
|
||||||
bool pressaoLinhaRegular = pressaoAlvo <= 0 || Math.Abs(pressaoLinha - pressaoAlvo) <= toleranciaPressao;
|
bool pressaoLinhaRegular = pressaoAlvo <= 0 || pressaoLinha >= Math.Max(0, pressaoAlvo - toleranciaPressao);
|
||||||
|
|
||||||
RedisService.AtualizarCampos(
|
RedisService.AtualizarCampos(
|
||||||
CtxKey.DadosContexto,
|
CtxKey.DadosContexto,
|
||||||
|
|
@ -222,7 +222,7 @@ namespace AgroBase.Services.Operadores
|
||||||
("Atuador", new
|
("Atuador", new
|
||||||
{
|
{
|
||||||
pressao_linha_regular = pressaoLinhaRegular,
|
pressao_linha_regular = pressaoLinhaRegular,
|
||||||
pressao_linha = _Sensoriamento.Atuador?.PressaoLinha ?? 0,
|
pressao_linha = pressaoLinha,
|
||||||
potencia_bomba = _Sensoriamento.Atuador?.LeituraPotenciaBomba ?? 0,
|
potencia_bomba = _Sensoriamento.Atuador?.LeituraPotenciaBomba ?? 0,
|
||||||
estado_bomba = _Sensoriamento.Atuador?.LeituraComandoBomba ?? false,
|
estado_bomba = _Sensoriamento.Atuador?.LeituraComandoBomba ?? false,
|
||||||
tempo_bomba_atuada = _Sensoriamento.Atuador?.TempoBombaAtuada ?? 0,
|
tempo_bomba_atuada = _Sensoriamento.Atuador?.TempoBombaAtuada ?? 0,
|
||||||
|
|
@ -537,6 +537,17 @@ namespace AgroBase.Services.Operadores
|
||||||
var agora_dt = DateTime.Now;
|
var agora_dt = DateTime.Now;
|
||||||
var agora = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
var agora = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||||
|
|
||||||
|
double CalcularIdadeMs(DateTime instante)
|
||||||
|
{
|
||||||
|
if (instante == DateTime.MinValue)
|
||||||
|
return 999999;
|
||||||
|
|
||||||
|
return Math.Max(
|
||||||
|
0,
|
||||||
|
(agora_dt - instante).TotalMilliseconds
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Gps)));
|
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Gps)));
|
||||||
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Dir)));
|
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Dir)));
|
||||||
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Mov)));
|
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Mov)));
|
||||||
|
|
@ -547,6 +558,8 @@ namespace AgroBase.Services.Operadores
|
||||||
//RedisService.SetObject(RedisService.ModKey(Enums.T_Code.Mov), string.Empty);
|
//RedisService.SetObject(RedisService.ModKey(Enums.T_Code.Mov), string.Empty);
|
||||||
//RedisService.SetObject(RedisService.ModKey(Enums.T_Code.Sen), string.Empty);
|
//RedisService.SetObject(RedisService.ModKey(Enums.T_Code.Sen), string.Empty);
|
||||||
|
|
||||||
|
#region MOV e DIR
|
||||||
|
|
||||||
var dirAtivos = new List<int>();
|
var dirAtivos = new List<int>();
|
||||||
var movAtivos = new List<int>();
|
var movAtivos = new List<int>();
|
||||||
|
|
||||||
|
|
@ -700,18 +713,20 @@ namespace AgroBase.Services.Operadores
|
||||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Dir), ("ativos", dirAtivos.ToList()));
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Dir), ("ativos", dirAtivos.ToList()));
|
||||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Mov), ("ativos", movAtivos.ToList()));
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Mov), ("ativos", movAtivos.ToList()));
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region SEN
|
||||||
|
|
||||||
var DadosSen = op.DispSen.Dados;
|
var DadosSen = op.DispSen.Dados;
|
||||||
DateTime ultimaRespostaSen = DadosSen.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
DateTime ultimaRespostaSen = DadosSen.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
||||||
|
|
||||||
double idadeUltimaRespostaSenMs =
|
double idadeUltimaRespostaSenMs = CalcularIdadeMs(ultimaRespostaSen);
|
||||||
ultimaRespostaSen == DateTime.MinValue
|
|
||||||
? 999999
|
|
||||||
: (agora_dt - ultimaRespostaSen).TotalMilliseconds;
|
|
||||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen),
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen),
|
||||||
("timestamp", agora),
|
("timestamp", agora),
|
||||||
("conectado", DadosSen.Conectado),
|
("conectado", DadosSen.Conectado),
|
||||||
|
("telemetria_recente", DadosSen.TelemetriaRecente),
|
||||||
|
("firmware_conectado", DadosSen.FirmwareDeclarouConectado),
|
||||||
("freq_base", DadosSen.FrequenciaBase),
|
("freq_base", DadosSen.FrequenciaBase),
|
||||||
("bms_ligado", _Sensoriamento.Bateria?.BmsLigado ?? false),
|
("bms_ligado", _Sensoriamento.Bateria?.BmsLigado ?? false),
|
||||||
|
|
||||||
|
|
@ -743,10 +758,7 @@ namespace AgroBase.Services.Operadores
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
double idadeUltimaRespostaSensorMs =
|
double idadeUltimaRespostaSensorMs = CalcularIdadeMs(sensor.UltimaLeitura);
|
||||||
sensor.UltimaLeitura == DateTime.MinValue
|
|
||||||
? 999999
|
|
||||||
: (agora_dt - sensor.UltimaLeitura).TotalMilliseconds;
|
|
||||||
|
|
||||||
var sensorObj = new
|
var sensorObj = new
|
||||||
{
|
{
|
||||||
|
|
@ -754,6 +766,7 @@ namespace AgroBase.Services.Operadores
|
||||||
id,
|
id,
|
||||||
label = sensor.ID,
|
label = sensor.ID,
|
||||||
tipo,
|
tipo,
|
||||||
|
freq_base = DadosSen.FrequenciaBase,
|
||||||
aferir = sensor.Aferir,
|
aferir = sensor.Aferir,
|
||||||
mandatorio = sensor.Mandatorio,
|
mandatorio = sensor.Mandatorio,
|
||||||
dados,
|
dados,
|
||||||
|
|
@ -799,15 +812,8 @@ namespace AgroBase.Services.Operadores
|
||||||
|
|
||||||
bool possuiRespostaServo = ultimaRespostaAnguloServo != DateTime.MinValue;
|
bool possuiRespostaServo = ultimaRespostaAnguloServo != DateTime.MinValue;
|
||||||
|
|
||||||
double idadeUltimoComandoServoMs =
|
double idadeUltimoComandoServoMs = CalcularIdadeMs(servo.UltimoComandoEnviado);
|
||||||
!possuiComandoServo
|
double idadeUltimaRespostaServoMs = CalcularIdadeMs(ultimaRespostaAnguloServo);
|
||||||
? 999999
|
|
||||||
: Math.Max(0, (agora_dt - servo.UltimoComandoEnviado).TotalMilliseconds);
|
|
||||||
|
|
||||||
double idadeUltimaRespostaServoMs =
|
|
||||||
!possuiRespostaServo
|
|
||||||
? 999999
|
|
||||||
: Math.Max(0, (agora_dt - ultimaRespostaAnguloServo).TotalMilliseconds);
|
|
||||||
|
|
||||||
bool telemetriaPosComandoServo =
|
bool telemetriaPosComandoServo =
|
||||||
possuiComandoServo &&
|
possuiComandoServo &&
|
||||||
|
|
@ -820,6 +826,7 @@ namespace AgroBase.Services.Operadores
|
||||||
id,
|
id,
|
||||||
label = servo.ID,
|
label = servo.ID,
|
||||||
tipo,
|
tipo,
|
||||||
|
freq_base = DadosSen.FrequenciaBase,
|
||||||
controlar = servo.Controlar,
|
controlar = servo.Controlar,
|
||||||
mandatorio = servo.Mandatorio,
|
mandatorio = servo.Mandatorio,
|
||||||
|
|
||||||
|
|
@ -875,15 +882,8 @@ namespace AgroBase.Services.Operadores
|
||||||
|
|
||||||
bool possuiRespostaRele = ultimaRespostaEstadoRele != DateTime.MinValue;
|
bool possuiRespostaRele = ultimaRespostaEstadoRele != DateTime.MinValue;
|
||||||
|
|
||||||
double idadeUltimoComandoReleMs =
|
double idadeUltimoComandoReleMs = CalcularIdadeMs(rele.UltimoComandoEnviado);
|
||||||
!possuiComandoRele
|
double idadeUltimaRespostaReleMs = CalcularIdadeMs(ultimaRespostaEstadoRele);
|
||||||
? 999999
|
|
||||||
: Math.Max(0, (agora_dt - rele.UltimoComandoEnviado).TotalMilliseconds);
|
|
||||||
|
|
||||||
double idadeUltimaRespostaReleMs =
|
|
||||||
!possuiRespostaRele
|
|
||||||
? 999999
|
|
||||||
: Math.Max(0, (agora_dt - ultimaRespostaEstadoRele).TotalMilliseconds);
|
|
||||||
|
|
||||||
bool telemetriaPosComandoRele =
|
bool telemetriaPosComandoRele =
|
||||||
possuiComandoRele &&
|
possuiComandoRele &&
|
||||||
|
|
@ -896,6 +896,7 @@ namespace AgroBase.Services.Operadores
|
||||||
id,
|
id,
|
||||||
label = rele.ID,
|
label = rele.ID,
|
||||||
tipo,
|
tipo,
|
||||||
|
freq_base = DadosSen.FrequenciaBase,
|
||||||
controlar = rele.Controlar,
|
controlar = rele.Controlar,
|
||||||
mandatorio = rele.Mandatorio,
|
mandatorio = rele.Mandatorio,
|
||||||
dados,
|
dados,
|
||||||
|
|
@ -966,15 +967,8 @@ namespace AgroBase.Services.Operadores
|
||||||
? (ultimaRespostaLedId <= ultimaRespostaLedStatus ? ultimaRespostaLedId : ultimaRespostaLedStatus)
|
? (ultimaRespostaLedId <= ultimaRespostaLedStatus ? ultimaRespostaLedId : ultimaRespostaLedStatus)
|
||||||
: DateTime.MinValue;
|
: DateTime.MinValue;
|
||||||
|
|
||||||
double idadeUltimoComandoLedMs =
|
double idadeUltimoComandoLedMs = CalcularIdadeMs(led.UltimoComandoEnviado);
|
||||||
!possuiComandoLed
|
double idadeUltimaRespostaLedMs = CalcularIdadeMs(ultimaRespostaLed);
|
||||||
? 999999
|
|
||||||
: Math.Max(0, (agora_dt - led.UltimoComandoEnviado).TotalMilliseconds);
|
|
||||||
|
|
||||||
double idadeUltimaRespostaLedMs =
|
|
||||||
!possuiRespostaLed
|
|
||||||
? 999999
|
|
||||||
: Math.Max(0, (agora_dt - ultimaRespostaLed).TotalMilliseconds);
|
|
||||||
|
|
||||||
bool telemetriaPosComandoLed =
|
bool telemetriaPosComandoLed =
|
||||||
possuiComandoLed &&
|
possuiComandoLed &&
|
||||||
|
|
@ -990,6 +984,7 @@ namespace AgroBase.Services.Operadores
|
||||||
id,
|
id,
|
||||||
label = led.ID,
|
label = led.ID,
|
||||||
tipo,
|
tipo,
|
||||||
|
freq_base = DadosSen.FrequenciaBase,
|
||||||
controlar = led.Controlar,
|
controlar = led.Controlar,
|
||||||
mandatorio = led.Mandatorio,
|
mandatorio = led.Mandatorio,
|
||||||
|
|
||||||
|
|
@ -1018,27 +1013,22 @@ namespace AgroBase.Services.Operadores
|
||||||
senLedsAtualizados.Add(("sinaleiros.ativos", senLedsAtivos));
|
senLedsAtualizados.Add(("sinaleiros.ativos", senLedsAtivos));
|
||||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), senLedsAtualizados.ToArray());
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), senLedsAtualizados.ToArray());
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region ATU
|
||||||
|
|
||||||
var DadosAtu = op.DispAtu.Dados;
|
var DadosAtu = op.DispAtu.Dados;
|
||||||
DateTime ultimaRespostaAtu = DadosAtu.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
DateTime ultimaRespostaAtu = DadosAtu.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
||||||
|
|
||||||
double idadeUltimaRespostaAtuMs =
|
double idadeUltimaRespostaAtuMs = CalcularIdadeMs(ultimaRespostaAtu);
|
||||||
ultimaRespostaAtu == DateTime.MinValue
|
|
||||||
? 999999
|
|
||||||
: (agora_dt - ultimaRespostaAtu).TotalMilliseconds;
|
|
||||||
|
|
||||||
bool pulverizadorAutomatico =
|
bool pulverizadorAutomatico = op.Parametros?.Controle?.PulverizadorAutomatico ?? false;
|
||||||
op.Parametros?.Controle?.PulverizadorAutomatico ?? false;
|
|
||||||
|
|
||||||
double pressaoAlvo =
|
double pressaoAlvo = op.Parametros?.Controle?.AtuPressaoLinha ?? 0;
|
||||||
op.Parametros?.Controle?.AtuPressaoLinha ?? 0;
|
|
||||||
|
|
||||||
double pressaoLinha =
|
double pressaoLinha = DadosAtu?.BombaPressurizadora?._PressaoAtual ?? _Sensoriamento?.Atuador?.PressaoLinha ?? 0;
|
||||||
DadosAtu.PressaoLinha;
|
double toleranciaPressao = Math.Max(2.0, pressaoAlvo * 0.15);
|
||||||
|
bool pressaoLinhaRegular = pressaoAlvo <= 0 || pressaoLinha >= Math.Max(0, pressaoAlvo - toleranciaPressao);
|
||||||
bool pressaoLinhaRegular =
|
|
||||||
pressaoAlvo <= 0 || pressaoLinha >= pressaoAlvo - 20;
|
|
||||||
RedisService.AtualizarCampos(
|
RedisService.AtualizarCampos(
|
||||||
RedisService.ModKey(Enums.T_Code.Atu),
|
RedisService.ModKey(Enums.T_Code.Atu),
|
||||||
("timestamp", agora),
|
("timestamp", agora),
|
||||||
|
|
@ -1091,10 +1081,7 @@ namespace AgroBase.Services.Operadores
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
double idadeUltimaRespostaSensorMs =
|
double idadeUltimaRespostaSensorMs = CalcularIdadeMs(sensor.UltimaLeitura);
|
||||||
sensor.UltimaLeitura == DateTime.MinValue
|
|
||||||
? 999999
|
|
||||||
: (agora_dt - sensor.UltimaLeitura).TotalMilliseconds;
|
|
||||||
|
|
||||||
var sensorObj = new
|
var sensorObj = new
|
||||||
{
|
{
|
||||||
|
|
@ -1107,6 +1094,7 @@ namespace AgroBase.Services.Operadores
|
||||||
dados,
|
dados,
|
||||||
timestamp = agora,
|
timestamp = agora,
|
||||||
saude = saude ?? new { },
|
saude = saude ?? new { },
|
||||||
|
freq_base = DadosAtu.FrequenciaBase,
|
||||||
ultima_resposta_ms = idadeUltimaRespostaSensorMs,
|
ultima_resposta_ms = idadeUltimaRespostaSensorMs,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -1139,15 +1127,11 @@ namespace AgroBase.Services.Operadores
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
double idadeUltimoComandoBicoMs =
|
DateTime ultimoComandoBico = bico.UltimoComandoEnfileirado;
|
||||||
bico.UltimoComandoEnviado == DateTime.MinValue
|
|
||||||
? 999999
|
|
||||||
: (agora_dt - bico.UltimoComandoEnviado).TotalMilliseconds;
|
|
||||||
|
|
||||||
double idadeUltimaRespostaBicoMs =
|
double idadeUltimoComandoBicoMs = CalcularIdadeMs(ultimoComandoBico);
|
||||||
bico.UltimaLeitura == DateTime.MinValue
|
|
||||||
? 999999
|
double idadeUltimaRespostaBicoMs = CalcularIdadeMs(bico.UltimaLeitura);
|
||||||
: (agora_dt - bico.UltimaLeitura).TotalMilliseconds;
|
|
||||||
|
|
||||||
var bicoObj = new
|
var bicoObj = new
|
||||||
{
|
{
|
||||||
|
|
@ -1160,6 +1144,7 @@ namespace AgroBase.Services.Operadores
|
||||||
dados,
|
dados,
|
||||||
timestamp = agora,
|
timestamp = agora,
|
||||||
saude = saude ?? new { },
|
saude = saude ?? new { },
|
||||||
|
freq_base = DadosAtu.FrequenciaBase,
|
||||||
|
|
||||||
comando_atuar = bico.ComandoAtuar,
|
comando_atuar = bico.ComandoAtuar,
|
||||||
estado_desejado = bico.UltimoStatusDesejado,
|
estado_desejado = bico.UltimoStatusDesejado,
|
||||||
|
|
@ -1181,15 +1166,15 @@ namespace AgroBase.Services.Operadores
|
||||||
vazao_media_mls = bico.VazaoMediaMLs,
|
vazao_media_mls = bico.VazaoMediaMLs,
|
||||||
volume_vazado_ml = bico.VolumeVazadoML,
|
volume_vazado_ml = bico.VolumeVazadoML,
|
||||||
|
|
||||||
comando_solicitado = bico.ComandoAtuar,
|
comando_solicitado = bico.Comandar && bico.ComandoAtuar,
|
||||||
status_desejado_fisico = bico.UltimoStatusDesejado,
|
status_desejado_fisico = bico.UltimoStatusDesejado,
|
||||||
angulo_desejado_fisico = bico.UltimoAnguloDesejado,
|
angulo_desejado_fisico = bico.UltimoAnguloDesejado,
|
||||||
ultimo_comando_ms = idadeUltimoComandoBicoMs,
|
ultimo_comando_ms = idadeUltimoComandoBicoMs,
|
||||||
ultima_resposta_ms = idadeUltimaRespostaBicoMs,
|
ultima_resposta_ms = idadeUltimaRespostaBicoMs,
|
||||||
telemetria_pos_comando =
|
telemetria_pos_comando =
|
||||||
bico.UltimoComandoEnviado != DateTime.MinValue &&
|
ultimoComandoBico != DateTime.MinValue &&
|
||||||
bico.UltimaLeitura != DateTime.MinValue &&
|
bico.UltimaRespostaPosteriorAoComando != DateTime.MinValue &&
|
||||||
bico.UltimaLeitura >= bico.UltimoComandoEnviado
|
bico.UltimaRespostaPosteriorAoComando >= ultimoComandoBico
|
||||||
};
|
};
|
||||||
|
|
||||||
atuBicosAtualizados.Add(($"{basePath}", bicoObj));
|
atuBicosAtualizados.Add(($"{basePath}", bicoObj));
|
||||||
|
|
@ -1221,15 +1206,11 @@ namespace AgroBase.Services.Operadores
|
||||||
})
|
})
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
double idadeUltimoComandoBombaMs =
|
DateTime ultimoComandoBomba = bomba.UltimoComandoEnfileirado;
|
||||||
bomba.UltimoComandoEnviado == DateTime.MinValue
|
|
||||||
? 999999
|
|
||||||
: (agora_dt - bomba.UltimoComandoEnviado).TotalMilliseconds;
|
|
||||||
|
|
||||||
double idadeUltimaRespostaBombaMs =
|
double idadeUltimoComandoBombaMs = CalcularIdadeMs(ultimoComandoBomba);
|
||||||
bomba.UltimaLeitura == DateTime.MinValue
|
|
||||||
? 999999
|
double idadeUltimaRespostaBombaMs = CalcularIdadeMs(bomba.UltimaLeitura);
|
||||||
: (agora_dt - bomba.UltimaLeitura).TotalMilliseconds;
|
|
||||||
|
|
||||||
var sensorObj = new
|
var sensorObj = new
|
||||||
{
|
{
|
||||||
|
|
@ -1242,6 +1223,7 @@ namespace AgroBase.Services.Operadores
|
||||||
dados,
|
dados,
|
||||||
timestamp = agora,
|
timestamp = agora,
|
||||||
saude = saude ?? new { },
|
saude = saude ?? new { },
|
||||||
|
freq_base = DadosAtu.FrequenciaBase,
|
||||||
|
|
||||||
comando_atuar = bomba.ComandoAtuar,
|
comando_atuar = bomba.ComandoAtuar,
|
||||||
estado_desejado = bomba.UltimoStatusDesejado,
|
estado_desejado = bomba.UltimoStatusDesejado,
|
||||||
|
|
@ -1273,13 +1255,13 @@ namespace AgroBase.Services.Operadores
|
||||||
pressao_desejada_fisica = bomba.UltimaPressaoDesejada,
|
pressao_desejada_fisica = bomba.UltimaPressaoDesejada,
|
||||||
ultimo_comando_ms = idadeUltimoComandoBombaMs,
|
ultimo_comando_ms = idadeUltimoComandoBombaMs,
|
||||||
ultima_resposta_ms = idadeUltimaRespostaBombaMs,
|
ultima_resposta_ms = idadeUltimaRespostaBombaMs,
|
||||||
telemetria_pos_comando =
|
telemetria_pos_comando =
|
||||||
bomba.UltimoComandoEnviado != DateTime.MinValue &&
|
ultimoComandoBomba != DateTime.MinValue &&
|
||||||
bomba.UltimaLeitura != DateTime.MinValue &&
|
bomba.UltimaRespostaPosteriorAoComando != DateTime.MinValue &&
|
||||||
bomba.UltimaLeitura >= bomba.UltimoComandoEnviado,
|
bomba.UltimaRespostaPosteriorAoComando >= ultimoComandoBomba,
|
||||||
|
|
||||||
desligar_sem_bicos_abertos = bomba.Controle?.DesligarSemBicosAbertos ?? true,
|
desligar_sem_bicos_abertos = bomba.Controle?.DesligarSemBicosAbertos ?? true,
|
||||||
qtd_bicos_ligados = bomba._QtdBicosLigados,
|
qtd_bicos_ligados = bomba._QtdBicosLigados,
|
||||||
|
|
||||||
potencia_final = bomba._PotenciaFinal,
|
potencia_final = bomba._PotenciaFinal,
|
||||||
potencia_feedforward = bomba._PotenciaFeedForwared,
|
potencia_feedforward = bomba._PotenciaFeedForwared,
|
||||||
pressao_sensor = bomba._PressaoSensor,
|
pressao_sensor = bomba._PressaoSensor,
|
||||||
|
|
@ -1300,6 +1282,7 @@ namespace AgroBase.Services.Operadores
|
||||||
atuBombasAtualizados.Add(("bombas.ativos", atuBombasAtivos));
|
atuBombasAtualizados.Add(("bombas.ativos", atuBombasAtivos));
|
||||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Atu), atuBombasAtualizados.ToArray());
|
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Atu), atuBombasAtualizados.ToArray());
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
|
||||||
//var DadosLra = Variaveis.LoraService;
|
//var DadosLra = Variaveis.LoraService;
|
||||||
|
|
|
||||||
|
|
@ -785,7 +785,7 @@ namespace AgroBase.Services
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CanManager.CanService.Iniciado)
|
if (!CanManager.CanService.Iniciado || !CanManager.CanService.IsConnected)
|
||||||
{
|
{
|
||||||
bool initialized = CanManager.CanService.Inicializar(250);
|
bool initialized = CanManager.CanService.Inicializar(250);
|
||||||
|
|
||||||
|
|
@ -1587,8 +1587,7 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void AdicionarOuAtualizarDispositivoMapeado(
|
public static void AdicionarOuAtualizarDispositivoMapeado(DispositivoDetalhesModel device)
|
||||||
DispositivoDetalhesModel device)
|
|
||||||
{
|
{
|
||||||
if (device == null)
|
if (device == null)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -56,6 +56,7 @@ class ModuloAtuador(ModuloDiagnosticoBase):
|
||||||
self.padroes_bicos = {}
|
self.padroes_bicos = {}
|
||||||
self.padroes_bombas = {}
|
self.padroes_bombas = {}
|
||||||
self.runtime_bombas = {}
|
self.runtime_bombas = {}
|
||||||
|
self.runtime_bicos = {}
|
||||||
|
|
||||||
def atualizar_saude(self):
|
def atualizar_saude(self):
|
||||||
try:
|
try:
|
||||||
|
|
@ -612,50 +613,149 @@ class ModuloAtuador(ModuloDiagnosticoBase):
|
||||||
dados = item["dados"]
|
dados = item["dados"]
|
||||||
label = dados.get("label")
|
label = dados.get("label")
|
||||||
|
|
||||||
|
agora = time.monotonic()
|
||||||
|
runtime = self._runtime_bico(item)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Comando desejado e estado físico
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
comando_presente = (
|
comando_presente = (
|
||||||
"comando_atuar" in dados or
|
"comando_atuar" in dados
|
||||||
"estado_desejado" in dados or
|
or "estado_desejado" in dados
|
||||||
"status_desejado_fisico" in dados or
|
or "status_desejado_fisico" in dados
|
||||||
"ultimo_status_desejado" in dados
|
or "ultimo_status_desejado" in dados
|
||||||
)
|
)
|
||||||
|
|
||||||
comando = self._estado_fisico_desejado_ligado(dados)
|
comando = self._estado_fisico_desejado_ligado(dados)
|
||||||
estado = self._get(dados, "estado_leitura", self._campo_atual(dados, "EstadoLeitura", 0))
|
|
||||||
|
estado = self._get(
|
||||||
|
dados,
|
||||||
|
"estado_leitura",
|
||||||
|
self._campo_atual(
|
||||||
|
dados,
|
||||||
|
"EstadoLeitura",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
estado_on = self._estado_ligado(estado)
|
estado_on = self._estado_ligado(estado)
|
||||||
|
|
||||||
usa_servo = dados.get("com_movimento", False)
|
# ============================================================
|
||||||
|
# Controle angular, quando o bico possui movimento
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
usa_servo = self._bool(
|
||||||
|
dados.get("com_movimento", False)
|
||||||
|
)
|
||||||
|
|
||||||
angulo = None
|
angulo = None
|
||||||
angulo_desejado = None
|
angulo_desejado = None
|
||||||
angulo_neutro = None
|
angulo_neutro = None
|
||||||
angulo_controle = None
|
angulo_controle = None
|
||||||
|
erro_angulo = 0.0
|
||||||
|
|
||||||
if usa_servo:
|
if usa_servo:
|
||||||
angulo = self._float(
|
angulo = self._float(
|
||||||
self._get(dados, "angulo_leitura", self._campo_atual(dados, "ServoAnguloLeitura", 0)),
|
self._get(
|
||||||
|
dados,
|
||||||
|
"angulo_leitura",
|
||||||
|
self._campo_atual(
|
||||||
|
dados,
|
||||||
|
"ServoAnguloLeitura",
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
),
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
|
|
||||||
angulo_neutro = self._float(dados.get("angulo_neutro", 90), 90)
|
angulo_neutro = self._float(
|
||||||
angulo_controle = self._float(dados.get("angulo_controle", dados.get("angulo_sp", 90)), 90)
|
dados.get("angulo_neutro", 90),
|
||||||
|
90,
|
||||||
|
)
|
||||||
|
|
||||||
|
angulo_controle = self._float(
|
||||||
|
dados.get(
|
||||||
|
"angulo_controle",
|
||||||
|
dados.get("angulo_sp", 90),
|
||||||
|
),
|
||||||
|
90,
|
||||||
|
)
|
||||||
|
|
||||||
angulo_desejado = self._float(
|
angulo_desejado = self._float(
|
||||||
dados.get(
|
dados.get(
|
||||||
"angulo_desejado_fisico",
|
"angulo_desejado_fisico",
|
||||||
dados.get(
|
dados.get(
|
||||||
"angulo_desejado",
|
"angulo_desejado",
|
||||||
angulo_controle if comando else angulo_neutro
|
(
|
||||||
)
|
angulo_controle
|
||||||
|
if comando
|
||||||
|
else angulo_neutro
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
angulo_controle
|
||||||
|
if comando
|
||||||
|
else angulo_neutro
|
||||||
),
|
),
|
||||||
angulo_controle if comando else angulo_neutro,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
ultimo_comando_ms = self._float(dados.get("ultimo_comando_ms", 999999), 999999)
|
erro_angulo = abs(
|
||||||
tem_idade_comando = ultimo_comando_ms < 900000
|
angulo - angulo_desejado
|
||||||
|
)
|
||||||
|
|
||||||
score = 100.0
|
# ============================================================
|
||||||
|
# Informações temporais
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
ultimo_comando_ms = self._float(
|
||||||
|
dados.get("ultimo_comando_ms", 999999),
|
||||||
|
999999,
|
||||||
|
)
|
||||||
|
|
||||||
|
ultima_resposta_ms = self._obter_ultima_resposta_ms(
|
||||||
|
dados
|
||||||
|
)
|
||||||
|
|
||||||
|
tem_comando_fisico = (
|
||||||
|
self._tem_comando_fisico_registrado(dados)
|
||||||
|
)
|
||||||
|
|
||||||
|
telemetria_fresca = (
|
||||||
|
ultima_resposta_ms <= self.RESPOSTA_RECENTE_MS
|
||||||
|
)
|
||||||
|
|
||||||
|
telemetria_pos_comando = (
|
||||||
|
self._telemetria_depois_do_comando(dados)
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Sem informações suficientes sobre o comando
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
if not comando_presente:
|
if not comando_presente:
|
||||||
|
self._atualizar_persistencia(
|
||||||
|
runtime,
|
||||||
|
"comandado_on_leitura_off_desde",
|
||||||
|
False,
|
||||||
|
agora,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._atualizar_persistencia(
|
||||||
|
runtime,
|
||||||
|
"comandado_off_leitura_on_desde",
|
||||||
|
False,
|
||||||
|
agora,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._atualizar_persistencia(
|
||||||
|
runtime,
|
||||||
|
"angulo_divergente_desde",
|
||||||
|
False,
|
||||||
|
agora,
|
||||||
|
)
|
||||||
|
|
||||||
return 92, {
|
return 92, {
|
||||||
"comando_disponivel": False,
|
"comando_disponivel": False,
|
||||||
"estado_on": estado_on,
|
"estado_on": estado_on,
|
||||||
|
|
@ -664,103 +764,367 @@ class ModuloAtuador(ModuloDiagnosticoBase):
|
||||||
"angulo_desejado": angulo_desejado,
|
"angulo_desejado": angulo_desejado,
|
||||||
}
|
}
|
||||||
|
|
||||||
if tem_idade_comando and ultimo_comando_ms < self.COMANDO_GRACE_MS:
|
# ============================================================
|
||||||
return 96, {
|
# Telemetria velha
|
||||||
"comando_atuar": comando,
|
#
|
||||||
"estado_on": estado_on,
|
# A avaliação de resposta do componente já diagnosticará isso.
|
||||||
"usa_servo": usa_servo,
|
# Não devemos transformar uma leitura antiga em falha mecânica.
|
||||||
"angulo": angulo,
|
# ============================================================
|
||||||
"angulo_desejado": angulo_desejado,
|
|
||||||
"ultimo_comando_ms": ultimo_comando_ms,
|
|
||||||
}
|
|
||||||
|
|
||||||
if self._aguardando_telemetria_pos_comando(dados):
|
|
||||||
return 94, {
|
|
||||||
"comando_disponivel": True,
|
|
||||||
"aguardando_telemetria_pos_comando": True,
|
|
||||||
"comando_fisico": comando,
|
|
||||||
"comando_logico": self._comando_logico_ligado(dados),
|
|
||||||
"estado_on": estado_on,
|
|
||||||
"usa_servo": usa_servo,
|
|
||||||
"angulo": angulo,
|
|
||||||
"angulo_desejado": angulo_desejado,
|
|
||||||
"ultimo_comando_ms": ultimo_comando_ms,
|
|
||||||
"ultima_resposta_ms": self._float(dados.get("ultima_resposta_ms", 999999), 999999),
|
|
||||||
"telemetria_pos_comando": self._telemetria_depois_do_comando(dados),
|
|
||||||
}
|
|
||||||
|
|
||||||
if comando and not estado_on:
|
if not telemetria_fresca:
|
||||||
severidade = 85 if tem_idade_comando and ultimo_comando_ms >= self.COMANDO_FALHA_MS else 70
|
self._atualizar_persistencia(
|
||||||
|
runtime,
|
||||||
motivos.append(f"Bico comandado ligado, mas leitura ainda indica desligado ({label})")
|
"comandado_on_leitura_off_desde",
|
||||||
condicoes.append({
|
False,
|
||||||
"label": "Bico",
|
agora,
|
||||||
"valor": 0,
|
|
||||||
"severidade": severidade,
|
|
||||||
"classe": "processo",
|
|
||||||
"descricao": f"Bico {label} comandado ligado, mas estado lido ainda está desligado",
|
|
||||||
"acoes": [
|
|
||||||
"Aguardar próxima atualização CAN.",
|
|
||||||
"Verificar solenoide do bico se a divergência persistir.",
|
|
||||||
"Conferir alimentação e retorno do bico.",
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
score = min(score, 55 if severidade >= 85 else 75)
|
|
||||||
|
|
||||||
if not comando and estado_on:
|
|
||||||
falha_confirmada = (
|
|
||||||
tem_idade_comando and
|
|
||||||
ultimo_comando_ms >= self.COMANDO_FALHA_MS and
|
|
||||||
self._telemetria_depois_do_comando(dados)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
severidade = 95 if falha_confirmada else 82
|
self._atualizar_persistencia(
|
||||||
|
runtime,
|
||||||
|
"comandado_off_leitura_on_desde",
|
||||||
|
False,
|
||||||
|
agora,
|
||||||
|
)
|
||||||
|
|
||||||
motivos.append(f"Bico ligado sem comando ({label})")
|
self._atualizar_persistencia(
|
||||||
condicoes.append({
|
runtime,
|
||||||
"label": "Bico",
|
"angulo_divergente_desde",
|
||||||
"valor": 1,
|
False,
|
||||||
"severidade": severidade,
|
agora,
|
||||||
"classe": "falha_hardware" if falha_confirmada else "processo",
|
)
|
||||||
"descricao": f"Bico {label} aparenta estar ligado sem comando",
|
|
||||||
"acoes": [
|
|
||||||
"Verificar travamento da solenoide.",
|
|
||||||
"Verificar se o comando de desligamento chegou ao ATU.",
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
score = min(score, 25 if falha_confirmada else 65)
|
return 100, {
|
||||||
|
"comando_disponivel": True,
|
||||||
|
"avaliacao_suspensa": (
|
||||||
|
"telemetria do bico desatualizada"
|
||||||
|
),
|
||||||
|
"comando_fisico": comando,
|
||||||
|
"estado_on": estado_on,
|
||||||
|
"ultima_resposta_ms": ultima_resposta_ms,
|
||||||
|
}
|
||||||
|
|
||||||
if usa_servo and angulo is not None and angulo_desejado is not None:
|
# ============================================================
|
||||||
erro_ang = abs(angulo - angulo_desejado)
|
# Divergências brutas
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
if erro_ang > self.ANGULO_TOL_BICO_GRAUS and (comando or estado_on):
|
divergencia_ligar = (
|
||||||
motivos.append(f"Ângulo do bico distante do desejado ({angulo:.1f}°/{angulo_desejado:.1f}°)")
|
comando and not estado_on
|
||||||
condicoes.append({
|
)
|
||||||
"label": "Ângulo bico",
|
|
||||||
"valor": angulo,
|
|
||||||
"severidade": min(75, max(30, int(erro_ang * 3))),
|
|
||||||
"classe": "processo",
|
|
||||||
"descricao": f"Ângulo do bico {label} distante do desejado",
|
|
||||||
"acoes": [
|
|
||||||
"Verificar servo/posição mecânica do bico.",
|
|
||||||
"Conferir neutro e ângulo de abertura configurados.",
|
|
||||||
],
|
|
||||||
})
|
|
||||||
score = min(score, 78)
|
|
||||||
|
|
||||||
return int(max(0, min(100, round(score)))), {
|
divergencia_desligar = (
|
||||||
|
not comando and estado_on
|
||||||
|
)
|
||||||
|
|
||||||
|
divergencia_angulo = (
|
||||||
|
usa_servo
|
||||||
|
and angulo is not None
|
||||||
|
and angulo_desejado is not None
|
||||||
|
and erro_angulo > self.ANGULO_TOL_BICO_GRAUS
|
||||||
|
)
|
||||||
|
|
||||||
|
# Durante estes contextos, o último comando ON pode estar
|
||||||
|
# temporariamente antigo em relação ao bloqueio operacional.
|
||||||
|
ignorar_divergencia_ligar = (
|
||||||
|
ctx.get("calibrando", False)
|
||||||
|
or ctx.get("finalizando", False)
|
||||||
|
or ctx.get("pausa", False)
|
||||||
|
or ctx.get("emergencia", False)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Durante calibragem, podem existir pulsos físicos controlados
|
||||||
|
# por uma sequência assíncrona própria.
|
||||||
|
ignorar_divergencia_desligar = (
|
||||||
|
ctx.get("calibrando", False)
|
||||||
|
)
|
||||||
|
|
||||||
|
ignorar_divergencia_angulo = (
|
||||||
|
ctx.get("calibrando", False)
|
||||||
|
or ctx.get("finalizando", False)
|
||||||
|
or ctx.get("emergencia", False)
|
||||||
|
)
|
||||||
|
|
||||||
|
# O ângulo só é avaliado quando o estado ON/OFF já está coerente.
|
||||||
|
avaliar_angulo = (
|
||||||
|
divergencia_angulo
|
||||||
|
and not divergencia_ligar
|
||||||
|
and not divergencia_desligar
|
||||||
|
and not ignorar_divergencia_angulo
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Persistência das divergências
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
tempo_divergencia_ligar = self._atualizar_persistencia(
|
||||||
|
runtime,
|
||||||
|
"comandado_on_leitura_off_desde",
|
||||||
|
(
|
||||||
|
divergencia_ligar
|
||||||
|
and not ignorar_divergencia_ligar
|
||||||
|
),
|
||||||
|
agora,
|
||||||
|
)
|
||||||
|
|
||||||
|
tempo_divergencia_desligar = self._atualizar_persistencia(
|
||||||
|
runtime,
|
||||||
|
"comandado_off_leitura_on_desde",
|
||||||
|
(
|
||||||
|
divergencia_desligar
|
||||||
|
and not ignorar_divergencia_desligar
|
||||||
|
),
|
||||||
|
agora,
|
||||||
|
)
|
||||||
|
|
||||||
|
tempo_divergencia_angulo = self._atualizar_persistencia(
|
||||||
|
runtime,
|
||||||
|
"angulo_divergente_desde",
|
||||||
|
avaliar_angulo,
|
||||||
|
agora,
|
||||||
|
)
|
||||||
|
|
||||||
|
debug = {
|
||||||
"comando_disponivel": True,
|
"comando_disponivel": True,
|
||||||
"comando_atuar": comando,
|
"comando_fisico": comando,
|
||||||
|
"comando_logico": self._comando_logico_ligado(dados),
|
||||||
"estado_on": estado_on,
|
"estado_on": estado_on,
|
||||||
"usa_servo": usa_servo,
|
"usa_servo": usa_servo,
|
||||||
"angulo": angulo,
|
"angulo": angulo,
|
||||||
"angulo_desejado": angulo_desejado,
|
"angulo_desejado": angulo_desejado,
|
||||||
"angulo_neutro": angulo_neutro,
|
"angulo_neutro": angulo_neutro,
|
||||||
"angulo_controle": angulo_controle,
|
"angulo_controle": angulo_controle,
|
||||||
|
"erro_angulo": erro_angulo,
|
||||||
"ultimo_comando_ms": ultimo_comando_ms,
|
"ultimo_comando_ms": ultimo_comando_ms,
|
||||||
|
"ultima_resposta_ms": ultima_resposta_ms,
|
||||||
|
"tem_comando_fisico": tem_comando_fisico,
|
||||||
|
"telemetria_fresca": telemetria_fresca,
|
||||||
|
"telemetria_pos_comando": telemetria_pos_comando,
|
||||||
|
"divergencia_ligar": divergencia_ligar,
|
||||||
|
"divergencia_desligar": divergencia_desligar,
|
||||||
|
"divergencia_angulo": divergencia_angulo,
|
||||||
|
"tempo_divergencia_ligar_s": tempo_divergencia_ligar,
|
||||||
|
"tempo_divergencia_desligar_s": tempo_divergencia_desligar,
|
||||||
|
"tempo_divergencia_angulo_s": tempo_divergencia_angulo,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Contextos nos quais o comando ON não deve ser avaliado
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
if divergencia_ligar and ignorar_divergencia_ligar:
|
||||||
|
debug["avaliacao_suspensa"] = (
|
||||||
|
"contexto operacional bloqueia comando ON"
|
||||||
|
)
|
||||||
|
|
||||||
|
return 96, debug
|
||||||
|
|
||||||
|
if (
|
||||||
|
divergencia_desligar
|
||||||
|
and ignorar_divergencia_desligar
|
||||||
|
):
|
||||||
|
debug["avaliacao_suspensa"] = (
|
||||||
|
"bico em calibragem"
|
||||||
|
)
|
||||||
|
|
||||||
|
return 96, debug
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Aguarda telemetria posterior ao comando
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
possui_divergencia = (
|
||||||
|
divergencia_ligar
|
||||||
|
or divergencia_desligar
|
||||||
|
or avaliar_angulo
|
||||||
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
possui_divergencia
|
||||||
|
and tem_comando_fisico
|
||||||
|
and not telemetria_pos_comando
|
||||||
|
):
|
||||||
|
debug["aguardando_telemetria_pos_comando"] = True
|
||||||
|
|
||||||
|
return 94, debug
|
||||||
|
|
||||||
|
score = 100.0
|
||||||
|
|
||||||
|
alerta_comando_s = (
|
||||||
|
self.COMANDO_ALERTA_MS / 1000.0
|
||||||
|
)
|
||||||
|
|
||||||
|
falha_comando_s = (
|
||||||
|
self.COMANDO_FALHA_MS / 1000.0
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Comando ON, mas leitura permanece OFF
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
if divergencia_ligar:
|
||||||
|
if tempo_divergencia_ligar < alerta_comando_s:
|
||||||
|
debug["divergencia_em_acomodacao"] = True
|
||||||
|
debug["tipo_divergencia"] = (
|
||||||
|
"comandado_on_leitura_off"
|
||||||
|
)
|
||||||
|
|
||||||
|
return 96, debug
|
||||||
|
|
||||||
|
falha_confirmada = (
|
||||||
|
tempo_divergencia_ligar >= falha_comando_s
|
||||||
|
and telemetria_pos_comando
|
||||||
|
)
|
||||||
|
|
||||||
|
severidade = (
|
||||||
|
92 if falha_confirmada else 70
|
||||||
|
)
|
||||||
|
|
||||||
|
motivos.append(
|
||||||
|
f"Bico comandado ligado, mas leitura "
|
||||||
|
f"ainda indica desligado ({label})"
|
||||||
|
)
|
||||||
|
|
||||||
|
condicoes.append({
|
||||||
|
"label": "Bico",
|
||||||
|
"valor": 0,
|
||||||
|
"severidade": severidade,
|
||||||
|
"classe": (
|
||||||
|
"falha_hardware"
|
||||||
|
if falha_confirmada
|
||||||
|
else "processo"
|
||||||
|
),
|
||||||
|
"descricao": (
|
||||||
|
f"Bico {label} permaneceu desligado "
|
||||||
|
"apesar do comando físico ON"
|
||||||
|
),
|
||||||
|
"acoes": [
|
||||||
|
"Verificar solenoide do bico.",
|
||||||
|
"Verificar alimentação e retorno do bico.",
|
||||||
|
"Conferir se o comando chegou ao ATU.",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
score = min(
|
||||||
|
score,
|
||||||
|
45 if falha_confirmada else 75,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Comando OFF, mas leitura permanece ON
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
if divergencia_desligar:
|
||||||
|
if tempo_divergencia_desligar < 2.5:
|
||||||
|
debug["divergencia_em_acomodacao"] = True
|
||||||
|
debug["tipo_divergencia"] = (
|
||||||
|
"comandado_off_leitura_on"
|
||||||
|
)
|
||||||
|
|
||||||
|
return 96, debug
|
||||||
|
|
||||||
|
falha_confirmada = (
|
||||||
|
tempo_divergencia_desligar >= 5.0
|
||||||
|
and telemetria_pos_comando
|
||||||
|
)
|
||||||
|
|
||||||
|
severidade = (
|
||||||
|
95 if falha_confirmada else 82
|
||||||
|
)
|
||||||
|
|
||||||
|
motivos.append(
|
||||||
|
f"Bico ligado sem comando ({label})"
|
||||||
|
)
|
||||||
|
|
||||||
|
condicoes.append({
|
||||||
|
"label": "Bico",
|
||||||
|
"valor": 1,
|
||||||
|
"severidade": severidade,
|
||||||
|
"classe": (
|
||||||
|
"falha_hardware"
|
||||||
|
if falha_confirmada
|
||||||
|
else "processo"
|
||||||
|
),
|
||||||
|
"descricao": (
|
||||||
|
f"Bico {label} permaneceu fisicamente ligado "
|
||||||
|
"apesar do comando OFF"
|
||||||
|
),
|
||||||
|
"acoes": [
|
||||||
|
"Verificar travamento da solenoide.",
|
||||||
|
"Verificar se o comando de desligamento chegou ao ATU.",
|
||||||
|
"Conferir a saída física da firmware.",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
score = min(
|
||||||
|
score,
|
||||||
|
25 if falha_confirmada else 65,
|
||||||
|
)
|
||||||
|
|
||||||
|
# ============================================================
|
||||||
|
# Estado correto, mas ângulo permanece divergente
|
||||||
|
# ============================================================
|
||||||
|
|
||||||
|
if avaliar_angulo:
|
||||||
|
if tempo_divergencia_angulo < alerta_comando_s:
|
||||||
|
debug["angulo_em_acomodacao"] = True
|
||||||
|
|
||||||
|
return 96, debug
|
||||||
|
|
||||||
|
falha_confirmada = (
|
||||||
|
tempo_divergencia_angulo >= falha_comando_s
|
||||||
|
and telemetria_pos_comando
|
||||||
|
)
|
||||||
|
|
||||||
|
severidade = (
|
||||||
|
88
|
||||||
|
if falha_confirmada
|
||||||
|
else min(
|
||||||
|
75,
|
||||||
|
max(
|
||||||
|
30,
|
||||||
|
int(erro_angulo * 3),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
motivos.append(
|
||||||
|
f"Ângulo do bico distante do desejado "
|
||||||
|
f"({angulo:.1f}°/{angulo_desejado:.1f}°)"
|
||||||
|
)
|
||||||
|
|
||||||
|
condicoes.append({
|
||||||
|
"label": "Ângulo bico",
|
||||||
|
"valor": angulo,
|
||||||
|
"severidade": severidade,
|
||||||
|
"classe": (
|
||||||
|
"falha_hardware"
|
||||||
|
if falha_confirmada
|
||||||
|
else "processo"
|
||||||
|
),
|
||||||
|
"descricao": (
|
||||||
|
f"Ângulo do bico {label} permaneceu "
|
||||||
|
"distante do desejado"
|
||||||
|
),
|
||||||
|
"acoes": [
|
||||||
|
"Verificar servo e posição mecânica do bico.",
|
||||||
|
"Conferir neutro e ângulo de abertura.",
|
||||||
|
"Verificar telemetria angular do bico.",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
|
||||||
|
score = min(
|
||||||
|
score,
|
||||||
|
45 if falha_confirmada else 78,
|
||||||
|
)
|
||||||
|
|
||||||
|
return int(
|
||||||
|
max(
|
||||||
|
0,
|
||||||
|
min(
|
||||||
|
100,
|
||||||
|
round(score),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
), debug
|
||||||
|
|
||||||
# ============================================================
|
# ============================================================
|
||||||
# Bombas
|
# Bombas
|
||||||
|
|
@ -1369,6 +1733,22 @@ class ModuloAtuador(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
return self.runtime_bombas[key]
|
return self.runtime_bombas[key]
|
||||||
|
|
||||||
|
def _runtime_bico(self, item):
|
||||||
|
key = str(
|
||||||
|
item.get("key")
|
||||||
|
or item.get("id")
|
||||||
|
or item.get("dados", {}).get("label")
|
||||||
|
or "bico"
|
||||||
|
)
|
||||||
|
|
||||||
|
if key not in self.runtime_bicos:
|
||||||
|
self.runtime_bicos[key] = {
|
||||||
|
"comandado_on_leitura_off_desde": None,
|
||||||
|
"comandado_off_leitura_on_desde": None,
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.runtime_bicos[key]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _atualizar_persistencia(runtime, campo, condicao, agora):
|
def _atualizar_persistencia(runtime, campo, condicao, agora):
|
||||||
"""
|
"""
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import time
|
import time
|
||||||
|
import math
|
||||||
from shared.enums import T_Code, StatusModulo
|
from shared.enums import T_Code, StatusModulo
|
||||||
from shared.utils import penalizar_frequencia
|
from shared.utils import penalizar_frequencia
|
||||||
from health_worker.modulos.base import ModuloDiagnosticoBase
|
from health_worker.modulos.base import ModuloDiagnosticoBase
|
||||||
|
|
@ -56,10 +57,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
operacao = ContextoGlobalRedis.get_operacao() or {}
|
operacao = ContextoGlobalRedis.get_operacao() or {}
|
||||||
equipamento = ContextoGlobalRedis.get_equipamento() or {}
|
equipamento = ContextoGlobalRedis.get_equipamento() or {}
|
||||||
|
|
||||||
conectado = self._bool(dados_sen.get("conectado", False))
|
conectado = self._bool(dados_sen.get("telemetria_recente", dados_sen.get("conectado", False)))
|
||||||
|
firmware_conectado = self._bool(dados_sen.get("firmware_conectado", conectado))
|
||||||
|
|
||||||
root_score, root_motivos, root_cond = self._avaliar_root(dados_sen)
|
root_score, root_motivos, root_cond = self._avaliar_root(dados_sen)
|
||||||
|
|
||||||
sen_disponivel = (conectado and self._ultima_resposta_ms(dados_sen) <= self.RESPOSTA_RECENTE_MS)
|
sen_disponivel = (conectado and firmware_conectado and self._ultima_resposta_ms(dados_sen) <= self.RESPOSTA_RECENTE_MS)
|
||||||
|
|
||||||
sensores = self._coletar(dados_sen, "sensores")
|
sensores = self._coletar(dados_sen, "sensores")
|
||||||
servos = self._coletar(dados_sen, "servos")
|
servos = self._coletar(dados_sen, "servos")
|
||||||
|
|
@ -78,7 +81,7 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
updates = {}
|
updates = {}
|
||||||
|
|
||||||
for item in sensores:
|
for item in sensores:
|
||||||
s = self._avaliar_sensor(item, ctx)
|
s = self._avaliar_sensor(item, ctx, modulo_disponivel=sen_disponivel)
|
||||||
saude_individual.append(s)
|
saude_individual.append(s)
|
||||||
updates[f"sensores.{item['key']}.saude"] = s
|
updates[f"sensores.{item['key']}.saude"] = s
|
||||||
|
|
||||||
|
|
@ -126,6 +129,8 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
if not conectado:
|
if not conectado:
|
||||||
saude_final = 0
|
saude_final = 0
|
||||||
|
elif not firmware_conectado:
|
||||||
|
saude_final = min(saude_final, 75)
|
||||||
if any(self._is_falha_hardware(c) for c in root_cond + processo["condicoes_operacionais"]):
|
if any(self._is_falha_hardware(c) for c in root_cond + processo["condicoes_operacionais"]):
|
||||||
saude_final = min(saude_final, 40)
|
saude_final = min(saude_final, 40)
|
||||||
if processo.get("energia_critica", False):
|
if processo.get("energia_critica", False):
|
||||||
|
|
@ -156,6 +161,8 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
saude_geral = {
|
saude_geral = {
|
||||||
"conectado": conectado,
|
"conectado": conectado,
|
||||||
|
"telemetria_recente": conectado,
|
||||||
|
"firmware_conectado": firmware_conectado,
|
||||||
"status": status.value,
|
"status": status.value,
|
||||||
"saude": saude_final,
|
"saude": saude_final,
|
||||||
"motivos": self._dedupe(motivos),
|
"motivos": self._dedupe(motivos),
|
||||||
|
|
@ -196,7 +203,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
def _avaliar_root(self, dados_sen):
|
def _avaliar_root(self, dados_sen):
|
||||||
conectado = self._bool(dados_sen.get("conectado", False))
|
conectado = self._bool(
|
||||||
|
dados_sen.get(
|
||||||
|
"telemetria_recente",
|
||||||
|
dados_sen.get("conectado", False),
|
||||||
|
)
|
||||||
|
)
|
||||||
idade_ms = self._ultima_resposta_ms(dados_sen)
|
idade_ms = self._ultima_resposta_ms(dados_sen)
|
||||||
motivos = []
|
motivos = []
|
||||||
condicoes = []
|
condicoes = []
|
||||||
|
|
@ -275,22 +287,97 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
# Sensores passivos
|
# Sensores passivos
|
||||||
# ============================================================
|
# ============================================================
|
||||||
|
|
||||||
def _avaliar_sensor(self, item, ctx):
|
def _avaliar_sensor(self, item, ctx, modulo_disponivel=True):
|
||||||
dados = item["dados"]
|
dados = item["dados"]
|
||||||
conectado = self._bool(dados.get("conectado", False))
|
conectado = self._bool(dados.get("conectado", False))
|
||||||
aferir = self._bool(dados.get("aferir", False))
|
aferir = self._bool(dados.get("aferir", False))
|
||||||
mandatorio = self._bool(dados.get("mandatorio", False))
|
mandatorio = self._bool(dados.get("mandatorio", False))
|
||||||
delegado_ao_mov = str(dados.get("label", "")).upper().startswith("TMV")
|
delegado_ao_mov = str(dados.get("label", "")).upper().startswith("TMV")
|
||||||
em_uso = aferir and not delegado_ao_mov
|
em_uso = aferir and not delegado_ao_mov
|
||||||
|
|
||||||
|
agora = time.monotonic()
|
||||||
|
runtime = self._runtime_saida_binaria(item)
|
||||||
|
|
||||||
|
if not modulo_disponivel:
|
||||||
|
runtime["modulo_disponivel_anterior"] = False
|
||||||
|
runtime["componente_disponivel_anterior"] = False
|
||||||
|
return self._item(
|
||||||
|
item, StatusModulo.DESCONECTADO, 0, [], [], False,
|
||||||
|
{
|
||||||
|
"avaliacao_suspensa": True,
|
||||||
|
"motivo_suspensao": "Módulo SEN indisponível",
|
||||||
|
},
|
||||||
|
"sensor",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not runtime["modulo_disponivel_anterior"]:
|
||||||
|
runtime["modulo_recuperado_em"] = agora
|
||||||
|
runtime["componente_disponivel_anterior"] = False
|
||||||
|
runtime["modulo_disponivel_anterior"] = True
|
||||||
|
|
||||||
|
tempo_retorno_modulo_ms = (
|
||||||
|
agora - runtime["modulo_recuperado_em"]
|
||||||
|
) * 1000.0
|
||||||
|
|
||||||
|
if not conectado:
|
||||||
|
runtime["componente_disponivel_anterior"] = False
|
||||||
|
|
||||||
|
if tempo_retorno_modulo_ms < self.MODULO_RECUPERACAO_GRACE_MS:
|
||||||
|
return self._item(
|
||||||
|
item, StatusModulo.DESCONECTADO, 0, [], [], False,
|
||||||
|
{
|
||||||
|
"avaliacao_suspensa": True,
|
||||||
|
"motivo_suspensao": "Aguardando sensor após retorno do SEN",
|
||||||
|
"tempo_desde_retorno_modulo_ms": tempo_retorno_modulo_ms,
|
||||||
|
},
|
||||||
|
"sensor",
|
||||||
|
)
|
||||||
|
|
||||||
|
return self._item(
|
||||||
|
item,
|
||||||
|
StatusModulo.DESCONECTADO,
|
||||||
|
0,
|
||||||
|
["desconectado após período de recuperação"],
|
||||||
|
[],
|
||||||
|
em_uso,
|
||||||
|
{"tempo_desde_retorno_modulo_ms": tempo_retorno_modulo_ms},
|
||||||
|
"sensor",
|
||||||
|
)
|
||||||
|
|
||||||
|
if not runtime["componente_disponivel_anterior"]:
|
||||||
|
runtime["componente_recuperado_em"] = agora
|
||||||
|
runtime["componente_disponivel_anterior"] = True
|
||||||
|
|
||||||
|
tempo_retorno_componente_ms = (
|
||||||
|
agora - runtime["componente_recuperado_em"]
|
||||||
|
) * 1000.0
|
||||||
|
|
||||||
|
if tempo_retorno_componente_ms < self.COMPONENTE_RECUPERACAO_GRACE_MS:
|
||||||
|
return self._item(
|
||||||
|
item,
|
||||||
|
StatusModulo.OPERANTE,
|
||||||
|
100,
|
||||||
|
[],
|
||||||
|
[],
|
||||||
|
em_uso,
|
||||||
|
{
|
||||||
|
"avaliacao_suspensa": True,
|
||||||
|
"motivo_suspensao": "Estabilização após reconexão do sensor",
|
||||||
|
"tempo_desde_retorno_componente_ms": tempo_retorno_componente_ms,
|
||||||
|
"avaliacao_delegada_ao_mov": delegado_ao_mov,
|
||||||
|
},
|
||||||
|
"sensor",
|
||||||
|
)
|
||||||
|
|
||||||
motivos = []
|
motivos = []
|
||||||
condicoes = []
|
condicoes = []
|
||||||
|
|
||||||
if not conectado:
|
|
||||||
return self._item(item, StatusModulo.DESCONECTADO, 0, ["desconectado"], [], em_uso, {}, "sensor")
|
|
||||||
|
|
||||||
resposta_score = self._score_resposta(dados, motivos, condicoes, "Sensor SEN")
|
resposta_score = self._score_resposta(dados, motivos, condicoes, "Sensor SEN")
|
||||||
valor_score, valor_debug = self._avaliar_valores_sensor(item, ctx, motivos, condicoes)
|
criticos = self._labels_criticos_sensor(dados, ctx, mandatorio)
|
||||||
telemetria = self._avaliar_telemetria(dados, motivos, self._labels_criticos_sensor(dados, ctx, mandatorio))
|
valor_score, valor_debug = self._avaliar_valores_sensor(
|
||||||
|
item, ctx, motivos, condicoes, criticos
|
||||||
|
)
|
||||||
|
telemetria = self._avaliar_telemetria(dados, motivos, criticos)
|
||||||
|
|
||||||
saude = resposta_score * 0.35 + valor_score * 0.45 + telemetria["score"] * 0.20
|
saude = resposta_score * 0.35 + valor_score * 0.45 + telemetria["score"] * 0.20
|
||||||
if any(self._is_falha_hardware(c) for c in condicoes):
|
if any(self._is_falha_hardware(c) for c in condicoes):
|
||||||
|
|
@ -298,29 +385,62 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
saude = int(max(0, min(100, round(saude))))
|
saude = int(max(0, min(100, round(saude))))
|
||||||
status = self._resolver_status_item(conectado, saude, condicoes)
|
status = self._resolver_status_item(conectado, saude, condicoes)
|
||||||
debug = {
|
|
||||||
"resposta": resposta_score,
|
|
||||||
"valores": valor_debug,
|
|
||||||
"telemetria": telemetria["parametros"],
|
|
||||||
"avaliacao_delegada_ao_mov": delegado_ao_mov,
|
|
||||||
}
|
|
||||||
return self._item(item, status, saude, motivos, condicoes, em_uso, debug, "sensor")
|
|
||||||
|
|
||||||
def _avaliar_valores_sensor(self, item, ctx, motivos, condicoes):
|
return self._item(
|
||||||
|
item,
|
||||||
|
status,
|
||||||
|
saude,
|
||||||
|
motivos,
|
||||||
|
condicoes,
|
||||||
|
em_uso,
|
||||||
|
{
|
||||||
|
"resposta": resposta_score,
|
||||||
|
"valores": valor_debug,
|
||||||
|
"telemetria": telemetria["parametros"],
|
||||||
|
"avaliacao_delegada_ao_mov": delegado_ao_mov,
|
||||||
|
},
|
||||||
|
"sensor",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _avaliar_valores_sensor(self, item, ctx, motivos, condicoes, criticos):
|
||||||
dados = item["dados"]
|
dados = item["dados"]
|
||||||
sensor_label = str(dados.get("label", ""))
|
sensor_label = str(dados.get("label", ""))
|
||||||
|
criticos = {self._canon(c) for c in (criticos or []) if c}
|
||||||
score = 100.0
|
score = 100.0
|
||||||
debug = {}
|
debug = {}
|
||||||
|
|
||||||
for key, campo in self._campos(dados).items():
|
for key, campo in self._campos(dados).items():
|
||||||
label = str(campo.get("label") or key)
|
label = str(campo.get("label") or key)
|
||||||
valor = self._float(campo.get("atual", 0), 0)
|
valor = self._float(campo.get("atual"), None)
|
||||||
minimo = campo.get("minimo", None)
|
minimo = campo.get("minimo", None)
|
||||||
maximo = campo.get("maximo", None)
|
maximo = campo.get("maximo", None)
|
||||||
nominal = campo.get("nominal", None)
|
nominal = campo.get("nominal", None)
|
||||||
unidade = campo.get("unidade", campo.get("unidade_medida", ""))
|
unidade = campo.get("unidade", campo.get("unidade_medida", ""))
|
||||||
|
|
||||||
debug[key] = {"label": label, "atual": valor, "minimo": minimo, "maximo": maximo, "nominal": nominal, "unidade": unidade}
|
debug[key] = {"label": label, "atual": valor, "minimo": minimo, "maximo": maximo, "nominal": nominal, "unidade": unidade}
|
||||||
|
|
||||||
|
if valor is None:
|
||||||
|
critico = self._canon(label) in criticos
|
||||||
|
motivos.append(f"{sensor_label}.{label} possui leitura inválida")
|
||||||
|
condicoes.append({
|
||||||
|
"label": "Leitura sensor SEN",
|
||||||
|
"valor": None,
|
||||||
|
"severidade": 92 if critico else 75,
|
||||||
|
"classe": "falha_hardware" if critico else "processo",
|
||||||
|
"descricao": f"{sensor_label}.{label} retornou valor inválido",
|
||||||
|
"acoes": [
|
||||||
|
"Verificar sensor, alimentação e cabeamento.",
|
||||||
|
"Verificar sentinela e conversão da firmware.",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
score = min(score, 0 if critico else 60)
|
||||||
|
debug[key] = {
|
||||||
|
"label": label,
|
||||||
|
"atual": None,
|
||||||
|
"valido": False,
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
|
||||||
sc, conds, mots = self._avaliar_campo(sensor_label, label, valor, minimo, maximo)
|
sc, conds, mots = self._avaliar_campo(sensor_label, label, valor, minimo, maximo)
|
||||||
score = min(score, sc)
|
score = min(score, sc)
|
||||||
motivos.extend(mots)
|
motivos.extend(mots)
|
||||||
|
|
@ -509,6 +629,11 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
token_desejado = round(angulo_sp, 3)
|
token_desejado = round(angulo_sp, 3)
|
||||||
self._registrar_desejado(runtime, token_desejado)
|
self._registrar_desejado(runtime, token_desejado)
|
||||||
|
self._atualizar_evidencia_comando(runtime, evidencia)
|
||||||
|
|
||||||
|
idade_desejo_ms = self._idade_desejo_ms(runtime)
|
||||||
|
debug["idade_desejo_ms"] = idade_desejo_ms
|
||||||
|
debug["telemetria_confirmada_desejo"] = runtime["telemetria_confirmada_desejo"]
|
||||||
|
|
||||||
erro = abs(angulo - angulo_sp)
|
erro = abs(angulo - angulo_sp)
|
||||||
debug["erro"] = erro
|
debug["erro"] = erro
|
||||||
|
|
@ -532,12 +657,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
||||||
if not evidencia["telemetria_pos_comando"]:
|
if not runtime["telemetria_confirmada_desejo"]:
|
||||||
runtime["divergencia_desde"] = None
|
runtime["divergencia_desde"] = None
|
||||||
debug["avaliacao_suspensa"] = "aguardando telemetria posterior ao comando"
|
debug["avaliacao_suspensa"] = "aguardando telemetria posterior ao comando"
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
||||||
if evidencia["ultimo_comando_ms"] < self.COMANDO_GRACE_MS:
|
if idade_desejo_ms < self.COMANDO_GRACE_MS:
|
||||||
runtime["divergencia_desde"] = None
|
runtime["divergencia_desde"] = None
|
||||||
debug["avaliacao_suspensa"] = "período de resposta ao comando"
|
debug["avaliacao_suspensa"] = "período de resposta ao comando"
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
@ -862,6 +987,9 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
"coerencia_desde": None,
|
"coerencia_desde": None,
|
||||||
"nivel_divergencia": None,
|
"nivel_divergencia": None,
|
||||||
"ultimo_desejado": None,
|
"ultimo_desejado": None,
|
||||||
|
"desejo_alterado_em": None,
|
||||||
|
"telemetria_confirmada_desejo": False,
|
||||||
|
"possui_comando_anterior": False,
|
||||||
})
|
})
|
||||||
|
|
||||||
def _preparar_atuador_auxiliar(
|
def _preparar_atuador_auxiliar(
|
||||||
|
|
@ -995,10 +1123,18 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
def _registrar_desejado(self, runtime, token_desejado):
|
def _registrar_desejado(self, runtime, token_desejado):
|
||||||
if runtime.get("ultimo_desejado") == token_desejado:
|
if runtime.get("ultimo_desejado") == token_desejado:
|
||||||
return
|
return False
|
||||||
|
|
||||||
|
self._resetar_estado_comando(
|
||||||
|
runtime,
|
||||||
|
manter_desejado=False,
|
||||||
|
)
|
||||||
|
|
||||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
|
||||||
runtime["ultimo_desejado"] = token_desejado
|
runtime["ultimo_desejado"] = token_desejado
|
||||||
|
runtime["desejo_alterado_em"] = time.monotonic()
|
||||||
|
runtime["telemetria_confirmada_desejo"] = False
|
||||||
|
runtime["possui_comando_anterior"] = False
|
||||||
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _resetar_estado_comando(runtime, manter_desejado=True):
|
def _resetar_estado_comando(runtime, manter_desejado=True):
|
||||||
|
|
@ -1007,6 +1143,43 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
runtime["nivel_divergencia"] = None
|
runtime["nivel_divergencia"] = None
|
||||||
if not manter_desejado:
|
if not manter_desejado:
|
||||||
runtime["ultimo_desejado"] = None
|
runtime["ultimo_desejado"] = None
|
||||||
|
runtime["desejo_alterado_em"] = None
|
||||||
|
runtime["telemetria_confirmada_desejo"] = False
|
||||||
|
runtime["possui_comando_anterior"] = False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _atualizar_evidencia_comando(runtime, evidencia):
|
||||||
|
possui_comando = bool(
|
||||||
|
evidencia.get("possui_comando", False)
|
||||||
|
)
|
||||||
|
|
||||||
|
if not possui_comando:
|
||||||
|
runtime["possui_comando_anterior"] = False
|
||||||
|
runtime["telemetria_confirmada_desejo"] = False
|
||||||
|
return
|
||||||
|
|
||||||
|
if not runtime.get("possui_comando_anterior", False):
|
||||||
|
runtime["possui_comando_anterior"] = True
|
||||||
|
runtime["desejo_alterado_em"] = time.monotonic()
|
||||||
|
runtime["telemetria_confirmada_desejo"] = False
|
||||||
|
runtime["divergencia_desde"] = None
|
||||||
|
runtime["coerencia_desde"] = None
|
||||||
|
runtime["nivel_divergencia"] = None
|
||||||
|
|
||||||
|
if evidencia.get("telemetria_pos_comando", False):
|
||||||
|
runtime["telemetria_confirmada_desejo"] = True
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _idade_desejo_ms(runtime):
|
||||||
|
alterado_em = runtime.get("desejo_alterado_em")
|
||||||
|
|
||||||
|
if alterado_em is None:
|
||||||
|
return 999999.0
|
||||||
|
|
||||||
|
return max(
|
||||||
|
0.0,
|
||||||
|
(time.monotonic() - alterado_em) * 1000.0,
|
||||||
|
)
|
||||||
|
|
||||||
def _avaliar_comando_binario(
|
def _avaliar_comando_binario(
|
||||||
self,
|
self,
|
||||||
|
|
@ -1019,10 +1192,7 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
):
|
):
|
||||||
dados = item["dados"]
|
dados = item["dados"]
|
||||||
label = dados.get("label")
|
label = dados.get("label")
|
||||||
|
controlar = self._bool(dados.get("controlar", False))
|
||||||
controlar = self._bool(
|
|
||||||
dados.get("controlar", False)
|
|
||||||
)
|
|
||||||
|
|
||||||
desejado_presente = any(
|
desejado_presente = any(
|
||||||
chave in dados
|
chave in dados
|
||||||
|
|
@ -1051,89 +1221,95 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
lido = self._estado_ligado(
|
lido = self._estado_ligado(
|
||||||
dados.get(
|
dados.get(
|
||||||
"estado_leitura",
|
"estado_leitura",
|
||||||
self._campo_atual(
|
self._campo_atual(dados, "EstadoLeitura", False),
|
||||||
dados,
|
|
||||||
"EstadoLeitura",
|
|
||||||
False,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
ultimo_comando_ms = self._float(
|
evidencia = self._evidencia_comando(dados)
|
||||||
dados.get("ultimo_comando_ms", 999999),
|
|
||||||
999999,
|
|
||||||
)
|
|
||||||
|
|
||||||
ultima_resposta_ms = self._float(
|
|
||||||
dados.get("ultima_resposta_ms", 999999),
|
|
||||||
999999,
|
|
||||||
)
|
|
||||||
|
|
||||||
possui_comando = self._bool(
|
|
||||||
dados.get(
|
|
||||||
"possui_comando",
|
|
||||||
ultimo_comando_ms < 900000,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
if "telemetria_pos_comando" in dados:
|
|
||||||
telemetria_pos_comando = self._bool(
|
|
||||||
dados.get("telemetria_pos_comando", False)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
# Compatibilidade com publicação antiga.
|
|
||||||
telemetria_pos_comando = (
|
|
||||||
possui_comando and
|
|
||||||
ultima_resposta_ms <= ultimo_comando_ms + 100.0
|
|
||||||
)
|
|
||||||
|
|
||||||
debug = {
|
debug = {
|
||||||
"controlar": controlar,
|
"controlar": controlar,
|
||||||
"estado_lido": lido,
|
"estado_lido": lido,
|
||||||
"estado_desejado": desejado,
|
"estado_desejado": desejado,
|
||||||
"possui_comando": possui_comando,
|
**evidencia,
|
||||||
"telemetria_pos_comando": telemetria_pos_comando,
|
|
||||||
"ultimo_comando_ms": ultimo_comando_ms,
|
|
||||||
"ultima_resposta_ms": ultima_resposta_ms,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if not controlar:
|
if not controlar:
|
||||||
runtime["divergencia_desde"] = None
|
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||||
return 100, debug
|
return 100, debug
|
||||||
|
|
||||||
if not desejado_presente or not leitura_presente:
|
if not desejado_presente or not leitura_presente:
|
||||||
runtime["divergencia_desde"] = None
|
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||||
debug["contrato_comando_completo"] = False
|
debug["contrato_comando_completo"] = False
|
||||||
return 92, debug
|
return 92, debug
|
||||||
|
|
||||||
# Estado coerente: limpa qualquer divergência anterior.
|
token_desejado = bool(desejado)
|
||||||
|
self._registrar_desejado(runtime, token_desejado)
|
||||||
|
self._atualizar_evidencia_comando(runtime, evidencia)
|
||||||
|
|
||||||
|
idade_desejo_ms = self._idade_desejo_ms(runtime)
|
||||||
|
debug["idade_desejo_ms"] = idade_desejo_ms
|
||||||
|
debug["telemetria_confirmada_desejo"] = runtime["telemetria_confirmada_desejo"]
|
||||||
|
|
||||||
if lido == desejado:
|
if lido == desejado:
|
||||||
runtime["divergencia_desde"] = None
|
runtime["divergencia_desde"] = None
|
||||||
debug["divergente"] = False
|
debug["divergente"] = False
|
||||||
return 100, debug
|
|
||||||
|
if runtime["nivel_divergencia"] is None:
|
||||||
|
runtime["coerencia_desde"] = None
|
||||||
|
return 100, debug
|
||||||
|
|
||||||
|
agora = time.monotonic()
|
||||||
|
if runtime["coerencia_desde"] is None:
|
||||||
|
runtime["coerencia_desde"] = agora
|
||||||
|
|
||||||
|
tempo_coerente_ms = (
|
||||||
|
agora - runtime["coerencia_desde"]
|
||||||
|
) * 1000.0
|
||||||
|
|
||||||
|
debug["recuperacao_em_confirmacao"] = True
|
||||||
|
debug["tempo_coerente_ms"] = tempo_coerente_ms
|
||||||
|
|
||||||
|
if tempo_coerente_ms >= self.COMANDO_RECUPERACAO_MS:
|
||||||
|
runtime["nivel_divergencia"] = None
|
||||||
|
runtime["coerencia_desde"] = None
|
||||||
|
return 100, debug
|
||||||
|
|
||||||
|
motivos.append(
|
||||||
|
f"{label} recuperou o estado desejado; confirmando estabilidade"
|
||||||
|
)
|
||||||
|
condicoes.append({
|
||||||
|
"label": nome,
|
||||||
|
"valor": int(lido),
|
||||||
|
"severidade": 55,
|
||||||
|
"classe": "processo",
|
||||||
|
"descricao": (
|
||||||
|
f"{label} voltou ao estado desejado e está em confirmação"
|
||||||
|
),
|
||||||
|
"acoes": [
|
||||||
|
"Aguardar confirmação da estabilidade do retorno.",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
return 78, debug
|
||||||
|
|
||||||
debug["divergente"] = True
|
debug["divergente"] = True
|
||||||
|
runtime["coerencia_desde"] = None
|
||||||
|
|
||||||
# Não existe evidência de que o estado desejado foi enviado.
|
if not evidencia["possui_comando"]:
|
||||||
if not possui_comando:
|
|
||||||
runtime["divergencia_desde"] = None
|
runtime["divergencia_desde"] = None
|
||||||
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
||||||
# A resposta ainda é antiga em relação ao comando.
|
if not runtime["telemetria_confirmada_desejo"]:
|
||||||
if not telemetria_pos_comando:
|
|
||||||
runtime["divergencia_desde"] = None
|
runtime["divergencia_desde"] = None
|
||||||
debug["avaliacao_suspensa"] = (
|
debug["avaliacao_suspensa"] = (
|
||||||
"aguardando telemetria posterior ao comando"
|
"aguardando telemetria posterior ao comando"
|
||||||
)
|
)
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
||||||
# Comando acabou de ser enviado.
|
if idade_desejo_ms < self.COMANDO_GRACE_MS:
|
||||||
if ultimo_comando_ms < self.COMANDO_GRACE_MS:
|
|
||||||
runtime["divergencia_desde"] = None
|
runtime["divergencia_desde"] = None
|
||||||
debug["avaliacao_suspensa"] = (
|
debug["avaliacao_suspensa"] = "período de resposta ao comando"
|
||||||
"período de resposta ao comando"
|
|
||||||
)
|
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
||||||
agora = time.monotonic()
|
agora = time.monotonic()
|
||||||
|
|
@ -1147,16 +1323,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
< self.COMPONENTE_RECUPERACAO_GRACE_MS
|
< self.COMPONENTE_RECUPERACAO_GRACE_MS
|
||||||
):
|
):
|
||||||
runtime["divergencia_desde"] = None
|
runtime["divergencia_desde"] = None
|
||||||
debug["avaliacao_suspensa"] = (
|
debug["avaliacao_suspensa"] = "estabilização após reconexão"
|
||||||
"estabilização após reconexão"
|
|
||||||
)
|
|
||||||
debug["tempo_desde_retorno_componente_ms"] = (
|
debug["tempo_desde_retorno_componente_ms"] = (
|
||||||
tempo_desde_retorno_componente_ms
|
tempo_desde_retorno_componente_ms
|
||||||
)
|
)
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
||||||
# A persistência começa quando a divergência é efetivamente
|
|
||||||
# observada em condições válidas.
|
|
||||||
if runtime["divergencia_desde"] is None:
|
if runtime["divergencia_desde"] is None:
|
||||||
runtime["divergencia_desde"] = agora
|
runtime["divergencia_desde"] = agora
|
||||||
|
|
||||||
|
|
@ -1166,39 +1338,54 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
debug["tempo_divergente_ms"] = tempo_divergente_ms
|
debug["tempo_divergente_ms"] = tempo_divergente_ms
|
||||||
|
|
||||||
|
if runtime["nivel_divergencia"] == "falha":
|
||||||
|
motivos.append(f"{label} não acompanhou o estado desejado")
|
||||||
|
condicoes.append({
|
||||||
|
"label": nome,
|
||||||
|
"valor": int(lido),
|
||||||
|
"severidade": severidade_falha,
|
||||||
|
"classe": "falha_hardware",
|
||||||
|
"descricao": f"{label} permaneceu diferente do estado desejado",
|
||||||
|
"acoes": [
|
||||||
|
"Verificar saída física do relé.",
|
||||||
|
"Verificar carga conectada.",
|
||||||
|
"Verificar comando e retorno do SEN.",
|
||||||
|
],
|
||||||
|
})
|
||||||
|
return 30, debug
|
||||||
|
|
||||||
if tempo_divergente_ms < self.COMANDO_ALERTA_MS:
|
if tempo_divergente_ms < self.COMANDO_ALERTA_MS:
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
||||||
if tempo_divergente_ms < self.COMANDO_FALHA_MS:
|
if tempo_divergente_ms < self.COMANDO_FALHA_MS:
|
||||||
|
runtime["nivel_divergencia"] = "alerta"
|
||||||
|
|
||||||
motivos.append(
|
motivos.append(
|
||||||
f"{label} ainda não acompanhou o estado desejado"
|
f"{label} ainda não acompanhou o estado desejado"
|
||||||
)
|
)
|
||||||
|
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": nome,
|
"label": nome,
|
||||||
"valor": int(lido),
|
"valor": int(lido),
|
||||||
"severidade": 78,
|
"severidade": 78,
|
||||||
"classe": "processo",
|
"classe": "processo",
|
||||||
"descricao":
|
"descricao": (
|
||||||
f"{label} apresenta divergência persistente de comando",
|
f"{label} apresenta divergência persistente de comando"
|
||||||
|
),
|
||||||
"acoes": [
|
"acoes": [
|
||||||
"Aguardar confirmação ou verificar saída física.",
|
"Aguardar confirmação ou verificar saída física.",
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
|
||||||
return 68, debug
|
return 68, debug
|
||||||
|
|
||||||
motivos.append(
|
runtime["nivel_divergencia"] = "falha"
|
||||||
f"{label} não acompanhou o estado desejado"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
motivos.append(f"{label} não acompanhou o estado desejado")
|
||||||
condicoes.append({
|
condicoes.append({
|
||||||
"label": nome,
|
"label": nome,
|
||||||
"valor": int(lido),
|
"valor": int(lido),
|
||||||
"severidade": severidade_falha,
|
"severidade": severidade_falha,
|
||||||
"classe": "falha_hardware",
|
"classe": "falha_hardware",
|
||||||
"descricao":
|
"descricao": f"{label} permaneceu diferente do estado desejado",
|
||||||
f"{label} permaneceu diferente do estado desejado",
|
|
||||||
"acoes": [
|
"acoes": [
|
||||||
"Verificar saída física do relé.",
|
"Verificar saída física do relé.",
|
||||||
"Verificar carga conectada.",
|
"Verificar carga conectada.",
|
||||||
|
|
@ -1319,6 +1506,11 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
st_sp if status_sp_presente else None,
|
st_sp if status_sp_presente else None,
|
||||||
)
|
)
|
||||||
self._registrar_desejado(runtime, token_desejado)
|
self._registrar_desejado(runtime, token_desejado)
|
||||||
|
self._atualizar_evidencia_comando(runtime, evidencia)
|
||||||
|
|
||||||
|
idade_desejo_ms = self._idade_desejo_ms(runtime)
|
||||||
|
debug["idade_desejo_ms"] = idade_desejo_ms
|
||||||
|
debug["telemetria_confirmada_desejo"] = runtime["telemetria_confirmada_desejo"]
|
||||||
|
|
||||||
# Se o SP existe, uma leitura ausente (-1) também é divergência.
|
# Se o SP existe, uma leitura ausente (-1) também é divergência.
|
||||||
coerente_id = (
|
coerente_id = (
|
||||||
|
|
@ -1366,12 +1558,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
||||||
if not evidencia["telemetria_pos_comando"]:
|
if not runtime["telemetria_confirmada_desejo"]:
|
||||||
runtime["divergencia_desde"] = None
|
runtime["divergencia_desde"] = None
|
||||||
debug["avaliacao_suspensa"] = "aguardando telemetria posterior ao comando"
|
debug["avaliacao_suspensa"] = "aguardando telemetria posterior ao comando"
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
||||||
if evidencia["ultimo_comando_ms"] < self.COMANDO_GRACE_MS:
|
if idade_desejo_ms < self.COMANDO_GRACE_MS:
|
||||||
runtime["divergencia_desde"] = None
|
runtime["divergencia_desde"] = None
|
||||||
debug["avaliacao_suspensa"] = "período de resposta ao comando"
|
debug["avaliacao_suspensa"] = "período de resposta ao comando"
|
||||||
return 96, debug
|
return 96, debug
|
||||||
|
|
@ -1653,6 +1845,8 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
if not conectado:
|
if not conectado:
|
||||||
return StatusModulo.DESCONECTADO
|
return StatusModulo.DESCONECTADO
|
||||||
em_uso = [x for x in saude_individual if self._bool(x.get("em_uso", False))]
|
em_uso = [x for x in saude_individual if self._bool(x.get("em_uso", False))]
|
||||||
|
if any(x.get("status") == StatusModulo.DESCONECTADO.value and self._bool(x.get("mandatorio", False)) for x in em_uso):
|
||||||
|
return StatusModulo.FALHA
|
||||||
if any(self._is_falha_hardware(c) for c in condicoes):
|
if any(self._is_falha_hardware(c) for c in condicoes):
|
||||||
return StatusModulo.FALHA
|
return StatusModulo.FALHA
|
||||||
if any(x.get("status") == StatusModulo.FALHA.value for x in em_uso):
|
if any(x.get("status") == StatusModulo.FALHA.value for x in em_uso):
|
||||||
|
|
@ -1665,6 +1859,8 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
return StatusModulo.ALERTA
|
return StatusModulo.ALERTA
|
||||||
if any(x.get("status") == StatusModulo.ALERTA.value for x in em_uso):
|
if any(x.get("status") == StatusModulo.ALERTA.value for x in em_uso):
|
||||||
return StatusModulo.ALERTA
|
return StatusModulo.ALERTA
|
||||||
|
if any(x.get("status") == StatusModulo.DESCONECTADO.value for x in em_uso):
|
||||||
|
return StatusModulo.ALERTA
|
||||||
return StatusModulo.OPERANTE
|
return StatusModulo.OPERANTE
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|
@ -1795,14 +1991,24 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _float(valor, padrao=0.0):
|
def _float(valor, padrao=0.0):
|
||||||
|
fallback = None if padrao is None else float(padrao)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if valor is None:
|
if valor is None:
|
||||||
return float(padrao)
|
return fallback
|
||||||
|
|
||||||
if hasattr(valor, "value"):
|
if hasattr(valor, "value"):
|
||||||
valor = valor.value
|
valor = valor.value
|
||||||
return float(valor)
|
|
||||||
|
numero = float(valor)
|
||||||
|
|
||||||
|
if not math.isfinite(numero):
|
||||||
|
return fallback
|
||||||
|
|
||||||
|
return numero
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return float(padrao)
|
return fallback
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _safe_int(valor, padrao=0):
|
def _safe_int(valor, padrao=0):
|
||||||
|
|
|
||||||
|
|
@ -78,6 +78,31 @@ public:
|
||||||
// Mantem o watchdog de tasks como ultimo cinto de seguranca contra firmware
|
// Mantem o watchdog de tasks como ultimo cinto de seguranca contra firmware
|
||||||
// realmente travado. Ele nao e usado para decidir se o barramento esta vivo.
|
// realmente travado. Ele nao e usado para decidir se o barramento esta vivo.
|
||||||
esp_task_wdt_init(TASK_WDT_SECONDS, true);
|
esp_task_wdt_init(TASK_WDT_SECONDS, true);
|
||||||
|
|
||||||
|
// PARA VERSAO 3.3.11 do CORE
|
||||||
|
/*esp_task_wdt_config_t wdtConfig = {};
|
||||||
|
|
||||||
|
wdtConfig.timeout_ms = static_cast<uint32_t>(TASK_WDT_SECONDS) * 1000UL;
|
||||||
|
|
||||||
|
// Nao inscrevemos automaticamente as idle tasks.
|
||||||
|
// Nosso CanService registra explicitamente apenas as tasks
|
||||||
|
// que deseja supervisionar.
|
||||||
|
wdtConfig.idle_core_mask = 0;
|
||||||
|
|
||||||
|
wdtConfig.trigger_panic = true;
|
||||||
|
|
||||||
|
esp_err_t wdtErr = esp_task_wdt_reconfigure(&wdtConfig);
|
||||||
|
|
||||||
|
// Se ainda nao tiver sido inicializado pelo core,
|
||||||
|
// inicializa agora.
|
||||||
|
if (wdtErr == ESP_ERR_INVALID_STATE) {
|
||||||
|
wdtErr = esp_task_wdt_init(&wdtConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (wdtErr != ESP_OK) {
|
||||||
|
PrintTela("[CAN] Falha ao configurar Task WDT: " + String((int)wdtErr));
|
||||||
|
}*/
|
||||||
|
|
||||||
esp_task_wdt_add(CanTaskRxHandle);
|
esp_task_wdt_add(CanTaskRxHandle);
|
||||||
esp_task_wdt_add(CanTaskTxHandle);
|
esp_task_wdt_add(CanTaskTxHandle);
|
||||||
esp_task_wdt_add(CallbackWorkerHandle);
|
esp_task_wdt_add(CallbackWorkerHandle);
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,11 @@ class I2CService {
|
||||||
uint32_t readyDropsAds = 0;
|
uint32_t readyDropsAds = 0;
|
||||||
uint32_t falhasIoAds = 0;
|
uint32_t falhasIoAds = 0;
|
||||||
uint32_t wireTimeoutMs = 0;
|
uint32_t wireTimeoutMs = 0;
|
||||||
|
|
||||||
|
// Recovery local: nao altera a geracao do Wire.
|
||||||
|
uint32_t recoveriesLocaisMux = 0;
|
||||||
|
uint32_t recoveriesLocaisAds = 0;
|
||||||
|
uint32_t falhasLocaisDispositivos = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
static DiagnosticoCAN ObterDiagnosticoCAN() {
|
static DiagnosticoCAN ObterDiagnosticoCAN() {
|
||||||
|
|
@ -121,6 +126,10 @@ class I2CService {
|
||||||
d.readyDropsAds = _readyDropsAds;
|
d.readyDropsAds = _readyDropsAds;
|
||||||
d.falhasIoAds = _falhasIoAds;
|
d.falhasIoAds = _falhasIoAds;
|
||||||
|
|
||||||
|
d.recoveriesLocaisMux = _recoveriesLocaisMux;
|
||||||
|
d.recoveriesLocaisAds = _recoveriesLocaisAds;
|
||||||
|
d.falhasLocaisDispositivos = _falhasLocaisDispositivos;
|
||||||
|
|
||||||
portEXIT_CRITICAL(&_estadoLock);
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
d.infraestruturaMuxOk = (d.muxEsperadoMask & d.muxValidadoMask) == d.muxEsperadoMask;
|
d.infraestruturaMuxOk = (d.muxEsperadoMask & d.muxValidadoMask) == d.muxEsperadoMask;
|
||||||
|
|
@ -390,8 +399,58 @@ class I2CService {
|
||||||
return RecuperarBarramento(motivo, ignorarCooldown);
|
return RecuperarBarramento(motivo, ignorarCooldown);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Falha de um dispositivo individual NAO e falha global do barramento.
|
||||||
|
*
|
||||||
|
* O proprio model do sensor continua responsavel por:
|
||||||
|
* - contar falhas consecutivas;
|
||||||
|
* - marcar Iniciado=false;
|
||||||
|
* - tentar Inicializar() novamente.
|
||||||
|
*
|
||||||
|
* O I2CService apenas registra a degradacao local. Isso evita que um
|
||||||
|
* unico sensor derrube a geracao de todos os outros.
|
||||||
|
*/
|
||||||
static void NotificarFalhaDispositivo(int idSensor, const String& motivo) {
|
static void NotificarFalhaDispositivo(int idSensor, const String& motivo) {
|
||||||
RegistrarFalhaTransacao("ID " + String(idSensor) + " | " + motivo);
|
RegistrarFalhaLocalDispositivo(idSensor, motivo);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void NotificarSucessoDispositivo(int idSensor) {
|
||||||
|
RegistrarSucessoLocalDispositivo(idSensor);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Use SOMENTE depois que o model do sensor esgotar suas tentativas locais
|
||||||
|
* de reinicializacao. Esta e a ponte explicita entre recovery local e uma
|
||||||
|
* nova geracao global.
|
||||||
|
*
|
||||||
|
* Nao chamar por amostra invalida isolada.
|
||||||
|
*/
|
||||||
|
static void NotificarFalhaPersistenteDispositivo(
|
||||||
|
int idSensor,
|
||||||
|
const String& motivo
|
||||||
|
) {
|
||||||
|
RegistrarFalhaLocalDispositivo(
|
||||||
|
idSensor,
|
||||||
|
"persistente | " + motivo
|
||||||
|
);
|
||||||
|
|
||||||
|
AgendarRecovery(
|
||||||
|
"Dispositivo nao reviveu apos retries locais | ID " +
|
||||||
|
String(idSensor) + " | " + motivo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Por padrao mantemos o ultimo canal do MUX selecionado entre acessos.
|
||||||
|
* No SEN isso e seguro porque os INAs downstream usam 0x40 e os
|
||||||
|
* dispositivos do barramento principal usam outros enderecos.
|
||||||
|
*
|
||||||
|
* Isso reduz praticamente pela metade as escritas no TCA9548A.
|
||||||
|
* Pode ser reativado para outras topologias que precisem isolar o ramal
|
||||||
|
* ao final de cada acesso.
|
||||||
|
*/
|
||||||
|
static void DefinirDesabilitarMuxAoLiberar(bool ativo) {
|
||||||
|
_desabilitarMuxAoLiberar = ativo;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool CanalMuxEmQuarentena(uint8_t canalGlobal) {
|
static bool CanalMuxEmQuarentena(uint8_t canalGlobal) {
|
||||||
|
|
@ -578,6 +637,9 @@ class I2CService {
|
||||||
s += " SCL=" + String((_pinoSCL >= 0) ? digitalRead(_pinoSCL) : -1);
|
s += " SCL=" + String((_pinoSCL >= 0) ? digitalRead(_pinoSCL) : -1);
|
||||||
s += " q_mux=" + String(QuantidadeCanaisMuxEmQuarentena());
|
s += " q_mux=" + String(QuantidadeCanaisMuxEmQuarentena());
|
||||||
s += " q_ads=" + String(QuantidadeADSEmQuarentena());
|
s += " q_ads=" + String(QuantidadeADSEmQuarentena());
|
||||||
|
s += " rec_local_mux=" + String(_recoveriesLocaisMux);
|
||||||
|
s += " rec_local_ads=" + String(_recoveriesLocaisAds);
|
||||||
|
s += " falhas_locais=" + String(_falhasLocaisDispositivos);
|
||||||
|
|
||||||
if (_pinoTcaRdy >= 0) {
|
if (_pinoTcaRdy >= 0) {
|
||||||
s += " tca_rdy=" + String(digitalRead(_pinoTcaRdy) == HIGH ? 1 : 0);
|
s += " tca_rdy=" + String(digitalRead(_pinoTcaRdy) == HIGH ? 1 : 0);
|
||||||
|
|
@ -761,6 +823,7 @@ class I2CService {
|
||||||
ContextoBarramento contexto = _contextoAtual;
|
ContextoBarramento contexto = _contextoAtual;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
_desabilitarMuxAoLiberar &&
|
||||||
contexto.valido &&
|
contexto.valido &&
|
||||||
contexto.canalMuxSolicitado >= 0 &&
|
contexto.canalMuxSolicitado >= 0 &&
|
||||||
!RecoveryPendente()
|
!RecoveryPendente()
|
||||||
|
|
@ -944,65 +1007,240 @@ class I2CService {
|
||||||
return TrocarCanalMux(enderecoMux + indiceMux, canal);
|
return TrocarCanalMux(enderecoMux + indiceMux, canal);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool TrocarCanalMux(uint8_t endereco, uint8_t canalMux) {
|
static bool EscreverMascaraMuxSemRegistrar(
|
||||||
int indiceMux = IndiceMux(endereco);
|
uint8_t endereco,
|
||||||
|
uint8_t mascara,
|
||||||
|
uint8_t& erro,
|
||||||
|
uint32_t& duracaoMs
|
||||||
|
) {
|
||||||
|
const uint32_t inicio = millis();
|
||||||
|
|
||||||
if (indiceMux < 0 || canalMux >= 8) {
|
Wire.beginTransmission(endereco);
|
||||||
MostrarLog(
|
Wire.write(mascara);
|
||||||
"MUX",
|
erro = Wire.endTransmission();
|
||||||
"Endereco ou canal invalido | endereco=0x" +
|
|
||||||
String(endereco, HEX) + " canal=" + String(canalMux)
|
duracaoMs =
|
||||||
);
|
(uint32_t)(millis() - inicio);
|
||||||
|
|
||||||
|
return erro == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Recovery local do TCA9548A.
|
||||||
|
*
|
||||||
|
* Reseta SOMENTE o MUX, revalida o endereco e volta a selecionar o
|
||||||
|
* canal desejado. Nao desmonta Wire e nao altera a geracao.
|
||||||
|
*/
|
||||||
|
static bool RecuperarMuxLocalSemGeracao(
|
||||||
|
uint8_t endereco,
|
||||||
|
uint8_t canalMux
|
||||||
|
) {
|
||||||
|
const int indiceMux =
|
||||||
|
IndiceMux(endereco);
|
||||||
|
|
||||||
|
if (
|
||||||
|
indiceMux < 0 ||
|
||||||
|
canalMux >= 8
|
||||||
|
) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
portENTER_CRITICAL(&_estadoLock);
|
portENTER_CRITICAL(&_estadoLock);
|
||||||
_muxEsperadoMask |= (uint8_t)(1U << indiceMux);
|
if (_recoveriesLocaisMux < UINT32_MAX) {
|
||||||
|
_recoveriesLocaisMux++;
|
||||||
|
}
|
||||||
portEXIT_CRITICAL(&_estadoLock);
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
uint8_t canalGlobal = (indiceMux * 10) + canalMux;
|
MostrarLog(
|
||||||
|
"MUX",
|
||||||
int canalAnterior = CanalGlobalAtivoAtual();
|
"Recovery LOCAL | endereco=0x" +
|
||||||
int sdaAntes = digitalRead(_pinoSDA);
|
String(endereco, HEX) +
|
||||||
int sclAntes = digitalRead(_pinoSCL);
|
" canal=" + String(canalMux) +
|
||||||
int rdyAntes = TCAReadyOk() ? 1 : 0;
|
" | geracao=" +
|
||||||
uint32_t inicio = millis();
|
String(GeracaoBarramento())
|
||||||
|
);
|
||||||
|
|
||||||
if (CanalMuxEmQuarentena(canalGlobal)) {
|
if (!ResetarMuxSePossivel()) {
|
||||||
MostrarLog("MUX", "Canal bloqueado por quarentena | canalGlobal=" + String(canalGlobal));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_muxValidado[indiceMux]) {
|
if (!TCAReadyOk()) {
|
||||||
MostrarLog("MUX", "Validando MUX em 0x" + String(endereco, HEX));
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (!VerificaEnderecoBarramento(endereco)) {
|
if (
|
||||||
MostrarLog("MUX", "MUX nao encontrado em 0x" + String(endereco, HEX));
|
!PingEnderecoSemRegistrar(
|
||||||
return false;
|
endereco,
|
||||||
|
80
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_muxValidado[indiceMux] = true;
|
||||||
|
MuxIniciado = true;
|
||||||
|
|
||||||
|
uint8_t erro = 0xFF;
|
||||||
|
uint32_t duracao = 0;
|
||||||
|
|
||||||
|
const bool ok =
|
||||||
|
EscreverMascaraMuxSemRegistrar(
|
||||||
|
endereco,
|
||||||
|
(uint8_t)(1U << canalMux),
|
||||||
|
erro,
|
||||||
|
duracao
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ok) {
|
||||||
|
MostrarLog(
|
||||||
|
"MUX",
|
||||||
|
"Recovery LOCAL nao conseguiu selecionar canal"
|
||||||
|
" | erro=" + String(erro) +
|
||||||
|
" duracao=" + String(duracao) + "ms"
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
enderecoMuxAtivo = endereco;
|
||||||
|
canalAtivo = canalMux;
|
||||||
|
|
||||||
|
MostrarLog(
|
||||||
|
"MUX",
|
||||||
|
"Recovery LOCAL concluido"
|
||||||
|
" | canal=" + String(canalMux) +
|
||||||
|
" | geracao=" +
|
||||||
|
String(GeracaoBarramento())
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool TrocarCanalMux(
|
||||||
|
uint8_t endereco,
|
||||||
|
uint8_t canalMux
|
||||||
|
) {
|
||||||
|
const int indiceMux =
|
||||||
|
IndiceMux(endereco);
|
||||||
|
|
||||||
|
if (
|
||||||
|
indiceMux < 0 ||
|
||||||
|
canalMux >= 8
|
||||||
|
) {
|
||||||
|
MostrarLog(
|
||||||
|
"MUX",
|
||||||
|
"Endereco ou canal invalido | endereco=0x" +
|
||||||
|
String(endereco, HEX) +
|
||||||
|
" canal=" +
|
||||||
|
String(canalMux)
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
portENTER_CRITICAL(&_estadoLock);
|
||||||
|
_muxEsperadoMask |=
|
||||||
|
(uint8_t)(1U << indiceMux);
|
||||||
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
|
const uint8_t canalGlobal =
|
||||||
|
(uint8_t)(
|
||||||
|
(indiceMux * 10) +
|
||||||
|
canalMux
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
CanalMuxEmQuarentena(
|
||||||
|
canalGlobal
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
MostrarLog(
|
||||||
|
"MUX",
|
||||||
|
"Canal bloqueado por quarentena"
|
||||||
|
" | canalGlobal=" +
|
||||||
|
String(canalGlobal)
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Se o MUX ainda nao foi validado, primeiro tentamos uma descoberta
|
||||||
|
* passiva. Ausencia momentanea NAO soma falha global.
|
||||||
|
*/
|
||||||
|
if (!_muxValidado[indiceMux]) {
|
||||||
|
const bool encontrado =
|
||||||
|
PingEnderecoSemRegistrar(
|
||||||
|
endereco,
|
||||||
|
80
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!encontrado) {
|
||||||
|
if (
|
||||||
|
!RecuperarMuxLocalSemGeracao(
|
||||||
|
endereco,
|
||||||
|
canalMux
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
RegistrarFalhaTransacao(
|
||||||
|
"MUX nao respondeu apos recovery local"
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegistrarSucessoTransacao();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_muxValidado[indiceMux] = true;
|
_muxValidado[indiceMux] = true;
|
||||||
MuxIniciado = true;
|
MuxIniciado = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enderecoMuxAtivo == endereco && canalAtivo == canalMux) {
|
if (
|
||||||
|
enderecoMuxAtivo == endereco &&
|
||||||
|
canalAtivo == canalMux
|
||||||
|
) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Evita deixar dois ramais conectados simultaneamente.
|
/*
|
||||||
if (enderecoMuxAtivo != 0xFF && enderecoMuxAtivo != endereco) {
|
* SEN atual possui um unico TCA9548A. Este bloco preserva suporte a
|
||||||
Wire.beginTransmission(enderecoMuxAtivo);
|
* multiplos MUXes: se um endereco diferente ficou selecionado,
|
||||||
Wire.write(0x00);
|
* desabilita-o antes de ativar o proximo.
|
||||||
int erroDesabilitar = Wire.endTransmission();
|
*/
|
||||||
|
if (
|
||||||
|
enderecoMuxAtivo != 0xFF &&
|
||||||
|
enderecoMuxAtivo != endereco
|
||||||
|
) {
|
||||||
|
uint8_t erroDesabilitar = 0xFF;
|
||||||
|
uint32_t duracaoDesabilitar = 0;
|
||||||
|
|
||||||
if (erroDesabilitar != 0) {
|
bool desabilitou =
|
||||||
|
EscreverMascaraMuxSemRegistrar(
|
||||||
|
enderecoMuxAtivo,
|
||||||
|
0x00,
|
||||||
|
erroDesabilitar,
|
||||||
|
duracaoDesabilitar
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!desabilitou) {
|
||||||
MostrarLog(
|
MostrarLog(
|
||||||
"MUX",
|
"MUX",
|
||||||
"Falha ao desabilitar MUX anterior 0x" +
|
"Falha ao desabilitar MUX anterior"
|
||||||
|
" | endereco=0x" +
|
||||||
String(enderecoMuxAtivo, HEX) +
|
String(enderecoMuxAtivo, HEX) +
|
||||||
" | erro=" + String(erroDesabilitar)
|
" erro=" +
|
||||||
|
String(erroDesabilitar) +
|
||||||
|
" duracao=" +
|
||||||
|
String(duracaoDesabilitar) +
|
||||||
|
"ms"
|
||||||
);
|
);
|
||||||
RegistrarFalhaTransacao("Falha ao desabilitar MUX anterior");
|
|
||||||
|
RegistrarFalhaTransacao(
|
||||||
|
"Falha ao desabilitar MUX anterior"
|
||||||
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1010,51 +1248,84 @@ class I2CService {
|
||||||
canalAtivo = -1;
|
canalAtivo = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Wire.beginTransmission(endereco);
|
const int canalAnterior =
|
||||||
Wire.write((uint8_t)(1U << canalMux));
|
CanalGlobalAtivoAtual();
|
||||||
int erro = Wire.endTransmission();
|
|
||||||
|
|
||||||
uint32_t duracao = (uint32_t)(millis() - inicio);
|
uint8_t ultimoErro = 0xFF;
|
||||||
int sdaDepois = digitalRead(_pinoSDA);
|
uint32_t ultimaDuracao = 0;
|
||||||
int sclDepois = digitalRead(_pinoSCL);
|
|
||||||
int rdyDepois = TCAReadyOk() ? 1 : 0;
|
|
||||||
|
|
||||||
MostrarLog(
|
for (
|
||||||
"MUX",
|
uint8_t tentativa = 0;
|
||||||
"Troca"
|
tentativa <
|
||||||
" | anterior=" + String(canalAnterior) +
|
TentativasTrocaMuxAntesResetLocal;
|
||||||
" solicitado=" + String(canalGlobal) +
|
tentativa++
|
||||||
" erro=" + String(erro) +
|
) {
|
||||||
" duracao=" + String(duracao) + "ms" +
|
const bool ok =
|
||||||
" antes[SDA=" + String(sdaAntes) +
|
EscreverMascaraMuxSemRegistrar(
|
||||||
" SCL=" + String(sclAntes) +
|
endereco,
|
||||||
" RDY=" + String(rdyAntes) + "]" +
|
(uint8_t)(1U << canalMux),
|
||||||
" depois[SDA=" + String(sdaDepois) +
|
ultimoErro,
|
||||||
" SCL=" + String(sclDepois) +
|
ultimaDuracao
|
||||||
" RDY=" + String(rdyDepois) + "]"
|
);
|
||||||
);
|
|
||||||
|
if (ok) {
|
||||||
|
enderecoMuxAtivo = endereco;
|
||||||
|
canalAtivo = canalMux;
|
||||||
|
|
||||||
|
RegistrarSucessoTransacao();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (erro != 0) {
|
|
||||||
MostrarLog(
|
MostrarLog(
|
||||||
"MUX",
|
"MUX",
|
||||||
"Falha ao trocar canal | endereco=0x" + String(endereco, HEX) +
|
"Falha local ao selecionar canal"
|
||||||
" canal=" + String(canalMux) + " erro=" + String(erro)
|
" | anterior=" +
|
||||||
|
String(canalAnterior) +
|
||||||
|
" solicitado=" +
|
||||||
|
String(canalGlobal) +
|
||||||
|
" tentativa=" +
|
||||||
|
String(tentativa + 1) +
|
||||||
|
" erro=" +
|
||||||
|
String(ultimoErro) +
|
||||||
|
" duracao=" +
|
||||||
|
String(ultimaDuracao) +
|
||||||
|
"ms"
|
||||||
|
" SDA=" +
|
||||||
|
String(digitalRead(_pinoSDA)) +
|
||||||
|
" SCL=" +
|
||||||
|
String(digitalRead(_pinoSCL)) +
|
||||||
|
" RDY=" +
|
||||||
|
String(TCAReadyOk() ? 1 : 0)
|
||||||
);
|
);
|
||||||
RegistrarFalhaTransacao("Falha ao trocar canal MUX");
|
|
||||||
return false;
|
DelayMs(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
enderecoMuxAtivo = endereco;
|
/*
|
||||||
canalAtivo = canalMux;
|
* As tentativas simples falharam. Antes de tocar no Wire, recupera
|
||||||
RegistrarSucessoTransacao();
|
* SOMENTE o MUX e tenta novamente.
|
||||||
|
*/
|
||||||
|
if (
|
||||||
|
RecuperarMuxLocalSemGeracao(
|
||||||
|
endereco,
|
||||||
|
canalMux
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
RegistrarSucessoTransacao();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
MostrarLog(
|
/*
|
||||||
"MUX",
|
* Somente agora uma falha do MUX alimenta o contador GLOBAL.
|
||||||
"Canal ativo | endereco=0x" + String(endereco, HEX) +
|
* Sao necessarias LimiteFalhasTransacaoRecovery ocorrencias
|
||||||
" canal=" + String(canalMux)
|
* consecutivas deste nivel para gerar uma nova geracao.
|
||||||
|
*/
|
||||||
|
RegistrarFalhaTransacao(
|
||||||
|
"MUX nao reviveu apos retries e reset local"
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int SelecionarCanalADS(uint8_t canalGlobal) {
|
static int SelecionarCanalADS(uint8_t canalGlobal) {
|
||||||
|
|
@ -1106,6 +1377,11 @@ class I2CService {
|
||||||
const bool leituraOk = LerADSSingleShotComTimeout(canal, leituraADC);
|
const bool leituraOk = LerADSSingleShotComTimeout(canal, leituraADC);
|
||||||
|
|
||||||
if (!leituraOk) {
|
if (!leituraOk) {
|
||||||
|
TratarFalhaADSLocal(
|
||||||
|
idSensor,
|
||||||
|
"falha de transporte/conversao"
|
||||||
|
);
|
||||||
|
|
||||||
leituraFalhou = true;
|
leituraFalhou = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
@ -1127,6 +1403,14 @@ class I2CService {
|
||||||
leiturasConcluidas++;
|
leiturasConcluidas++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
!leituraFalhou &&
|
||||||
|
leiturasConcluidas == leituras
|
||||||
|
) {
|
||||||
|
ResetarFalhasAdsLocais();
|
||||||
|
RegistrarSucessoLocalDispositivo(idSensor);
|
||||||
|
}
|
||||||
|
|
||||||
if (RecoveryPendente()) {
|
if (RecoveryPendente()) {
|
||||||
leituraFalhou = true;
|
leituraFalhou = true;
|
||||||
}
|
}
|
||||||
|
|
@ -1323,9 +1607,10 @@ class I2CService {
|
||||||
static ContextoBarramento _contextoFalha;
|
static ContextoBarramento _contextoFalha;
|
||||||
static portMUX_TYPE _estadoLock;
|
static portMUX_TYPE _estadoLock;
|
||||||
|
|
||||||
// Transacoes deste SEN sao de poucos bytes a 100 kHz.
|
// No core 2.0.11 existem caminhos do Wire que podem ignorar este
|
||||||
// 25 ms ainda e uma margem muito grande, mas limita melhor uma
|
// limite e retornar apenas ~1 s depois. Mantemos 25 ms como intencao do
|
||||||
// transferencia realmente emperrada.
|
// driver, mas o recovery local evita transformar esse stall em reboot
|
||||||
|
// global imediato.
|
||||||
static constexpr uint32_t TimeoutWireMs = 25;
|
static constexpr uint32_t TimeoutWireMs = 25;
|
||||||
static constexpr uint32_t LimiteReadyLowMs = 200;
|
static constexpr uint32_t LimiteReadyLowMs = 200;
|
||||||
static constexpr uint32_t CooldownRecoveryMs = 1000;
|
static constexpr uint32_t CooldownRecoveryMs = 1000;
|
||||||
|
|
@ -1350,6 +1635,24 @@ class I2CService {
|
||||||
static uint32_t _readyDropsAds;
|
static uint32_t _readyDropsAds;
|
||||||
static uint32_t _falhasIoAds;
|
static uint32_t _falhasIoAds;
|
||||||
|
|
||||||
|
// Saude/recovery local. Nenhum destes contadores muda a geracao.
|
||||||
|
static uint32_t _recoveriesLocaisMux;
|
||||||
|
static uint32_t _recoveriesLocaisAds;
|
||||||
|
static uint32_t _falhasLocaisDispositivos;
|
||||||
|
|
||||||
|
static uint8_t _falhasLocaisPorId[256];
|
||||||
|
|
||||||
|
static uint8_t _falhasAdsConsecutivas;
|
||||||
|
static uint8_t _falhasRecoveryLocalAdsConsecutivas;
|
||||||
|
static uint32_t _ultimoRecoveryLocalAdsMs;
|
||||||
|
|
||||||
|
static bool _desabilitarMuxAoLiberar;
|
||||||
|
|
||||||
|
static constexpr uint8_t LimiteFalhasAdsAntesRecoveryLocal = 3;
|
||||||
|
static constexpr uint8_t LimiteFalhasRecoveryLocalAdsAntesGlobal = 2;
|
||||||
|
static constexpr uint32_t CooldownRecoveryLocalAdsMs = 500;
|
||||||
|
static constexpr uint8_t TentativasTrocaMuxAntesResetLocal = 2;
|
||||||
|
|
||||||
static void MostrarLog(const String& componente, const String& mensagem) {
|
static void MostrarLog(const String& componente, const String& mensagem) {
|
||||||
if (DebugMode) {
|
if (DebugMode) {
|
||||||
PrintTela("[" + componente + "] " + mensagem);
|
PrintTela("[" + componente + "] " + mensagem);
|
||||||
|
|
@ -1975,6 +2278,195 @@ class I2CService {
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void RegistrarFalhaLocalDispositivo(
|
||||||
|
int idSensor,
|
||||||
|
const String& motivo
|
||||||
|
) {
|
||||||
|
uint8_t consecutivas = 0;
|
||||||
|
|
||||||
|
portENTER_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
|
if (_falhasLocaisDispositivos < UINT32_MAX) {
|
||||||
|
_falhasLocaisDispositivos++;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (idSensor >= 0 && idSensor <= 255) {
|
||||||
|
uint8_t& valor =
|
||||||
|
_falhasLocaisPorId[(uint8_t)idSensor];
|
||||||
|
|
||||||
|
if (valor < 255) {
|
||||||
|
valor++;
|
||||||
|
}
|
||||||
|
|
||||||
|
consecutivas = valor;
|
||||||
|
}
|
||||||
|
|
||||||
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
|
MostrarLog(
|
||||||
|
"I2C/LOCAL",
|
||||||
|
"ID " + String(idSensor) +
|
||||||
|
" | falha local #" + String(consecutivas) +
|
||||||
|
" | " + motivo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegistrarSucessoLocalDispositivo(int idSensor) {
|
||||||
|
if (idSensor < 0 || idSensor > 255) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
portENTER_CRITICAL(&_estadoLock);
|
||||||
|
_falhasLocaisPorId[(uint8_t)idSensor] = 0;
|
||||||
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ResetarFalhasAdsLocais() {
|
||||||
|
portENTER_CRITICAL(&_estadoLock);
|
||||||
|
_falhasAdsConsecutivas = 0;
|
||||||
|
_falhasRecoveryLocalAdsConsecutivas = 0;
|
||||||
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void RegistrarFalhaAdsConsecutiva() {
|
||||||
|
portENTER_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
|
if (_falhasAdsConsecutivas < 255) {
|
||||||
|
_falhasAdsConsecutivas++;
|
||||||
|
}
|
||||||
|
|
||||||
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t FalhasAdsConsecutivas() {
|
||||||
|
uint8_t valor;
|
||||||
|
|
||||||
|
portENTER_CRITICAL(&_estadoLock);
|
||||||
|
valor = _falhasAdsConsecutivas;
|
||||||
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
|
return valor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Recovery local do ADS:
|
||||||
|
* ADS power-cycle -> ping -> begin -> gain/rate
|
||||||
|
*
|
||||||
|
* Nao toca em Wire.end()/Wire.begin(), logo NAO altera a geracao e
|
||||||
|
* nao invalida INAs/MUX/outros sensores.
|
||||||
|
*
|
||||||
|
* Esta funcao e chamada com ownership do i2cMutex.
|
||||||
|
*/
|
||||||
|
static bool RecuperarADSLocalSemGeracao() {
|
||||||
|
const uint32_t agora = millis();
|
||||||
|
|
||||||
|
if (
|
||||||
|
_ultimoRecoveryLocalAdsMs > 0 &&
|
||||||
|
!TempoPassou(
|
||||||
|
_ultimoRecoveryLocalAdsMs,
|
||||||
|
CooldownRecoveryLocalAdsMs
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ultimoRecoveryLocalAdsMs = agora;
|
||||||
|
|
||||||
|
portENTER_CRITICAL(&_estadoLock);
|
||||||
|
if (_recoveriesLocaisAds < UINT32_MAX) {
|
||||||
|
_recoveriesLocaisAds++;
|
||||||
|
}
|
||||||
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
|
MostrarLog(
|
||||||
|
"ADS",
|
||||||
|
"Recovery LOCAL iniciado | sem alterar geracao"
|
||||||
|
);
|
||||||
|
|
||||||
|
AdsIniciado = false;
|
||||||
|
ultimoEnderecoADS = 0xFF;
|
||||||
|
|
||||||
|
if (_pinoAdsPwrEn >= 0) {
|
||||||
|
ResetarADSSePossivel();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
DelayMs(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool pingOk =
|
||||||
|
PingEnderecoSemRegistrar(
|
||||||
|
enderecoAds,
|
||||||
|
80
|
||||||
|
);
|
||||||
|
|
||||||
|
const bool iniciou =
|
||||||
|
pingOk &&
|
||||||
|
IniciarADSSemMutex(
|
||||||
|
enderecoAds
|
||||||
|
);
|
||||||
|
|
||||||
|
if (iniciou) {
|
||||||
|
ResetarFalhasAdsLocais();
|
||||||
|
|
||||||
|
MostrarLog(
|
||||||
|
"ADS",
|
||||||
|
"Recovery LOCAL concluido | geracao=" +
|
||||||
|
String(GeracaoBarramento())
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t falhasRecovery;
|
||||||
|
|
||||||
|
portENTER_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
|
if (
|
||||||
|
_falhasRecoveryLocalAdsConsecutivas <
|
||||||
|
255
|
||||||
|
) {
|
||||||
|
_falhasRecoveryLocalAdsConsecutivas++;
|
||||||
|
}
|
||||||
|
|
||||||
|
falhasRecovery =
|
||||||
|
_falhasRecoveryLocalAdsConsecutivas;
|
||||||
|
|
||||||
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
|
MostrarLog(
|
||||||
|
"ADS",
|
||||||
|
"Recovery LOCAL falhou | tentativas=" +
|
||||||
|
String(falhasRecovery)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
falhasRecovery >=
|
||||||
|
LimiteFalhasRecoveryLocalAdsAntesGlobal
|
||||||
|
) {
|
||||||
|
AgendarRecovery(
|
||||||
|
"ADS nao reviveu apos recoveries locais"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void TratarFalhaADSLocal(int idSensor, const String& motivo) {
|
||||||
|
RegistrarFalhaLocalDispositivo(
|
||||||
|
idSensor,
|
||||||
|
"ADS | " + motivo
|
||||||
|
);
|
||||||
|
|
||||||
|
RegistrarFalhaAdsConsecutiva();
|
||||||
|
|
||||||
|
if (
|
||||||
|
FalhasAdsConsecutivas() >=
|
||||||
|
LimiteFalhasAdsAntesRecoveryLocal
|
||||||
|
) {
|
||||||
|
(void)RecuperarADSLocalSemGeracao();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void RegistrarFalhaTransacao(const String& motivo) {
|
static void RegistrarFalhaTransacao(const String& motivo) {
|
||||||
uint32_t falhas;
|
uint32_t falhas;
|
||||||
|
|
||||||
|
|
@ -2455,7 +2947,13 @@ class I2CService {
|
||||||
static void RegistrarTimeoutADS(uint32_t inicioMs, const char* motivo) {
|
static void RegistrarTimeoutADS(uint32_t inicioMs, const char* motivo) {
|
||||||
AtualizarMaxDuracaoADS(inicioMs);
|
AtualizarMaxDuracaoADS(inicioMs);
|
||||||
IncrementarTimeoutADS();
|
IncrementarTimeoutADS();
|
||||||
RegistrarFalhaTransacao(motivo);
|
|
||||||
|
// Timeout isolado do ADS e degradacao LOCAL.
|
||||||
|
MostrarLog(
|
||||||
|
"ADS",
|
||||||
|
String(motivo) +
|
||||||
|
" | tratado como falha local"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void RegistrarReadyDropADS(uint32_t inicioMs) {
|
static void RegistrarReadyDropADS(uint32_t inicioMs) {
|
||||||
|
|
@ -2469,6 +2967,7 @@ class I2CService {
|
||||||
|
|
||||||
portEXIT_CRITICAL(&_estadoLock);
|
portEXIT_CRITICAL(&_estadoLock);
|
||||||
|
|
||||||
|
// READY pertence a infraestrutura global do barramento.
|
||||||
RegistrarFalhaTransacao("READY caiu durante leitura ADS");
|
RegistrarFalhaTransacao("READY caiu durante leitura ADS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2481,10 +2980,14 @@ class I2CService {
|
||||||
AtualizarMaxDuracaoADS(inicioMs);
|
AtualizarMaxDuracaoADS(inicioMs);
|
||||||
IncrementarFalhaIoADS();
|
IncrementarFalhaIoADS();
|
||||||
|
|
||||||
RegistrarFalhaTransacao(
|
// Erro de I/O do ADS continua local. O chamador decide quando
|
||||||
String("ADS ") + etapa +
|
// tentar power-cycle do ADS, sem alterar a geracao.
|
||||||
|
MostrarLog(
|
||||||
|
"ADS",
|
||||||
|
String(etapa) +
|
||||||
" | erro=" + String(erro) +
|
" | erro=" + String(erro) +
|
||||||
" | duracao=" + String(duracaoMs) + "ms"
|
" | duracao=" +
|
||||||
|
String(duracaoMs) + "ms"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2518,7 +3021,10 @@ class I2CService {
|
||||||
endereco == 0xFF ||
|
endereco == 0xFF ||
|
||||||
IndiceADS(endereco) < 0
|
IndiceADS(endereco) < 0
|
||||||
) {
|
) {
|
||||||
RegistrarFalhaTransacao("ADS sem endereco ativo");
|
MostrarLog(
|
||||||
|
"ADS",
|
||||||
|
"Sem endereco ativo para leitura"
|
||||||
|
);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -2769,4 +3275,16 @@ uint32_t I2CService::_timeoutsAds = 0;
|
||||||
uint32_t I2CService::_readyDropsAds = 0;
|
uint32_t I2CService::_readyDropsAds = 0;
|
||||||
uint32_t I2CService::_falhasIoAds = 0;
|
uint32_t I2CService::_falhasIoAds = 0;
|
||||||
|
|
||||||
|
uint32_t I2CService::_recoveriesLocaisMux = 0;
|
||||||
|
uint32_t I2CService::_recoveriesLocaisAds = 0;
|
||||||
|
uint32_t I2CService::_falhasLocaisDispositivos = 0;
|
||||||
|
|
||||||
|
uint8_t I2CService::_falhasLocaisPorId[256] = {0};
|
||||||
|
|
||||||
|
uint8_t I2CService::_falhasAdsConsecutivas = 0;
|
||||||
|
uint8_t I2CService::_falhasRecoveryLocalAdsConsecutivas = 0;
|
||||||
|
uint32_t I2CService::_ultimoRecoveryLocalAdsMs = 0;
|
||||||
|
|
||||||
|
bool I2CService::_desabilitarMuxAoLiberar = false;
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
||||||
|
|
@ -108,6 +108,9 @@ class PinoModel {
|
||||||
if (barramento == CONTROLADOR) {
|
if (barramento == CONTROLADOR) {
|
||||||
if (tipo == _PWM) {
|
if (tipo == _PWM) {
|
||||||
ledcDetachPin(num);
|
ledcDetachPin(num);
|
||||||
|
|
||||||
|
// PARA VERSAO 3.3.11 do core
|
||||||
|
//ledcDetach(static_cast<uint8_t>(num));
|
||||||
}
|
}
|
||||||
pinMode(num, INPUT);
|
pinMode(num, INPUT);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,11 +48,19 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EstaIniciado() {
|
bool EstaIniciado() {
|
||||||
|
const uint32_t geracaoAtual = I2CService::GeracaoBarramento();
|
||||||
|
|
||||||
bool iniciado;
|
bool iniciado;
|
||||||
|
uint32_t geracaoInicializacao;
|
||||||
|
|
||||||
portENTER_CRITICAL(&_snapshotLock);
|
portENTER_CRITICAL(&_snapshotLock);
|
||||||
iniciado = Iniciado;
|
iniciado = Iniciado;
|
||||||
|
geracaoInicializacao = _geracaoI2CInicializacao;
|
||||||
portEXIT_CRITICAL(&_snapshotLock);
|
portEXIT_CRITICAL(&_snapshotLock);
|
||||||
return iniciado;
|
|
||||||
|
return iniciado &&
|
||||||
|
geracaoAtual != 0 &&
|
||||||
|
geracaoInicializacao == geracaoAtual;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t FalhasConsecutivas() {
|
uint8_t FalhasConsecutivas() {
|
||||||
|
|
@ -84,7 +92,7 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
||||||
|
|
||||||
isINA219 = (_A_Shunt == TiposShuntCorrente::INA219_3A2);
|
isINA219 = (_A_Shunt == TiposShuntCorrente::INA219_3A2);
|
||||||
|
|
||||||
bool encontrado = I2CService::VerificaEnderecoBarramento(_Endereco);
|
bool encontrado = I2CService::VerificaEnderecoBarramentoPassivo(_Endereco);
|
||||||
if (!encontrado) {
|
if (!encontrado) {
|
||||||
AtualizarEstadoIniciado(false);
|
AtualizarEstadoIniciado(false);
|
||||||
MostrarLog("Endereco I2C nao encontrado: " + String(_Endereco));
|
MostrarLog("Endereco I2C nao encontrado: " + String(_Endereco));
|
||||||
|
|
@ -94,7 +102,7 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
||||||
|
|
||||||
if (isINA219) {
|
if (isINA219) {
|
||||||
sINA219 = Adafruit_INA219(_Endereco);
|
sINA219 = Adafruit_INA219(_Endereco);
|
||||||
bool iniciou = sINA219.begin();
|
bool iniciou = sINA219.begin(&Wire);
|
||||||
AtualizarEstadoIniciado(iniciou);
|
AtualizarEstadoIniciado(iniciou);
|
||||||
|
|
||||||
if (iniciou) {
|
if (iniciou) {
|
||||||
|
|
@ -106,7 +114,7 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
sINA228 = Adafruit_INA228();
|
sINA228 = Adafruit_INA228();
|
||||||
bool iniciou = sINA228.begin(_Endereco);
|
bool iniciou = sINA228.begin(_Endereco, &Wire);
|
||||||
AtualizarEstadoIniciado(iniciou);
|
AtualizarEstadoIniciado(iniciou);
|
||||||
|
|
||||||
if (iniciou) {
|
if (iniciou) {
|
||||||
|
|
@ -148,6 +156,7 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
||||||
data.push_back(static_cast<uint8_t>(posicao));
|
data.push_back(static_cast<uint8_t>(posicao));
|
||||||
data.push_back(ID_Num);
|
data.push_back(ID_Num);
|
||||||
|
|
||||||
|
const uint32_t geracaoAtual = I2CService::GeracaoBarramento();
|
||||||
bool iniciado;
|
bool iniciado;
|
||||||
bool ina219;
|
bool ina219;
|
||||||
float snapBusVoltage;
|
float snapBusVoltage;
|
||||||
|
|
@ -159,7 +168,9 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
||||||
|
|
||||||
// Copia curta e atômica. Serialização acontece fora da critical section.
|
// Copia curta e atômica. Serialização acontece fora da critical section.
|
||||||
portENTER_CRITICAL(&_snapshotLock);
|
portENTER_CRITICAL(&_snapshotLock);
|
||||||
iniciado = Iniciado;
|
iniciado = Iniciado &&
|
||||||
|
geracaoAtual != 0 &&
|
||||||
|
_geracaoI2CInicializacao == geracaoAtual;
|
||||||
ina219 = isINA219;
|
ina219 = isINA219;
|
||||||
snapBusVoltage = busVoltage;
|
snapBusVoltage = busVoltage;
|
||||||
snapCurrent = current;
|
snapCurrent = current;
|
||||||
|
|
@ -210,11 +221,16 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
||||||
portMUX_TYPE _snapshotLock = portMUX_INITIALIZER_UNLOCKED;
|
portMUX_TYPE _snapshotLock = portMUX_INITIALIZER_UNLOCKED;
|
||||||
uint8_t _falhasConsecutivas = 0;
|
uint8_t _falhasConsecutivas = 0;
|
||||||
uint32_t _totalFalhasLeitura = 0;
|
uint32_t _totalFalhasLeitura = 0;
|
||||||
|
uint32_t _geracaoI2CInicializacao = 0;
|
||||||
static constexpr uint8_t LimiteFalhasAntesReinit = 3;
|
static constexpr uint8_t LimiteFalhasAntesReinit = 3;
|
||||||
|
|
||||||
void AtualizarEstadoIniciado(bool iniciado) {
|
void AtualizarEstadoIniciado(bool iniciado) {
|
||||||
|
const uint32_t geracao =
|
||||||
|
iniciado ? I2CService::GeracaoBarramento() : 0;
|
||||||
|
|
||||||
portENTER_CRITICAL(&_snapshotLock);
|
portENTER_CRITICAL(&_snapshotLock);
|
||||||
Iniciado = iniciado;
|
Iniciado = iniciado;
|
||||||
|
_geracaoI2CInicializacao = geracao;
|
||||||
portEXIT_CRITICAL(&_snapshotLock);
|
portEXIT_CRITICAL(&_snapshotLock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -238,12 +254,16 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
||||||
AtualizarEstadoIniciado(false);
|
AtualizarEstadoIniciado(false);
|
||||||
MostrarLog("Sensor marcado para reinicializacao controlada");
|
MostrarLog("Sensor marcado para reinicializacao controlada");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
I2CService::NotificarFalhaDispositivo(ID_Num, motivo);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegistrarSucessoAquisicao() {
|
void RegistrarSucessoAquisicao() {
|
||||||
portENTER_CRITICAL(&_snapshotLock);
|
portENTER_CRITICAL(&_snapshotLock);
|
||||||
_falhasConsecutivas = 0;
|
_falhasConsecutivas = 0;
|
||||||
portEXIT_CRITICAL(&_snapshotLock);
|
portEXIT_CRITICAL(&_snapshotLock);
|
||||||
|
|
||||||
|
I2CService::NotificarSucessoDispositivo(ID_Num);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AmostraValida(
|
bool AmostraValida(
|
||||||
|
|
@ -307,7 +327,7 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
||||||
|
|
||||||
// As APIs Adafruit abaixo nao propagam de forma util os erros de Wire.
|
// As APIs Adafruit abaixo nao propagam de forma util os erros de Wire.
|
||||||
// Este ping confirma o dispositivo no ramal antes da aquisicao.
|
// Este ping confirma o dispositivo no ramal antes da aquisicao.
|
||||||
if (!I2CService::VerificaEnderecoBarramento(_Endereco)) {
|
if (!I2CService::VerificaEnderecoBarramentoPassivo(_Endereco)) {
|
||||||
I2CService::LiberarAcessoI2C(ID_Num);
|
I2CService::LiberarAcessoI2C(ID_Num);
|
||||||
RegistrarFalhaAquisicao("dispositivo nao respondeu no endereco I2C");
|
RegistrarFalhaAquisicao("dispositivo nao respondeu no endereco I2C");
|
||||||
return;
|
return;
|
||||||
|
|
@ -341,12 +361,6 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!amostraValida) {
|
if (!amostraValida) {
|
||||||
// Ainda dentro do ownership do I2C: o service consegue associar a
|
|
||||||
// falha ao ID/canal correto antes de o mutex ser liberado.
|
|
||||||
I2CService::NotificarFalhaDispositivo(
|
|
||||||
ID_Num,
|
|
||||||
"amostra INA invalida"
|
|
||||||
);
|
|
||||||
I2CService::LiberarAcessoI2C(ID_Num);
|
I2CService::LiberarAcessoI2C(ID_Num);
|
||||||
RegistrarFalhaAquisicao("amostra invalida/fora da faixa");
|
RegistrarFalhaAquisicao("amostra invalida/fora da faixa");
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -33,7 +33,7 @@ bool Conectado = false;
|
||||||
bool Conectar = false;
|
bool Conectar = false;
|
||||||
|
|
||||||
int _TaxaAmostragem = 1000;
|
int _TaxaAmostragem = 1000;
|
||||||
bool RequisitarDados = false;
|
bool RequisitarDados = true;
|
||||||
int latenciaLoop = 0;
|
int latenciaLoop = 0;
|
||||||
int qtdEnviosStatus = 0;
|
int qtdEnviosStatus = 0;
|
||||||
int maxEnviosStatus = 5;
|
int maxEnviosStatus = 5;
|
||||||
|
|
@ -76,9 +76,9 @@ uint32_t publicacaoMaxMs = 0;
|
||||||
|
|
||||||
// Aquisição física é responsabilidade exclusiva da TelemetriaTask.
|
// Aquisição física é responsabilidade exclusiva da TelemetriaTask.
|
||||||
// O caminho CAN apenas serializa os snapshots já disponíveis em memória.
|
// O caminho CAN apenas serializa os snapshots já disponíveis em memória.
|
||||||
static constexpr uint32_t PERIODO_CORRENTE_MS = 200; // 5 Hz
|
static constexpr uint32_t PERIODO_CORRENTE_MS = 500; // 2 Hz
|
||||||
static constexpr uint32_t PERIODO_NTC_MS = 500; // 2 Hz
|
static constexpr uint32_t PERIODO_NTC_MS = 1000; // 1 Hz
|
||||||
static constexpr uint32_t PERIODO_RETRY_I2C_MS = 10000; // retry lento
|
static constexpr uint32_t PERIODO_RETRY_I2C_MS = 3000; // retry lento
|
||||||
static constexpr uint32_t PERIODO_RETRY_SENSOR_MS = 10000;
|
static constexpr uint32_t PERIODO_RETRY_SENSOR_MS = 10000;
|
||||||
static constexpr uint32_t PERIODO_DIAG_TELEMETRIA_MS = 10000;
|
static constexpr uint32_t PERIODO_DIAG_TELEMETRIA_MS = 10000;
|
||||||
|
|
||||||
|
|
@ -139,10 +139,14 @@ Rele_v2* ObterRelePorId(uint8_t idNum) {
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
#pragma region TEMPERATURA NTC
|
#pragma region TEMPERATURA NTC
|
||||||
SensorTemperaturaNtc sNTC_MotorET("TMVET", 1, 6, TiposBarramentos::CONTROLADOR, -1, 0.0f, false);
|
//SensorTemperaturaNtc sNTC_MotorET("TMVET", 1, 6, TiposBarramentos::CONTROLADOR, -1, 0.0f, false);
|
||||||
SensorTemperaturaNtc sNTC_MotorDT("TMVDT", 3, 7, TiposBarramentos::CONTROLADOR, -1, 0.0f, true);
|
SensorTemperaturaNtc sNTC_MotorET("TMVET", 1, 0, TiposBarramentos::EXPANSOR_ADS, -1, 0.0f, true);
|
||||||
SensorTemperaturaNtc sNTC_MotorEF("TMVEF", 2, 8, TiposBarramentos::CONTROLADOR, -1, 0.0f, false);
|
//SensorTemperaturaNtc sNTC_MotorDT("TMVDT", 3, 7, TiposBarramentos::CONTROLADOR, -1, 0.0f, true);
|
||||||
SensorTemperaturaNtc sNTC_MotorDF("TMVDF", 4, 9, TiposBarramentos::CONTROLADOR, -1, 0.0f, true);
|
SensorTemperaturaNtc sNTC_MotorDT("TMVDT", 3, 1, TiposBarramentos::EXPANSOR_ADS, -1, 0.0f, true);
|
||||||
|
//SensorTemperaturaNtc sNTC_MotorEF("TMVEF", 2, 8, TiposBarramentos::CONTROLADOR, -1, 0.0f, false);
|
||||||
|
SensorTemperaturaNtc sNTC_MotorEF("TMVEF", 2, 2, TiposBarramentos::EXPANSOR_ADS, -1, 0.0f, true);
|
||||||
|
//SensorTemperaturaNtc sNTC_MotorDF("TMVDF", 4, 9, TiposBarramentos::CONTROLADOR, -1, 0.0f, true);
|
||||||
|
SensorTemperaturaNtc sNTC_MotorDF("TMVDF", 4, 3, TiposBarramentos::EXPANSOR_ADS, -1, 0.0f, true);
|
||||||
|
|
||||||
SensorTemperaturaNtc* ObterTempNtcPorId(uint8_t idNum) {
|
SensorTemperaturaNtc* ObterTempNtcPorId(uint8_t idNum) {
|
||||||
switch (idNum) {
|
switch (idNum) {
|
||||||
|
|
@ -195,6 +199,156 @@ SensorMagnetico_v2* ObterSensorMagneticoPorId(uint8_t idNum) {
|
||||||
}
|
}
|
||||||
#pragma endregion
|
#pragma endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#pragma region DIAGNOSTICO
|
||||||
|
|
||||||
|
uint8_t SaturarByte(uint32_t valor) {
|
||||||
|
return valor > 255 ? 255 : (uint8_t)valor;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t SaturarU16(uint32_t valor) {
|
||||||
|
return valor > 65535U ? 65535U : static_cast<uint16_t>(valor);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t MascaraSensoresCorrenteIniciados() {
|
||||||
|
uint8_t mask = 0;
|
||||||
|
|
||||||
|
SensorCorrente_v2* sensores[] = {
|
||||||
|
&sCOR_3V0,
|
||||||
|
&sCOR_5V0,
|
||||||
|
&sCOR_7V2,
|
||||||
|
&sCOR_12V,
|
||||||
|
&sCOR_19V,
|
||||||
|
&sCOR_24V,
|
||||||
|
&sCOR_36V
|
||||||
|
};
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < 7; i++) {
|
||||||
|
if (sensores[i] != nullptr && sensores[i]->EstaIniciado()) {
|
||||||
|
mask |= (uint8_t)(1U << i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> MontarFrameDiagnosticoI2C(CanMessagePosicaoDados posicao) {
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
|
||||||
|
data.push_back(static_cast<uint8_t>(posicao));
|
||||||
|
data.push_back(canService.ID_Num_sMOD);
|
||||||
|
I2CService::DiagnosticoCAN diag = I2CService::ObterDiagnosticoCAN();
|
||||||
|
|
||||||
|
if (posicao == CanMessagePosicaoDados::Dados1) {
|
||||||
|
uint8_t flagsEstado = 0;
|
||||||
|
|
||||||
|
if (diag.i2cIniciado)
|
||||||
|
flagsEstado |= (1U << 0);
|
||||||
|
|
||||||
|
if (diag.muxIniciado)
|
||||||
|
flagsEstado |= (1U << 1);
|
||||||
|
|
||||||
|
if (diag.infraestruturaMuxOk)
|
||||||
|
flagsEstado |= (1U << 2);
|
||||||
|
|
||||||
|
if (diag.recoveryEmAndamento)
|
||||||
|
flagsEstado |= (1U << 3);
|
||||||
|
|
||||||
|
if (diag.recoveryPendente)
|
||||||
|
flagsEstado |= (1U << 4);
|
||||||
|
|
||||||
|
if (diag.sdaHigh)
|
||||||
|
flagsEstado |= (1U << 5);
|
||||||
|
|
||||||
|
if (diag.sclHigh)
|
||||||
|
flagsEstado |= (1U << 6);
|
||||||
|
|
||||||
|
if (diag.ultimoIdFalha >= 0)
|
||||||
|
flagsEstado |= (1U << 7);
|
||||||
|
|
||||||
|
|
||||||
|
uint8_t flagsTca = 0;
|
||||||
|
|
||||||
|
if (diag.tcaRdyConfigurado)
|
||||||
|
flagsTca |= (1U << 0);
|
||||||
|
|
||||||
|
if (diag.tcaReady)
|
||||||
|
flagsTca |= (1U << 1);
|
||||||
|
|
||||||
|
if (diag.tcaEnConfigurado)
|
||||||
|
flagsTca |= (1U << 2);
|
||||||
|
|
||||||
|
if (diag.tcaEnHigh)
|
||||||
|
flagsTca |= (1U << 3);
|
||||||
|
|
||||||
|
if (diag.algumCanalMuxQuarentena)
|
||||||
|
flagsTca |= (1U << 4);
|
||||||
|
|
||||||
|
if (diag.algumAdsQuarentena)
|
||||||
|
flagsTca |= (1U << 5);
|
||||||
|
|
||||||
|
|
||||||
|
data.push_back(flagsEstado);
|
||||||
|
data.push_back(flagsTca);
|
||||||
|
data.push_back((uint8_t)(diag.geracaoBarramento & 0xFF));
|
||||||
|
data.push_back(SaturarByte(diag.totalRecoveries));
|
||||||
|
data.push_back(SaturarByte(diag.falhasRecoveryConsecutivas));
|
||||||
|
data.push_back(SaturarByte(diag.falhasTransacaoConsecutivas));
|
||||||
|
}
|
||||||
|
else if (posicao == CanMessagePosicaoDados::Dados2) {
|
||||||
|
data.push_back(diag.ultimoIdFalha >= 0 && diag.ultimoIdFalha <= 255 ? (uint8_t)diag.ultimoIdFalha : 0xFF);
|
||||||
|
data.push_back(diag.ultimoCanalFalha >= 0 && diag.ultimoCanalFalha <= 255 ? (uint8_t)diag.ultimoCanalFalha : 0xFF);
|
||||||
|
data.push_back(diag.muxEsperadoMask);
|
||||||
|
data.push_back(diag.muxValidadoMask);
|
||||||
|
data.push_back(diag.mux0QuarentenaMask);
|
||||||
|
data.push_back(MascaraSensoresCorrenteIniciados());
|
||||||
|
}
|
||||||
|
else if (posicao == CanMessagePosicaoDados::Dados3) {
|
||||||
|
const uint16_t maxAdsMs = SaturarU16(diag.maxLeituraAdsMs);
|
||||||
|
|
||||||
|
const uint16_t timeoutsAds = SaturarU16(diag.timeoutsAds);
|
||||||
|
|
||||||
|
const uint16_t readyDropsAds = SaturarU16(diag.readyDropsAds);
|
||||||
|
|
||||||
|
// bytes 2..3
|
||||||
|
data.push_back(static_cast<uint8_t>(maxAdsMs >> 8));
|
||||||
|
|
||||||
|
data.push_back(static_cast<uint8_t>(maxAdsMs & 0xFF));
|
||||||
|
|
||||||
|
// bytes 4..5
|
||||||
|
data.push_back(static_cast<uint8_t>(timeoutsAds >> 8));
|
||||||
|
|
||||||
|
data.push_back(static_cast<uint8_t>(timeoutsAds & 0xFF));
|
||||||
|
|
||||||
|
// bytes 6..7
|
||||||
|
data.push_back(static_cast<uint8_t>(readyDropsAds >> 8));
|
||||||
|
|
||||||
|
data.push_back(static_cast<uint8_t>(readyDropsAds & 0xFF));
|
||||||
|
|
||||||
|
}
|
||||||
|
else if (posicao == CanMessagePosicaoDados::Dados4) {
|
||||||
|
const uint16_t maxStart = SaturarU16(diag.maxInicioAdsMs);
|
||||||
|
const uint16_t maxRead = SaturarU16(diag.maxResultadoAdsMs);
|
||||||
|
const uint16_t falhasIo = SaturarU16(diag.falhasIoAds);
|
||||||
|
|
||||||
|
data.push_back((uint8_t)(maxStart >> 8));
|
||||||
|
data.push_back((uint8_t)(maxStart & 0xFF));
|
||||||
|
|
||||||
|
data.push_back((uint8_t)(maxRead >> 8));
|
||||||
|
data.push_back((uint8_t)(maxRead & 0xFF));
|
||||||
|
|
||||||
|
data.push_back((uint8_t)(falhasIo >> 8));
|
||||||
|
data.push_back((uint8_t)(falhasIo & 0xFF));
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
void MostrarLog(String mensagem) {
|
void MostrarLog(String mensagem) {
|
||||||
bool _debugMode = false;
|
bool _debugMode = false;
|
||||||
if (_debugMode) {
|
if (_debugMode) {
|
||||||
|
|
@ -203,9 +357,9 @@ void MostrarLog(String mensagem) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void inicializarDependenciasI2C() {
|
void inicializarDependenciasI2C() {
|
||||||
I2CService::DefinirPinos(1, 2, -1, -1, 39, 40);
|
I2CService::DefinirPinos(1, 2, 10, 11, 39, 40);
|
||||||
I2CService::IniciarI2C();
|
I2CService::IniciarI2C();
|
||||||
//I2CService::IniciarADS();
|
I2CService::IniciarADS();
|
||||||
I2CService::IniciarMUX();
|
I2CService::IniciarMUX();
|
||||||
//I2CService::IniciarMCP();
|
//I2CService::IniciarMCP();
|
||||||
}
|
}
|
||||||
|
|
@ -260,6 +414,30 @@ void TentarReinicializarSensoresCorrente() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void TentarReinicializarSensoresNtc() {
|
||||||
|
if (!I2CService::I2CIniciado)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!I2CService::AdsIniciado) {
|
||||||
|
if (!I2CService::IniciarADS())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SensorTemperaturaNtc* sensores[] = {
|
||||||
|
&sNTC_MotorET,
|
||||||
|
&sNTC_MotorDT,
|
||||||
|
&sNTC_MotorEF,
|
||||||
|
&sNTC_MotorDF
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto* sensor : sensores) {
|
||||||
|
if (sensor != nullptr && !sensor->EstaIniciado()) {
|
||||||
|
sensor->Inicializar();
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void TelemetriaTask(void* pv) {
|
void TelemetriaTask(void* pv) {
|
||||||
TickType_t ultimaCorrente = 0;
|
TickType_t ultimaCorrente = 0;
|
||||||
TickType_t ultimoNtc = 0;
|
TickType_t ultimoNtc = 0;
|
||||||
|
|
@ -271,18 +449,18 @@ void TelemetriaTask(void* pv) {
|
||||||
const TickType_t agora = xTaskGetTickCount();
|
const TickType_t agora = xTaskGetTickCount();
|
||||||
|
|
||||||
if ((agora - ultimaCorrente) >= pdMS_TO_TICKS(PERIODO_CORRENTE_MS)) {
|
if ((agora - ultimaCorrente) >= pdMS_TO_TICKS(PERIODO_CORRENTE_MS)) {
|
||||||
ultimaCorrente = agora;
|
|
||||||
const uint32_t inicioAquisicao = millis();
|
const uint32_t inicioAquisicao = millis();
|
||||||
AtualizarSensoresCorrente();
|
AtualizarSensoresCorrente();
|
||||||
|
ultimaCorrente = xTaskGetTickCount();
|
||||||
const uint32_t duracao = millis() - inicioAquisicao;
|
const uint32_t duracao = millis() - inicioAquisicao;
|
||||||
if (duracao > maxAquisicaoCorrenteMs) maxAquisicaoCorrenteMs = duracao;
|
if (duracao > maxAquisicaoCorrenteMs) maxAquisicaoCorrenteMs = duracao;
|
||||||
ciclosTelemetria++;
|
ciclosTelemetria++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((agora - ultimoNtc) >= pdMS_TO_TICKS(PERIODO_NTC_MS)) {
|
if ((agora - ultimoNtc) >= pdMS_TO_TICKS(PERIODO_NTC_MS)) {
|
||||||
ultimoNtc = agora;
|
|
||||||
const uint32_t inicioAquisicao = millis();
|
const uint32_t inicioAquisicao = millis();
|
||||||
AtualizarSensoresNtc();
|
AtualizarSensoresNtc();
|
||||||
|
ultimoNtc = xTaskGetTickCount();
|
||||||
const uint32_t duracao = millis() - inicioAquisicao;
|
const uint32_t duracao = millis() - inicioAquisicao;
|
||||||
if (duracao > maxAquisicaoNtcMs) maxAquisicaoNtcMs = duracao;
|
if (duracao > maxAquisicaoNtcMs) maxAquisicaoNtcMs = duracao;
|
||||||
}
|
}
|
||||||
|
|
@ -295,6 +473,7 @@ void TelemetriaTask(void* pv) {
|
||||||
if ((agora - ultimoRetrySensor) >= pdMS_TO_TICKS(PERIODO_RETRY_SENSOR_MS)) {
|
if ((agora - ultimoRetrySensor) >= pdMS_TO_TICKS(PERIODO_RETRY_SENSOR_MS)) {
|
||||||
ultimoRetrySensor = agora;
|
ultimoRetrySensor = agora;
|
||||||
TentarReinicializarSensoresCorrente();
|
TentarReinicializarSensoresCorrente();
|
||||||
|
TentarReinicializarSensoresNtc();
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((agora - ultimoDiag) >= pdMS_TO_TICKS(PERIODO_DIAG_TELEMETRIA_MS)) {
|
if ((agora - ultimoDiag) >= pdMS_TO_TICKS(PERIODO_DIAG_TELEMETRIA_MS)) {
|
||||||
|
|
@ -342,21 +521,33 @@ void setup() {
|
||||||
sLED_Manual.Inicializar();
|
sLED_Manual.Inicializar();
|
||||||
sLED_Automatico.Inicializar();
|
sLED_Automatico.Inicializar();
|
||||||
sLED_Operacao.Inicializar();
|
sLED_Operacao.Inicializar();
|
||||||
sNTC_MotorET.Inicializar();
|
|
||||||
sNTC_MotorDT.Inicializar();
|
if (I2CService::I2CIniciado) {
|
||||||
sNTC_MotorEF.Inicializar();
|
|
||||||
sNTC_MotorDF.Inicializar();
|
if (I2CService::AdsIniciado) {
|
||||||
sCOR_3V0.Inicializar();
|
sNTC_MotorET.Inicializar();
|
||||||
sCOR_5V0.Inicializar();
|
sNTC_MotorDT.Inicializar();
|
||||||
sCOR_7V2.Inicializar();
|
sNTC_MotorEF.Inicializar();
|
||||||
sCOR_12V.Inicializar();
|
sNTC_MotorDF.Inicializar();
|
||||||
sCOR_19V.Inicializar();
|
}
|
||||||
sCOR_24V.Inicializar();
|
|
||||||
sCOR_36V.Inicializar();
|
if (I2CService::MuxIniciado) {
|
||||||
//sMAG_MotorET.Inicializar();
|
sCOR_3V0.Inicializar();
|
||||||
//sMAG_MotorDT.Inicializar();
|
sCOR_5V0.Inicializar();
|
||||||
//sMAG_MotorEF.Inicializar();
|
sCOR_7V2.Inicializar();
|
||||||
//sMAG_MotorDF.Inicializar();
|
sCOR_12V.Inicializar();
|
||||||
|
sCOR_19V.Inicializar();
|
||||||
|
sCOR_24V.Inicializar();
|
||||||
|
sCOR_36V.Inicializar();
|
||||||
|
}
|
||||||
|
|
||||||
|
//sMAG_MotorET.Inicializar();
|
||||||
|
//sMAG_MotorDT.Inicializar();
|
||||||
|
//sMAG_MotorEF.Inicializar();
|
||||||
|
//sMAG_MotorDF.Inicializar();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ledManager.Adicionar(&sLED_Manual);
|
ledManager.Adicionar(&sLED_Manual);
|
||||||
ledManager.Adicionar(&sLED_Automatico);
|
ledManager.Adicionar(&sLED_Automatico);
|
||||||
|
|
@ -439,9 +630,33 @@ void enviarDadosSensores(uint8_t id_num, CanMessagePosicaoDados posicao) {
|
||||||
bool todosIDs = id_num == canService.ID_Num_sTOD;
|
bool todosIDs = id_num == canService.ID_Num_sTOD;
|
||||||
|
|
||||||
if (id_num == canService.ID_Num_sMOD) {
|
if (id_num == canService.ID_Num_sMOD) {
|
||||||
EnviarDadosCAN(canService.MontarFrameReqStatusMod(D_Code, cfg.conectado, VERSION));
|
switch (posicao) {
|
||||||
if (PublicacaoMutex != nullptr) xSemaphoreGive(PublicacaoMutex);
|
case CanMessagePosicaoDados::Status:
|
||||||
return;
|
EnviarDadosCAN(canService.MontarFrameReqStatusMod(D_Code, cfg.conectado, VERSION));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CanMessagePosicaoDados::Dados1:
|
||||||
|
case CanMessagePosicaoDados::Dados2:
|
||||||
|
case CanMessagePosicaoDados::Dados3:
|
||||||
|
case CanMessagePosicaoDados::Dados4:
|
||||||
|
EnviarDadosCAN(MontarFrameDiagnosticoI2C(posicao));
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PublicacaoMutex != nullptr)
|
||||||
|
xSemaphoreGive(PublicacaoMutex);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (todosIDs && todosDados) {
|
||||||
|
EnviarDadosCAN(MontarFrameDiagnosticoI2C(CanMessagePosicaoDados::Dados1));
|
||||||
|
EnviarDadosCAN(MontarFrameDiagnosticoI2C(CanMessagePosicaoDados::Dados2));
|
||||||
|
EnviarDadosCAN(MontarFrameDiagnosticoI2C(CanMessagePosicaoDados::Dados3));
|
||||||
|
EnviarDadosCAN(MontarFrameDiagnosticoI2C(CanMessagePosicaoDados::Dados4));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (todosIDs)
|
if (todosIDs)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue