Compare commits
2 Commits
e07a842152
...
9410933079
| Author | SHA1 | Date |
|---|---|---|
|
|
9410933079 | |
|
|
8054055ec8 |
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.LatenciaLoopMs = leitura?.LatenciaLoop ?? 0;
|
||||
model.StatusI2C = new Dictionary<CanMessagePosicaoDados, byte[]>(leitura?.StatusI2C ?? new Dictionary<CanMessagePosicaoDados, byte[]>());
|
||||
|
||||
model.UltimoComandoRespondido = leitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
||||
model.IdadeUltimoComandoMs = IdadeMs(model.UltimoComandoRespondido);
|
||||
|
|
@ -6132,6 +6133,7 @@ namespace AgroBase.Models
|
|||
public bool RequisitarDados { get; set; }
|
||||
|
||||
public int LatenciaLoopMs { get; set; }
|
||||
public Dictionary<CanMessagePosicaoDados, byte[]> StatusI2C { get; set; }
|
||||
|
||||
public DateTime UltimoComandoRespondido { get; set; } = DateTime.MinValue;
|
||||
public double IdadeUltimoComandoMs { get; set; }
|
||||
|
|
@ -6196,6 +6198,7 @@ namespace AgroBase.Models
|
|||
RequisitarDados = RequisitarDados,
|
||||
|
||||
LatenciaLoopMs = LatenciaLoopMs,
|
||||
StatusI2C = new Dictionary<CanMessagePosicaoDados, byte[]>(StatusI2C ?? new Dictionary<CanMessagePosicaoDados, byte[]>()),
|
||||
|
||||
UltimoComandoRespondido = UltimoComandoRespondido,
|
||||
IdadeUltimoComandoMs = IdadeUltimoComandoMs,
|
||||
|
|
|
|||
|
|
@ -315,8 +315,7 @@ namespace AgroBase.Services
|
|||
catch (Exception ex)
|
||||
{
|
||||
MostrarLog($"Erro ao inicializar: {ex.Message}");
|
||||
IsConnected = false;
|
||||
Iniciado = false;
|
||||
MarcarTransporteIndisponivel();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -326,23 +325,41 @@ namespace AgroBase.Services
|
|||
try
|
||||
{
|
||||
_tcpClient = new TcpClient();
|
||||
|
||||
_tcpClient.NoDelay = true;
|
||||
_tcpClient.ReceiveBufferSize = 64 * 1024;
|
||||
_tcpClient.SendBufferSize = 64 * 1024;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
_tcpStream = _tcpClient.GetStream();
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
try { _tcpStream?.Close(); }
|
||||
catch { }
|
||||
|
||||
try { _tcpClient?.Close(); }
|
||||
catch { }
|
||||
|
||||
_tcpStream = null;
|
||||
_tcpClient = null;
|
||||
|
||||
MostrarLog($"Erro TCP: {ex.Message}");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -368,19 +385,16 @@ namespace AgroBase.Services
|
|||
|
||||
private void DefinirDispositivo()
|
||||
{
|
||||
if (!SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Can))
|
||||
{
|
||||
if (IsConnected && Iniciado)
|
||||
{
|
||||
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
|
||||
if (!IsConnected || !Iniciado)
|
||||
return;
|
||||
|
||||
SerialService.AdicionarOuAtualizarDispositivoMapeado(new DispositivoDetalhesModel
|
||||
{
|
||||
Dispositivo = T_Code.Can,
|
||||
Endereco = IpAddress,
|
||||
Versao = ("1").ToString()
|
||||
Versao = "1"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
MostrarLog($"Erro ao enviar frame ID=0x{msg.IdTx:X}", msg.IdTx);
|
||||
IsConnected = false;
|
||||
MarcarTransporteIndisponivel();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -695,24 +709,28 @@ namespace AgroBase.Services
|
|||
catch (Exception ex)
|
||||
{
|
||||
MostrarLog($"Erro RX: {ex.Message}");
|
||||
IsConnected = false;
|
||||
MarcarTransporteIndisponivel();
|
||||
}
|
||||
|
||||
await Task.CompletedTask;
|
||||
}
|
||||
|
||||
private void OuvirTcp()
|
||||
{
|
||||
lock (_socketLock)
|
||||
{
|
||||
if (_tcpClient == null || _tcpStream == null || !_tcpClient.Connected)
|
||||
{
|
||||
IsConnected = false;
|
||||
MarcarTransporteIndisponivel();
|
||||
return;
|
||||
}
|
||||
|
||||
while (_tcpStream.DataAvailable)
|
||||
{
|
||||
byte[] buffer = new byte[4096];
|
||||
|
||||
int lidos = _tcpStream.Read(buffer, 0, buffer.Length);
|
||||
|
||||
if (lidos <= 0)
|
||||
break;
|
||||
|
||||
|
|
@ -723,37 +741,50 @@ namespace AgroBase.Services
|
|||
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()
|
||||
{
|
||||
lock (_socketLock)
|
||||
{
|
||||
if (_udpClient == null)
|
||||
{
|
||||
IsConnected = false;
|
||||
MarcarTransporteIndisponivel();
|
||||
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];
|
||||
|
||||
Buffer.BlockCopy(pacote, offset, frame, 0, USR_FRAME_SIZE);
|
||||
|
||||
ProcessarPacoteUsrStandard(frame);
|
||||
|
||||
offset += USR_FRAME_SIZE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ProcessarPacoteUsrStandard(byte[] packet)
|
||||
{
|
||||
|
|
@ -1142,8 +1173,8 @@ namespace AgroBase.Services
|
|||
DateTime agora = DateTime.Now;
|
||||
|
||||
bool socketOk = TransportMode == CanEthernetTransportMode.Tcp
|
||||
? (_tcpClient != null && _tcpClient.Connected && _tcpStream != null)
|
||||
: (_udpClient != null);
|
||||
? TcpSocketSaudavel()
|
||||
: _udpClient != null;
|
||||
|
||||
double ageTx = _ultimoTx == DateTime.MinValue
|
||||
? 999999
|
||||
|
|
@ -1159,13 +1190,11 @@ namespace AgroBase.Services
|
|||
_socketIndisponivelDesde = agora;
|
||||
|
||||
double tempoSuspeito = (agora - _socketIndisponivelDesde).TotalSeconds;
|
||||
|
||||
bool semTrafegoRecente = ageTx > 2.0 && ageRx > 2.0;
|
||||
|
||||
if (tempoSuspeito >= 1.0 && semTrafegoRecente)
|
||||
if (tempoSuspeito >= 1.0 && (IsConnected || Iniciado))
|
||||
{
|
||||
IsConnected = false;
|
||||
MarcarTransporteIndisponivel();
|
||||
RegistrarErroCritico("socket_indisponivel_confirmado");
|
||||
MostrarLog("Socket CAN-ETH indisponível; aguardando reinicialização do transporte...");
|
||||
}
|
||||
|
||||
return;
|
||||
|
|
@ -1174,25 +1203,50 @@ namespace AgroBase.Services
|
|||
_socketIndisponivelDesde = DateTime.MinValue;
|
||||
|
||||
bool existeDemanda = ExisteDemandaCan();
|
||||
|
||||
// Se existe demanda, mas houve TX recente e não houve RX,
|
||||
// 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)
|
||||
bool txAtivo = ageTx <= 2.0 || FreqTxJanela > 0.5;
|
||||
if (IsConnected && existeDemanda && txAtivo && ageRx > 8.0)
|
||||
{
|
||||
RegistrarErroCritico("sem_trafego_prolongado_com_demanda");
|
||||
MostrarLog("Sem tráfego prolongado no CAN-ETH com demanda, reiniciando conexão...");
|
||||
RegistrarErroCritico("tx_ativo_sem_rx_prolongado");
|
||||
MostrarLog("TX ativo sem RX prolongado; reiniciando conexão CAN-ETH...");
|
||||
ReabrirConexao();
|
||||
}
|
||||
|
||||
if (_ultimoErroCritico != DateTime.MinValue &&
|
||||
(agora - _ultimoErroCritico).TotalMinutes > 10)
|
||||
if (_ultimoErroCritico != DateTime.MinValue && (agora - _ultimoErroCritico).TotalMinutes > 10)
|
||||
{
|
||||
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
|
||||
{
|
||||
|
|
@ -1200,15 +1254,23 @@ namespace AgroBase.Services
|
|||
|
||||
FecharConexaoSemLimparHandlers();
|
||||
|
||||
if (TransportMode == CanEthernetTransportMode.Tcp)
|
||||
IsConnected = ConectarTcp();
|
||||
else
|
||||
IsConnected = ConectarUdp();
|
||||
bool conectado = TransportMode == CanEthernetTransportMode.Tcp
|
||||
? ConectarTcp()
|
||||
: ConectarUdp();
|
||||
|
||||
Iniciado = IsConnected;
|
||||
IsConnected = conectado;
|
||||
Iniciado = conectado;
|
||||
|
||||
if (!conectado)
|
||||
{
|
||||
MostrarLog($"Reconexão CAN-ETH falhou em {IpAddress}:{Port}");
|
||||
return false;
|
||||
}
|
||||
|
||||
ReconexoesTotal++;
|
||||
|
||||
_socketIndisponivelDesde = DateTime.MinValue;
|
||||
|
||||
_ultimoRx = DateTime.Now;
|
||||
_ultimoTx = DateTime.Now;
|
||||
|
||||
|
|
@ -1217,11 +1279,16 @@ namespace AgroBase.Services
|
|||
|
||||
UltimoRxUnixMs = NowUnixMs();
|
||||
UltimoTxUnixMs = NowUnixMs();
|
||||
|
||||
MostrarLog($"CAN-ETH reconectado em {IpAddress}:{Port}");
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
IsConnected = false;
|
||||
Iniciado = false;
|
||||
MarcarTransporteIndisponivel();
|
||||
MostrarLog($"Erro ao reabrir CAN-ETH: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1260,6 +1327,12 @@ namespace AgroBase.Services
|
|||
Iniciado = false;
|
||||
}
|
||||
|
||||
private void MarcarTransporteIndisponivel()
|
||||
{
|
||||
IsConnected = false;
|
||||
Iniciado = false;
|
||||
}
|
||||
|
||||
public void Fechar()
|
||||
{
|
||||
tmrOuvirCAN?.Dispose();
|
||||
|
|
|
|||
|
|
@ -445,7 +445,7 @@ namespace AgroBase.Services
|
|||
if (leitura == null)
|
||||
continue;
|
||||
|
||||
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
|
||||
SerialService.AdicionarOuAtualizarDispositivoMapeado(new DispositivoDetalhesModel()
|
||||
{
|
||||
Dispositivo = T_Code.Dir,
|
||||
Endereco = CanManager.CanService._portName,
|
||||
|
|
|
|||
|
|
@ -456,7 +456,7 @@ namespace AgroBase.Services
|
|||
|
||||
if (!SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Mov && x.Mod_ID == modId))
|
||||
{
|
||||
SerialService.DispositivosMapeados.Add(
|
||||
SerialService.AdicionarOuAtualizarDispositivoMapeado(
|
||||
new DispositivoDetalhesModel
|
||||
{
|
||||
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 pressaoLinha = bombaLinha?._PressaoAtual ?? _Sensoriamento?.Atuador?.PressaoLinha ?? 0;
|
||||
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(
|
||||
CtxKey.DadosContexto,
|
||||
|
|
@ -222,7 +222,7 @@ namespace AgroBase.Services.Operadores
|
|||
("Atuador", new
|
||||
{
|
||||
pressao_linha_regular = pressaoLinhaRegular,
|
||||
pressao_linha = _Sensoriamento.Atuador?.PressaoLinha ?? 0,
|
||||
pressao_linha = pressaoLinha,
|
||||
potencia_bomba = _Sensoriamento.Atuador?.LeituraPotenciaBomba ?? 0,
|
||||
estado_bomba = _Sensoriamento.Atuador?.LeituraComandoBomba ?? false,
|
||||
tempo_bomba_atuada = _Sensoriamento.Atuador?.TempoBombaAtuada ?? 0,
|
||||
|
|
@ -537,6 +537,17 @@ namespace AgroBase.Services.Operadores
|
|||
var agora_dt = DateTime.Now;
|
||||
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.Dir)));
|
||||
//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.Sen), string.Empty);
|
||||
|
||||
#region MOV e DIR
|
||||
|
||||
var dirAtivos = 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.Mov), ("ativos", movAtivos.ToList()));
|
||||
|
||||
#endregion
|
||||
|
||||
#region SEN
|
||||
|
||||
var DadosSen = op.DispSen.Dados;
|
||||
DateTime ultimaRespostaSen = DadosSen.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
||||
|
||||
double idadeUltimaRespostaSenMs =
|
||||
ultimaRespostaSen == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - ultimaRespostaSen).TotalMilliseconds;
|
||||
double idadeUltimaRespostaSenMs = CalcularIdadeMs(ultimaRespostaSen);
|
||||
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen),
|
||||
("timestamp", agora),
|
||||
("conectado", DadosSen.Conectado),
|
||||
("telemetria_recente", DadosSen.TelemetriaRecente),
|
||||
("firmware_conectado", DadosSen.FirmwareDeclarouConectado),
|
||||
("freq_base", DadosSen.FrequenciaBase),
|
||||
("bms_ligado", _Sensoriamento.Bateria?.BmsLigado ?? false),
|
||||
|
||||
|
|
@ -743,10 +758,7 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimaRespostaSensorMs =
|
||||
sensor.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - sensor.UltimaLeitura).TotalMilliseconds;
|
||||
double idadeUltimaRespostaSensorMs = CalcularIdadeMs(sensor.UltimaLeitura);
|
||||
|
||||
var sensorObj = new
|
||||
{
|
||||
|
|
@ -754,6 +766,7 @@ namespace AgroBase.Services.Operadores
|
|||
id,
|
||||
label = sensor.ID,
|
||||
tipo,
|
||||
freq_base = DadosSen.FrequenciaBase,
|
||||
aferir = sensor.Aferir,
|
||||
mandatorio = sensor.Mandatorio,
|
||||
dados,
|
||||
|
|
@ -799,15 +812,8 @@ namespace AgroBase.Services.Operadores
|
|||
|
||||
bool possuiRespostaServo = ultimaRespostaAnguloServo != DateTime.MinValue;
|
||||
|
||||
double idadeUltimoComandoServoMs =
|
||||
!possuiComandoServo
|
||||
? 999999
|
||||
: Math.Max(0, (agora_dt - servo.UltimoComandoEnviado).TotalMilliseconds);
|
||||
|
||||
double idadeUltimaRespostaServoMs =
|
||||
!possuiRespostaServo
|
||||
? 999999
|
||||
: Math.Max(0, (agora_dt - ultimaRespostaAnguloServo).TotalMilliseconds);
|
||||
double idadeUltimoComandoServoMs = CalcularIdadeMs(servo.UltimoComandoEnviado);
|
||||
double idadeUltimaRespostaServoMs = CalcularIdadeMs(ultimaRespostaAnguloServo);
|
||||
|
||||
bool telemetriaPosComandoServo =
|
||||
possuiComandoServo &&
|
||||
|
|
@ -820,6 +826,7 @@ namespace AgroBase.Services.Operadores
|
|||
id,
|
||||
label = servo.ID,
|
||||
tipo,
|
||||
freq_base = DadosSen.FrequenciaBase,
|
||||
controlar = servo.Controlar,
|
||||
mandatorio = servo.Mandatorio,
|
||||
|
||||
|
|
@ -875,15 +882,8 @@ namespace AgroBase.Services.Operadores
|
|||
|
||||
bool possuiRespostaRele = ultimaRespostaEstadoRele != DateTime.MinValue;
|
||||
|
||||
double idadeUltimoComandoReleMs =
|
||||
!possuiComandoRele
|
||||
? 999999
|
||||
: Math.Max(0, (agora_dt - rele.UltimoComandoEnviado).TotalMilliseconds);
|
||||
|
||||
double idadeUltimaRespostaReleMs =
|
||||
!possuiRespostaRele
|
||||
? 999999
|
||||
: Math.Max(0, (agora_dt - ultimaRespostaEstadoRele).TotalMilliseconds);
|
||||
double idadeUltimoComandoReleMs = CalcularIdadeMs(rele.UltimoComandoEnviado);
|
||||
double idadeUltimaRespostaReleMs = CalcularIdadeMs(ultimaRespostaEstadoRele);
|
||||
|
||||
bool telemetriaPosComandoRele =
|
||||
possuiComandoRele &&
|
||||
|
|
@ -896,6 +896,7 @@ namespace AgroBase.Services.Operadores
|
|||
id,
|
||||
label = rele.ID,
|
||||
tipo,
|
||||
freq_base = DadosSen.FrequenciaBase,
|
||||
controlar = rele.Controlar,
|
||||
mandatorio = rele.Mandatorio,
|
||||
dados,
|
||||
|
|
@ -966,15 +967,8 @@ namespace AgroBase.Services.Operadores
|
|||
? (ultimaRespostaLedId <= ultimaRespostaLedStatus ? ultimaRespostaLedId : ultimaRespostaLedStatus)
|
||||
: DateTime.MinValue;
|
||||
|
||||
double idadeUltimoComandoLedMs =
|
||||
!possuiComandoLed
|
||||
? 999999
|
||||
: Math.Max(0, (agora_dt - led.UltimoComandoEnviado).TotalMilliseconds);
|
||||
|
||||
double idadeUltimaRespostaLedMs =
|
||||
!possuiRespostaLed
|
||||
? 999999
|
||||
: Math.Max(0, (agora_dt - ultimaRespostaLed).TotalMilliseconds);
|
||||
double idadeUltimoComandoLedMs = CalcularIdadeMs(led.UltimoComandoEnviado);
|
||||
double idadeUltimaRespostaLedMs = CalcularIdadeMs(ultimaRespostaLed);
|
||||
|
||||
bool telemetriaPosComandoLed =
|
||||
possuiComandoLed &&
|
||||
|
|
@ -990,6 +984,7 @@ namespace AgroBase.Services.Operadores
|
|||
id,
|
||||
label = led.ID,
|
||||
tipo,
|
||||
freq_base = DadosSen.FrequenciaBase,
|
||||
controlar = led.Controlar,
|
||||
mandatorio = led.Mandatorio,
|
||||
|
||||
|
|
@ -1018,27 +1013,22 @@ namespace AgroBase.Services.Operadores
|
|||
senLedsAtualizados.Add(("sinaleiros.ativos", senLedsAtivos));
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), senLedsAtualizados.ToArray());
|
||||
|
||||
#endregion
|
||||
|
||||
#region ATU
|
||||
|
||||
var DadosAtu = op.DispAtu.Dados;
|
||||
DateTime ultimaRespostaAtu = DadosAtu.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
||||
|
||||
double idadeUltimaRespostaAtuMs =
|
||||
ultimaRespostaAtu == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - ultimaRespostaAtu).TotalMilliseconds;
|
||||
double idadeUltimaRespostaAtuMs = CalcularIdadeMs(ultimaRespostaAtu);
|
||||
|
||||
bool pulverizadorAutomatico =
|
||||
op.Parametros?.Controle?.PulverizadorAutomatico ?? false;
|
||||
bool pulverizadorAutomatico = op.Parametros?.Controle?.PulverizadorAutomatico ?? false;
|
||||
|
||||
double pressaoAlvo =
|
||||
op.Parametros?.Controle?.AtuPressaoLinha ?? 0;
|
||||
double pressaoAlvo = op.Parametros?.Controle?.AtuPressaoLinha ?? 0;
|
||||
|
||||
double pressaoLinha =
|
||||
DadosAtu.PressaoLinha;
|
||||
|
||||
bool pressaoLinhaRegular =
|
||||
pressaoAlvo <= 0 || pressaoLinha >= pressaoAlvo - 20;
|
||||
double pressaoLinha = DadosAtu?.BombaPressurizadora?._PressaoAtual ?? _Sensoriamento?.Atuador?.PressaoLinha ?? 0;
|
||||
double toleranciaPressao = Math.Max(2.0, pressaoAlvo * 0.15);
|
||||
bool pressaoLinhaRegular = pressaoAlvo <= 0 || pressaoLinha >= Math.Max(0, pressaoAlvo - toleranciaPressao);
|
||||
RedisService.AtualizarCampos(
|
||||
RedisService.ModKey(Enums.T_Code.Atu),
|
||||
("timestamp", agora),
|
||||
|
|
@ -1091,10 +1081,7 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimaRespostaSensorMs =
|
||||
sensor.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - sensor.UltimaLeitura).TotalMilliseconds;
|
||||
double idadeUltimaRespostaSensorMs = CalcularIdadeMs(sensor.UltimaLeitura);
|
||||
|
||||
var sensorObj = new
|
||||
{
|
||||
|
|
@ -1107,6 +1094,7 @@ namespace AgroBase.Services.Operadores
|
|||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { },
|
||||
freq_base = DadosAtu.FrequenciaBase,
|
||||
ultima_resposta_ms = idadeUltimaRespostaSensorMs,
|
||||
};
|
||||
|
||||
|
|
@ -1139,15 +1127,11 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimoComandoBicoMs =
|
||||
bico.UltimoComandoEnviado == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - bico.UltimoComandoEnviado).TotalMilliseconds;
|
||||
DateTime ultimoComandoBico = bico.UltimoComandoEnfileirado;
|
||||
|
||||
double idadeUltimaRespostaBicoMs =
|
||||
bico.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - bico.UltimaLeitura).TotalMilliseconds;
|
||||
double idadeUltimoComandoBicoMs = CalcularIdadeMs(ultimoComandoBico);
|
||||
|
||||
double idadeUltimaRespostaBicoMs = CalcularIdadeMs(bico.UltimaLeitura);
|
||||
|
||||
var bicoObj = new
|
||||
{
|
||||
|
|
@ -1160,6 +1144,7 @@ namespace AgroBase.Services.Operadores
|
|||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { },
|
||||
freq_base = DadosAtu.FrequenciaBase,
|
||||
|
||||
comando_atuar = bico.ComandoAtuar,
|
||||
estado_desejado = bico.UltimoStatusDesejado,
|
||||
|
|
@ -1181,15 +1166,15 @@ namespace AgroBase.Services.Operadores
|
|||
vazao_media_mls = bico.VazaoMediaMLs,
|
||||
volume_vazado_ml = bico.VolumeVazadoML,
|
||||
|
||||
comando_solicitado = bico.ComandoAtuar,
|
||||
comando_solicitado = bico.Comandar && bico.ComandoAtuar,
|
||||
status_desejado_fisico = bico.UltimoStatusDesejado,
|
||||
angulo_desejado_fisico = bico.UltimoAnguloDesejado,
|
||||
ultimo_comando_ms = idadeUltimoComandoBicoMs,
|
||||
ultima_resposta_ms = idadeUltimaRespostaBicoMs,
|
||||
telemetria_pos_comando =
|
||||
bico.UltimoComandoEnviado != DateTime.MinValue &&
|
||||
bico.UltimaLeitura != DateTime.MinValue &&
|
||||
bico.UltimaLeitura >= bico.UltimoComandoEnviado
|
||||
ultimoComandoBico != DateTime.MinValue &&
|
||||
bico.UltimaRespostaPosteriorAoComando != DateTime.MinValue &&
|
||||
bico.UltimaRespostaPosteriorAoComando >= ultimoComandoBico
|
||||
};
|
||||
|
||||
atuBicosAtualizados.Add(($"{basePath}", bicoObj));
|
||||
|
|
@ -1221,15 +1206,11 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimoComandoBombaMs =
|
||||
bomba.UltimoComandoEnviado == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - bomba.UltimoComandoEnviado).TotalMilliseconds;
|
||||
DateTime ultimoComandoBomba = bomba.UltimoComandoEnfileirado;
|
||||
|
||||
double idadeUltimaRespostaBombaMs =
|
||||
bomba.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - bomba.UltimaLeitura).TotalMilliseconds;
|
||||
double idadeUltimoComandoBombaMs = CalcularIdadeMs(ultimoComandoBomba);
|
||||
|
||||
double idadeUltimaRespostaBombaMs = CalcularIdadeMs(bomba.UltimaLeitura);
|
||||
|
||||
var sensorObj = new
|
||||
{
|
||||
|
|
@ -1242,6 +1223,7 @@ namespace AgroBase.Services.Operadores
|
|||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { },
|
||||
freq_base = DadosAtu.FrequenciaBase,
|
||||
|
||||
comando_atuar = bomba.ComandoAtuar,
|
||||
estado_desejado = bomba.UltimoStatusDesejado,
|
||||
|
|
@ -1274,12 +1256,12 @@ namespace AgroBase.Services.Operadores
|
|||
ultimo_comando_ms = idadeUltimoComandoBombaMs,
|
||||
ultima_resposta_ms = idadeUltimaRespostaBombaMs,
|
||||
telemetria_pos_comando =
|
||||
bomba.UltimoComandoEnviado != DateTime.MinValue &&
|
||||
bomba.UltimaLeitura != DateTime.MinValue &&
|
||||
bomba.UltimaLeitura >= bomba.UltimoComandoEnviado,
|
||||
|
||||
ultimoComandoBomba != DateTime.MinValue &&
|
||||
bomba.UltimaRespostaPosteriorAoComando != DateTime.MinValue &&
|
||||
bomba.UltimaRespostaPosteriorAoComando >= ultimoComandoBomba,
|
||||
desligar_sem_bicos_abertos = bomba.Controle?.DesligarSemBicosAbertos ?? true,
|
||||
qtd_bicos_ligados = bomba._QtdBicosLigados,
|
||||
|
||||
potencia_final = bomba._PotenciaFinal,
|
||||
potencia_feedforward = bomba._PotenciaFeedForwared,
|
||||
pressao_sensor = bomba._PressaoSensor,
|
||||
|
|
@ -1300,6 +1282,7 @@ namespace AgroBase.Services.Operadores
|
|||
atuBombasAtualizados.Add(("bombas.ativos", atuBombasAtivos));
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Atu), atuBombasAtualizados.ToArray());
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
//var DadosLra = Variaveis.LoraService;
|
||||
|
|
|
|||
|
|
@ -785,7 +785,7 @@ namespace AgroBase.Services
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!CanManager.CanService.Iniciado)
|
||||
if (!CanManager.CanService.Iniciado || !CanManager.CanService.IsConnected)
|
||||
{
|
||||
bool initialized = CanManager.CanService.Inicializar(250);
|
||||
|
||||
|
|
@ -1587,8 +1587,7 @@ namespace AgroBase.Services
|
|||
}
|
||||
}
|
||||
|
||||
public static void AdicionarOuAtualizarDispositivoMapeado(
|
||||
DispositivoDetalhesModel device)
|
||||
public static void AdicionarOuAtualizarDispositivoMapeado(DispositivoDetalhesModel device)
|
||||
{
|
||||
if (device == null)
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -56,6 +56,7 @@ class ModuloAtuador(ModuloDiagnosticoBase):
|
|||
self.padroes_bicos = {}
|
||||
self.padroes_bombas = {}
|
||||
self.runtime_bombas = {}
|
||||
self.runtime_bicos = {}
|
||||
|
||||
def atualizar_saude(self):
|
||||
try:
|
||||
|
|
@ -612,50 +613,149 @@ class ModuloAtuador(ModuloDiagnosticoBase):
|
|||
dados = item["dados"]
|
||||
label = dados.get("label")
|
||||
|
||||
agora = time.monotonic()
|
||||
runtime = self._runtime_bico(item)
|
||||
|
||||
# ============================================================
|
||||
# Comando desejado e estado físico
|
||||
# ============================================================
|
||||
|
||||
comando_presente = (
|
||||
"comando_atuar" in dados or
|
||||
"estado_desejado" in dados or
|
||||
"status_desejado_fisico" in dados or
|
||||
"ultimo_status_desejado" in dados
|
||||
"comando_atuar" in dados
|
||||
or "estado_desejado" in dados
|
||||
or "status_desejado_fisico" in dados
|
||||
or "ultimo_status_desejado" in 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)
|
||||
|
||||
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_desejado = None
|
||||
angulo_neutro = None
|
||||
angulo_controle = None
|
||||
erro_angulo = 0.0
|
||||
|
||||
if usa_servo:
|
||||
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,
|
||||
)
|
||||
|
||||
angulo_neutro = self._float(dados.get("angulo_neutro", 90), 90)
|
||||
angulo_controle = self._float(dados.get("angulo_controle", dados.get("angulo_sp", 90)), 90)
|
||||
angulo_neutro = self._float(
|
||||
dados.get("angulo_neutro", 90),
|
||||
90,
|
||||
)
|
||||
|
||||
angulo_controle = self._float(
|
||||
dados.get(
|
||||
"angulo_controle",
|
||||
dados.get("angulo_sp", 90),
|
||||
),
|
||||
90,
|
||||
)
|
||||
|
||||
angulo_desejado = self._float(
|
||||
dados.get(
|
||||
"angulo_desejado_fisico",
|
||||
dados.get(
|
||||
"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)
|
||||
tem_idade_comando = ultimo_comando_ms < 900000
|
||||
erro_angulo = abs(
|
||||
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:
|
||||
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, {
|
||||
"comando_disponivel": False,
|
||||
"estado_on": estado_on,
|
||||
|
|
@ -664,103 +764,367 @@ class ModuloAtuador(ModuloDiagnosticoBase):
|
|||
"angulo_desejado": angulo_desejado,
|
||||
}
|
||||
|
||||
if tem_idade_comando and ultimo_comando_ms < self.COMANDO_GRACE_MS:
|
||||
return 96, {
|
||||
"comando_atuar": comando,
|
||||
# ============================================================
|
||||
# Telemetria velha
|
||||
#
|
||||
# A avaliação de resposta do componente já diagnosticará isso.
|
||||
# Não devemos transformar uma leitura antiga em falha mecânica.
|
||||
# ============================================================
|
||||
|
||||
if not telemetria_fresca:
|
||||
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 100, {
|
||||
"comando_disponivel": True,
|
||||
"avaliacao_suspensa": (
|
||||
"telemetria do bico desatualizada"
|
||||
),
|
||||
"comando_fisico": comando,
|
||||
"estado_on": estado_on,
|
||||
"usa_servo": usa_servo,
|
||||
"angulo": angulo,
|
||||
"angulo_desejado": angulo_desejado,
|
||||
"ultimo_comando_ms": ultimo_comando_ms,
|
||||
"ultima_resposta_ms": ultima_resposta_ms,
|
||||
}
|
||||
|
||||
if self._aguardando_telemetria_pos_comando(dados):
|
||||
return 94, {
|
||||
# ============================================================
|
||||
# Divergências brutas
|
||||
# ============================================================
|
||||
|
||||
divergencia_ligar = (
|
||||
comando and not estado_on
|
||||
)
|
||||
|
||||
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,
|
||||
"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,
|
||||
"angulo_neutro": angulo_neutro,
|
||||
"angulo_controle": angulo_controle,
|
||||
"erro_angulo": erro_angulo,
|
||||
"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),
|
||||
"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,
|
||||
}
|
||||
|
||||
if comando and not estado_on:
|
||||
severidade = 85 if tem_idade_comando and ultimo_comando_ms >= self.COMANDO_FALHA_MS else 70
|
||||
# ============================================================
|
||||
# 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})"
|
||||
)
|
||||
|
||||
motivos.append(f"Bico comandado ligado, mas leitura ainda indica desligado ({label})")
|
||||
condicoes.append({
|
||||
"label": "Bico",
|
||||
"valor": 0,
|
||||
"severidade": severidade,
|
||||
"classe": "processo",
|
||||
"descricao": f"Bico {label} comandado ligado, mas estado lido ainda está desligado",
|
||||
"classe": (
|
||||
"falha_hardware"
|
||||
if falha_confirmada
|
||||
else "processo"
|
||||
),
|
||||
"descricao": (
|
||||
f"Bico {label} permaneceu desligado "
|
||||
"apesar do comando físico ON"
|
||||
),
|
||||
"acoes": [
|
||||
"Aguardar próxima atualização CAN.",
|
||||
"Verificar solenoide do bico se a divergência persistir.",
|
||||
"Conferir alimentação e retorno do bico.",
|
||||
"Verificar solenoide do bico.",
|
||||
"Verificar alimentação e retorno do bico.",
|
||||
"Conferir se o comando chegou ao ATU.",
|
||||
],
|
||||
})
|
||||
|
||||
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)
|
||||
score = min(
|
||||
score,
|
||||
45 if falha_confirmada else 75,
|
||||
)
|
||||
|
||||
severidade = 95 if falha_confirmada else 82
|
||||
# ============================================================
|
||||
# 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})"
|
||||
)
|
||||
|
||||
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} aparenta estar ligado sem comando",
|
||||
"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)
|
||||
score = min(
|
||||
score,
|
||||
25 if falha_confirmada else 65,
|
||||
)
|
||||
|
||||
if usa_servo and angulo is not None and angulo_desejado is not None:
|
||||
erro_ang = abs(angulo - angulo_desejado)
|
||||
# ============================================================
|
||||
# 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}°)"
|
||||
)
|
||||
|
||||
if erro_ang > self.ANGULO_TOL_BICO_GRAUS and (comando or estado_on):
|
||||
motivos.append(f"Ângulo do bico distante do desejado ({angulo:.1f}°/{angulo_desejado:.1f}°)")
|
||||
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",
|
||||
"severidade": severidade,
|
||||
"classe": (
|
||||
"falha_hardware"
|
||||
if falha_confirmada
|
||||
else "processo"
|
||||
),
|
||||
"descricao": (
|
||||
f"Ângulo do bico {label} permaneceu "
|
||||
"distante do desejado"
|
||||
),
|
||||
"acoes": [
|
||||
"Verificar servo/posição mecânica do bico.",
|
||||
"Conferir neutro e ângulo de abertura configurados.",
|
||||
"Verificar servo e posição mecânica do bico.",
|
||||
"Conferir neutro e ângulo de abertura.",
|
||||
"Verificar telemetria angular do bico.",
|
||||
],
|
||||
})
|
||||
score = min(score, 78)
|
||||
|
||||
return int(max(0, min(100, round(score)))), {
|
||||
"comando_disponivel": True,
|
||||
"comando_atuar": comando,
|
||||
"estado_on": estado_on,
|
||||
"usa_servo": usa_servo,
|
||||
"angulo": angulo,
|
||||
"angulo_desejado": angulo_desejado,
|
||||
"angulo_neutro": angulo_neutro,
|
||||
"angulo_controle": angulo_controle,
|
||||
"ultimo_comando_ms": ultimo_comando_ms,
|
||||
}
|
||||
score = min(
|
||||
score,
|
||||
45 if falha_confirmada else 78,
|
||||
)
|
||||
|
||||
return int(
|
||||
max(
|
||||
0,
|
||||
min(
|
||||
100,
|
||||
round(score),
|
||||
),
|
||||
)
|
||||
), debug
|
||||
|
||||
# ============================================================
|
||||
# Bombas
|
||||
|
|
@ -1369,6 +1733,22 @@ class ModuloAtuador(ModuloDiagnosticoBase):
|
|||
|
||||
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
|
||||
def _atualizar_persistencia(runtime, campo, condicao, agora):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import time
|
||||
import math
|
||||
from shared.enums import T_Code, StatusModulo
|
||||
from shared.utils import penalizar_frequencia
|
||||
from health_worker.modulos.base import ModuloDiagnosticoBase
|
||||
|
|
@ -56,10 +57,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
operacao = ContextoGlobalRedis.get_operacao() 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)
|
||||
|
||||
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")
|
||||
servos = self._coletar(dados_sen, "servos")
|
||||
|
|
@ -78,7 +81,7 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
updates = {}
|
||||
|
||||
for item in sensores:
|
||||
s = self._avaliar_sensor(item, ctx)
|
||||
s = self._avaliar_sensor(item, ctx, modulo_disponivel=sen_disponivel)
|
||||
saude_individual.append(s)
|
||||
updates[f"sensores.{item['key']}.saude"] = s
|
||||
|
||||
|
|
@ -126,6 +129,8 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
|
||||
if not conectado:
|
||||
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"]):
|
||||
saude_final = min(saude_final, 40)
|
||||
if processo.get("energia_critica", False):
|
||||
|
|
@ -156,6 +161,8 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
|
||||
saude_geral = {
|
||||
"conectado": conectado,
|
||||
"telemetria_recente": conectado,
|
||||
"firmware_conectado": firmware_conectado,
|
||||
"status": status.value,
|
||||
"saude": saude_final,
|
||||
"motivos": self._dedupe(motivos),
|
||||
|
|
@ -196,7 +203,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
# ============================================================
|
||||
|
||||
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)
|
||||
motivos = []
|
||||
condicoes = []
|
||||
|
|
@ -275,22 +287,97 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
# Sensores passivos
|
||||
# ============================================================
|
||||
|
||||
def _avaliar_sensor(self, item, ctx):
|
||||
def _avaliar_sensor(self, item, ctx, modulo_disponivel=True):
|
||||
dados = item["dados"]
|
||||
conectado = self._bool(dados.get("conectado", False))
|
||||
aferir = self._bool(dados.get("aferir", False))
|
||||
mandatorio = self._bool(dados.get("mandatorio", False))
|
||||
delegado_ao_mov = str(dados.get("label", "")).upper().startswith("TMV")
|
||||
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 = []
|
||||
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")
|
||||
valor_score, valor_debug = self._avaliar_valores_sensor(item, ctx, motivos, condicoes)
|
||||
telemetria = self._avaliar_telemetria(dados, motivos, self._labels_criticos_sensor(dados, ctx, mandatorio))
|
||||
criticos = 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
|
||||
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))))
|
||||
status = self._resolver_status_item(conectado, saude, condicoes)
|
||||
debug = {
|
||||
|
||||
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,
|
||||
}
|
||||
return self._item(item, status, saude, motivos, condicoes, em_uso, debug, "sensor")
|
||||
},
|
||||
"sensor",
|
||||
)
|
||||
|
||||
def _avaliar_valores_sensor(self, item, ctx, motivos, condicoes):
|
||||
def _avaliar_valores_sensor(self, item, ctx, motivos, condicoes, criticos):
|
||||
dados = item["dados"]
|
||||
sensor_label = str(dados.get("label", ""))
|
||||
criticos = {self._canon(c) for c in (criticos or []) if c}
|
||||
score = 100.0
|
||||
debug = {}
|
||||
|
||||
for key, campo in self._campos(dados).items():
|
||||
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)
|
||||
maximo = campo.get("maximo", None)
|
||||
nominal = campo.get("nominal", None)
|
||||
unidade = campo.get("unidade", campo.get("unidade_medida", ""))
|
||||
|
||||
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)
|
||||
score = min(score, sc)
|
||||
motivos.extend(mots)
|
||||
|
|
@ -509,6 +629,11 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
|
||||
token_desejado = round(angulo_sp, 3)
|
||||
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)
|
||||
debug["erro"] = erro
|
||||
|
|
@ -532,12 +657,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
||||
return 96, debug
|
||||
|
||||
if not evidencia["telemetria_pos_comando"]:
|
||||
if not runtime["telemetria_confirmada_desejo"]:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "aguardando telemetria posterior ao comando"
|
||||
return 96, debug
|
||||
|
||||
if evidencia["ultimo_comando_ms"] < self.COMANDO_GRACE_MS:
|
||||
if idade_desejo_ms < self.COMANDO_GRACE_MS:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "período de resposta ao comando"
|
||||
return 96, debug
|
||||
|
|
@ -862,6 +987,9 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
"coerencia_desde": None,
|
||||
"nivel_divergencia": None,
|
||||
"ultimo_desejado": None,
|
||||
"desejo_alterado_em": None,
|
||||
"telemetria_confirmada_desejo": False,
|
||||
"possui_comando_anterior": False,
|
||||
})
|
||||
|
||||
def _preparar_atuador_auxiliar(
|
||||
|
|
@ -995,10 +1123,18 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
|
||||
def _registrar_desejado(self, runtime, 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["desejo_alterado_em"] = time.monotonic()
|
||||
runtime["telemetria_confirmada_desejo"] = False
|
||||
runtime["possui_comando_anterior"] = False
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _resetar_estado_comando(runtime, manter_desejado=True):
|
||||
|
|
@ -1007,6 +1143,43 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
runtime["nivel_divergencia"] = None
|
||||
if not manter_desejado:
|
||||
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(
|
||||
self,
|
||||
|
|
@ -1019,10 +1192,7 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
):
|
||||
dados = item["dados"]
|
||||
label = dados.get("label")
|
||||
|
||||
controlar = self._bool(
|
||||
dados.get("controlar", False)
|
||||
)
|
||||
controlar = self._bool(dados.get("controlar", False))
|
||||
|
||||
desejado_presente = any(
|
||||
chave in dados
|
||||
|
|
@ -1051,89 +1221,95 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
lido = self._estado_ligado(
|
||||
dados.get(
|
||||
"estado_leitura",
|
||||
self._campo_atual(
|
||||
dados,
|
||||
"EstadoLeitura",
|
||||
False,
|
||||
),
|
||||
self._campo_atual(dados, "EstadoLeitura", False),
|
||||
)
|
||||
)
|
||||
|
||||
ultimo_comando_ms = self._float(
|
||||
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
|
||||
)
|
||||
evidencia = self._evidencia_comando(dados)
|
||||
|
||||
debug = {
|
||||
"controlar": controlar,
|
||||
"estado_lido": lido,
|
||||
"estado_desejado": desejado,
|
||||
"possui_comando": possui_comando,
|
||||
"telemetria_pos_comando": telemetria_pos_comando,
|
||||
"ultimo_comando_ms": ultimo_comando_ms,
|
||||
"ultima_resposta_ms": ultima_resposta_ms,
|
||||
**evidencia,
|
||||
}
|
||||
|
||||
if not controlar:
|
||||
runtime["divergencia_desde"] = None
|
||||
self._resetar_estado_comando(runtime, manter_desejado=False)
|
||||
return 100, debug
|
||||
|
||||
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
|
||||
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:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["divergente"] = False
|
||||
|
||||
if runtime["nivel_divergencia"] is None:
|
||||
runtime["coerencia_desde"] = None
|
||||
return 100, debug
|
||||
|
||||
debug["divergente"] = True
|
||||
agora = time.monotonic()
|
||||
if runtime["coerencia_desde"] is None:
|
||||
runtime["coerencia_desde"] = agora
|
||||
|
||||
# Não existe evidência de que o estado desejado foi enviado.
|
||||
if not possui_comando:
|
||||
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
|
||||
runtime["coerencia_desde"] = None
|
||||
|
||||
if not evidencia["possui_comando"]:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
||||
return 96, debug
|
||||
|
||||
# A resposta ainda é antiga em relação ao comando.
|
||||
if not telemetria_pos_comando:
|
||||
if not runtime["telemetria_confirmada_desejo"]:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = (
|
||||
"aguardando telemetria posterior ao comando"
|
||||
)
|
||||
return 96, debug
|
||||
|
||||
# Comando acabou de ser enviado.
|
||||
if ultimo_comando_ms < self.COMANDO_GRACE_MS:
|
||||
if idade_desejo_ms < self.COMANDO_GRACE_MS:
|
||||
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
|
||||
|
||||
agora = time.monotonic()
|
||||
|
|
@ -1147,16 +1323,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
< self.COMPONENTE_RECUPERACAO_GRACE_MS
|
||||
):
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = (
|
||||
"estabilização após reconexão"
|
||||
)
|
||||
debug["avaliacao_suspensa"] = "estabilização após reconexão"
|
||||
debug["tempo_desde_retorno_componente_ms"] = (
|
||||
tempo_desde_retorno_componente_ms
|
||||
)
|
||||
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:
|
||||
runtime["divergencia_desde"] = agora
|
||||
|
||||
|
|
@ -1166,39 +1338,54 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
|
||||
debug["tempo_divergente_ms"] = tempo_divergente_ms
|
||||
|
||||
if tempo_divergente_ms < self.COMANDO_ALERTA_MS:
|
||||
return 96, debug
|
||||
|
||||
if tempo_divergente_ms < self.COMANDO_FALHA_MS:
|
||||
motivos.append(
|
||||
f"{label} ainda não acompanhou o estado desejado"
|
||||
)
|
||||
|
||||
condicoes.append({
|
||||
"label": nome,
|
||||
"valor": int(lido),
|
||||
"severidade": 78,
|
||||
"classe": "processo",
|
||||
"descricao":
|
||||
f"{label} apresenta divergência persistente de comando",
|
||||
"acoes": [
|
||||
"Aguardar confirmação ou verificar saída física.",
|
||||
],
|
||||
})
|
||||
|
||||
return 68, debug
|
||||
|
||||
motivos.append(
|
||||
f"{label} não acompanhou o estado desejado"
|
||||
)
|
||||
|
||||
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",
|
||||
"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:
|
||||
return 96, debug
|
||||
|
||||
if tempo_divergente_ms < self.COMANDO_FALHA_MS:
|
||||
runtime["nivel_divergencia"] = "alerta"
|
||||
|
||||
motivos.append(
|
||||
f"{label} ainda não acompanhou o estado desejado"
|
||||
)
|
||||
condicoes.append({
|
||||
"label": nome,
|
||||
"valor": int(lido),
|
||||
"severidade": 78,
|
||||
"classe": "processo",
|
||||
"descricao": (
|
||||
f"{label} apresenta divergência persistente de comando"
|
||||
),
|
||||
"acoes": [
|
||||
"Aguardar confirmação ou verificar saída física.",
|
||||
],
|
||||
})
|
||||
return 68, debug
|
||||
|
||||
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.",
|
||||
|
|
@ -1319,6 +1506,11 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
st_sp if status_sp_presente else None,
|
||||
)
|
||||
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.
|
||||
coerente_id = (
|
||||
|
|
@ -1366,12 +1558,12 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
debug["avaliacao_suspensa"] = "aguardando primeiro comando"
|
||||
return 96, debug
|
||||
|
||||
if not evidencia["telemetria_pos_comando"]:
|
||||
if not runtime["telemetria_confirmada_desejo"]:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "aguardando telemetria posterior ao comando"
|
||||
return 96, debug
|
||||
|
||||
if evidencia["ultimo_comando_ms"] < self.COMANDO_GRACE_MS:
|
||||
if idade_desejo_ms < self.COMANDO_GRACE_MS:
|
||||
runtime["divergencia_desde"] = None
|
||||
debug["avaliacao_suspensa"] = "período de resposta ao comando"
|
||||
return 96, debug
|
||||
|
|
@ -1653,6 +1845,8 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
if not conectado:
|
||||
return StatusModulo.DESCONECTADO
|
||||
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):
|
||||
return StatusModulo.FALHA
|
||||
if any(x.get("status") == StatusModulo.FALHA.value for x in em_uso):
|
||||
|
|
@ -1665,6 +1859,8 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
return StatusModulo.ALERTA
|
||||
if any(x.get("status") == StatusModulo.ALERTA.value for x in em_uso):
|
||||
return StatusModulo.ALERTA
|
||||
if any(x.get("status") == StatusModulo.DESCONECTADO.value for x in em_uso):
|
||||
return StatusModulo.ALERTA
|
||||
return StatusModulo.OPERANTE
|
||||
|
||||
@staticmethod
|
||||
|
|
@ -1795,14 +1991,24 @@ class ModuloSensoriamento(ModuloDiagnosticoBase):
|
|||
|
||||
@staticmethod
|
||||
def _float(valor, padrao=0.0):
|
||||
fallback = None if padrao is None else float(padrao)
|
||||
|
||||
try:
|
||||
if valor is None:
|
||||
return float(padrao)
|
||||
return fallback
|
||||
|
||||
if hasattr(valor, "value"):
|
||||
valor = valor.value
|
||||
return float(valor)
|
||||
|
||||
numero = float(valor)
|
||||
|
||||
if not math.isfinite(numero):
|
||||
return fallback
|
||||
|
||||
return numero
|
||||
|
||||
except Exception:
|
||||
return float(padrao)
|
||||
return fallback
|
||||
|
||||
@staticmethod
|
||||
def _safe_int(valor, padrao=0):
|
||||
|
|
|
|||
|
|
@ -78,6 +78,31 @@ public:
|
|||
// 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.
|
||||
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(CanTaskTxHandle);
|
||||
esp_task_wdt_add(CallbackWorkerHandle);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -108,6 +108,9 @@ class PinoModel {
|
|||
if (barramento == CONTROLADOR) {
|
||||
if (tipo == _PWM) {
|
||||
ledcDetachPin(num);
|
||||
|
||||
// PARA VERSAO 3.3.11 do core
|
||||
//ledcDetach(static_cast<uint8_t>(num));
|
||||
}
|
||||
pinMode(num, INPUT);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,11 +48,19 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
|||
}
|
||||
|
||||
bool EstaIniciado() {
|
||||
const uint32_t geracaoAtual = I2CService::GeracaoBarramento();
|
||||
|
||||
bool iniciado;
|
||||
uint32_t geracaoInicializacao;
|
||||
|
||||
portENTER_CRITICAL(&_snapshotLock);
|
||||
iniciado = Iniciado;
|
||||
geracaoInicializacao = _geracaoI2CInicializacao;
|
||||
portEXIT_CRITICAL(&_snapshotLock);
|
||||
return iniciado;
|
||||
|
||||
return iniciado &&
|
||||
geracaoAtual != 0 &&
|
||||
geracaoInicializacao == geracaoAtual;
|
||||
}
|
||||
|
||||
uint8_t FalhasConsecutivas() {
|
||||
|
|
@ -84,7 +92,7 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
|||
|
||||
isINA219 = (_A_Shunt == TiposShuntCorrente::INA219_3A2);
|
||||
|
||||
bool encontrado = I2CService::VerificaEnderecoBarramento(_Endereco);
|
||||
bool encontrado = I2CService::VerificaEnderecoBarramentoPassivo(_Endereco);
|
||||
if (!encontrado) {
|
||||
AtualizarEstadoIniciado(false);
|
||||
MostrarLog("Endereco I2C nao encontrado: " + String(_Endereco));
|
||||
|
|
@ -94,7 +102,7 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
|||
|
||||
if (isINA219) {
|
||||
sINA219 = Adafruit_INA219(_Endereco);
|
||||
bool iniciou = sINA219.begin();
|
||||
bool iniciou = sINA219.begin(&Wire);
|
||||
AtualizarEstadoIniciado(iniciou);
|
||||
|
||||
if (iniciou) {
|
||||
|
|
@ -106,7 +114,7 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
|||
}
|
||||
else {
|
||||
sINA228 = Adafruit_INA228();
|
||||
bool iniciou = sINA228.begin(_Endereco);
|
||||
bool iniciou = sINA228.begin(_Endereco, &Wire);
|
||||
AtualizarEstadoIniciado(iniciou);
|
||||
|
||||
if (iniciou) {
|
||||
|
|
@ -148,6 +156,7 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
|||
data.push_back(static_cast<uint8_t>(posicao));
|
||||
data.push_back(ID_Num);
|
||||
|
||||
const uint32_t geracaoAtual = I2CService::GeracaoBarramento();
|
||||
bool iniciado;
|
||||
bool ina219;
|
||||
float snapBusVoltage;
|
||||
|
|
@ -159,7 +168,9 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
|||
|
||||
// Copia curta e atômica. Serialização acontece fora da critical section.
|
||||
portENTER_CRITICAL(&_snapshotLock);
|
||||
iniciado = Iniciado;
|
||||
iniciado = Iniciado &&
|
||||
geracaoAtual != 0 &&
|
||||
_geracaoI2CInicializacao == geracaoAtual;
|
||||
ina219 = isINA219;
|
||||
snapBusVoltage = busVoltage;
|
||||
snapCurrent = current;
|
||||
|
|
@ -210,11 +221,16 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
|||
portMUX_TYPE _snapshotLock = portMUX_INITIALIZER_UNLOCKED;
|
||||
uint8_t _falhasConsecutivas = 0;
|
||||
uint32_t _totalFalhasLeitura = 0;
|
||||
uint32_t _geracaoI2CInicializacao = 0;
|
||||
static constexpr uint8_t LimiteFalhasAntesReinit = 3;
|
||||
|
||||
void AtualizarEstadoIniciado(bool iniciado) {
|
||||
const uint32_t geracao =
|
||||
iniciado ? I2CService::GeracaoBarramento() : 0;
|
||||
|
||||
portENTER_CRITICAL(&_snapshotLock);
|
||||
Iniciado = iniciado;
|
||||
_geracaoI2CInicializacao = geracao;
|
||||
portEXIT_CRITICAL(&_snapshotLock);
|
||||
}
|
||||
|
||||
|
|
@ -238,12 +254,16 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
|||
AtualizarEstadoIniciado(false);
|
||||
MostrarLog("Sensor marcado para reinicializacao controlada");
|
||||
}
|
||||
|
||||
I2CService::NotificarFalhaDispositivo(ID_Num, motivo);
|
||||
}
|
||||
|
||||
void RegistrarSucessoAquisicao() {
|
||||
portENTER_CRITICAL(&_snapshotLock);
|
||||
_falhasConsecutivas = 0;
|
||||
portEXIT_CRITICAL(&_snapshotLock);
|
||||
|
||||
I2CService::NotificarSucessoDispositivo(ID_Num);
|
||||
}
|
||||
|
||||
bool AmostraValida(
|
||||
|
|
@ -307,7 +327,7 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
|||
|
||||
// As APIs Adafruit abaixo nao propagam de forma util os erros de Wire.
|
||||
// Este ping confirma o dispositivo no ramal antes da aquisicao.
|
||||
if (!I2CService::VerificaEnderecoBarramento(_Endereco)) {
|
||||
if (!I2CService::VerificaEnderecoBarramentoPassivo(_Endereco)) {
|
||||
I2CService::LiberarAcessoI2C(ID_Num);
|
||||
RegistrarFalhaAquisicao("dispositivo nao respondeu no endereco I2C");
|
||||
return;
|
||||
|
|
@ -341,12 +361,6 @@ class SensorCorrente_v2 : public ComponenteCAN {
|
|||
);
|
||||
|
||||
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);
|
||||
RegistrarFalhaAquisicao("amostra invalida/fora da faixa");
|
||||
return;
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -33,7 +33,7 @@ bool Conectado = false;
|
|||
bool Conectar = false;
|
||||
|
||||
int _TaxaAmostragem = 1000;
|
||||
bool RequisitarDados = false;
|
||||
bool RequisitarDados = true;
|
||||
int latenciaLoop = 0;
|
||||
int qtdEnviosStatus = 0;
|
||||
int maxEnviosStatus = 5;
|
||||
|
|
@ -76,9 +76,9 @@ uint32_t publicacaoMaxMs = 0;
|
|||
|
||||
// Aquisição física é responsabilidade exclusiva da TelemetriaTask.
|
||||
// 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_NTC_MS = 500; // 2 Hz
|
||||
static constexpr uint32_t PERIODO_RETRY_I2C_MS = 10000; // retry lento
|
||||
static constexpr uint32_t PERIODO_CORRENTE_MS = 500; // 2 Hz
|
||||
static constexpr uint32_t PERIODO_NTC_MS = 1000; // 1 Hz
|
||||
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_DIAG_TELEMETRIA_MS = 10000;
|
||||
|
||||
|
|
@ -139,10 +139,14 @@ Rele_v2* ObterRelePorId(uint8_t idNum) {
|
|||
#pragma endregion
|
||||
|
||||
#pragma region TEMPERATURA NTC
|
||||
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_MotorEF("TMVEF", 2, 8, TiposBarramentos::CONTROLADOR, -1, 0.0f, false);
|
||||
SensorTemperaturaNtc sNTC_MotorDF("TMVDF", 4, 9, TiposBarramentos::CONTROLADOR, -1, 0.0f, true);
|
||||
//SensorTemperaturaNtc sNTC_MotorET("TMVET", 1, 6, TiposBarramentos::CONTROLADOR, -1, 0.0f, false);
|
||||
SensorTemperaturaNtc sNTC_MotorET("TMVET", 1, 0, TiposBarramentos::EXPANSOR_ADS, -1, 0.0f, true);
|
||||
//SensorTemperaturaNtc sNTC_MotorDT("TMVDT", 3, 7, 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) {
|
||||
switch (idNum) {
|
||||
|
|
@ -195,6 +199,156 @@ SensorMagnetico_v2* ObterSensorMagneticoPorId(uint8_t idNum) {
|
|||
}
|
||||
#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) {
|
||||
bool _debugMode = false;
|
||||
if (_debugMode) {
|
||||
|
|
@ -203,9 +357,9 @@ void MostrarLog(String mensagem) {
|
|||
}
|
||||
|
||||
void inicializarDependenciasI2C() {
|
||||
I2CService::DefinirPinos(1, 2, -1, -1, 39, 40);
|
||||
I2CService::DefinirPinos(1, 2, 10, 11, 39, 40);
|
||||
I2CService::IniciarI2C();
|
||||
//I2CService::IniciarADS();
|
||||
I2CService::IniciarADS();
|
||||
I2CService::IniciarMUX();
|
||||
//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) {
|
||||
TickType_t ultimaCorrente = 0;
|
||||
TickType_t ultimoNtc = 0;
|
||||
|
|
@ -271,18 +449,18 @@ void TelemetriaTask(void* pv) {
|
|||
const TickType_t agora = xTaskGetTickCount();
|
||||
|
||||
if ((agora - ultimaCorrente) >= pdMS_TO_TICKS(PERIODO_CORRENTE_MS)) {
|
||||
ultimaCorrente = agora;
|
||||
const uint32_t inicioAquisicao = millis();
|
||||
AtualizarSensoresCorrente();
|
||||
ultimaCorrente = xTaskGetTickCount();
|
||||
const uint32_t duracao = millis() - inicioAquisicao;
|
||||
if (duracao > maxAquisicaoCorrenteMs) maxAquisicaoCorrenteMs = duracao;
|
||||
ciclosTelemetria++;
|
||||
}
|
||||
|
||||
if ((agora - ultimoNtc) >= pdMS_TO_TICKS(PERIODO_NTC_MS)) {
|
||||
ultimoNtc = agora;
|
||||
const uint32_t inicioAquisicao = millis();
|
||||
AtualizarSensoresNtc();
|
||||
ultimoNtc = xTaskGetTickCount();
|
||||
const uint32_t duracao = millis() - inicioAquisicao;
|
||||
if (duracao > maxAquisicaoNtcMs) maxAquisicaoNtcMs = duracao;
|
||||
}
|
||||
|
|
@ -295,6 +473,7 @@ void TelemetriaTask(void* pv) {
|
|||
if ((agora - ultimoRetrySensor) >= pdMS_TO_TICKS(PERIODO_RETRY_SENSOR_MS)) {
|
||||
ultimoRetrySensor = agora;
|
||||
TentarReinicializarSensoresCorrente();
|
||||
TentarReinicializarSensoresNtc();
|
||||
}
|
||||
|
||||
if ((agora - ultimoDiag) >= pdMS_TO_TICKS(PERIODO_DIAG_TELEMETRIA_MS)) {
|
||||
|
|
@ -342,10 +521,17 @@ void setup() {
|
|||
sLED_Manual.Inicializar();
|
||||
sLED_Automatico.Inicializar();
|
||||
sLED_Operacao.Inicializar();
|
||||
|
||||
if (I2CService::I2CIniciado) {
|
||||
|
||||
if (I2CService::AdsIniciado) {
|
||||
sNTC_MotorET.Inicializar();
|
||||
sNTC_MotorDT.Inicializar();
|
||||
sNTC_MotorEF.Inicializar();
|
||||
sNTC_MotorDF.Inicializar();
|
||||
}
|
||||
|
||||
if (I2CService::MuxIniciado) {
|
||||
sCOR_3V0.Inicializar();
|
||||
sCOR_5V0.Inicializar();
|
||||
sCOR_7V2.Inicializar();
|
||||
|
|
@ -353,10 +539,15 @@ void setup() {
|
|||
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_Automatico);
|
||||
|
|
@ -439,11 +630,35 @@ void enviarDadosSensores(uint8_t id_num, CanMessagePosicaoDados posicao) {
|
|||
bool todosIDs = id_num == canService.ID_Num_sTOD;
|
||||
|
||||
if (id_num == canService.ID_Num_sMOD) {
|
||||
switch (posicao) {
|
||||
case CanMessagePosicaoDados::Status:
|
||||
EnviarDadosCAN(canService.MontarFrameReqStatusMod(D_Code, cfg.conectado, VERSION));
|
||||
if (PublicacaoMutex != nullptr) xSemaphoreGive(PublicacaoMutex);
|
||||
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)
|
||||
MostrarLog("Atualizando dados dos sensores...");
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue