Compare commits

..

2 Commits

16 changed files with 6703 additions and 2204 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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,

View File

@ -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,18 +385,15 @@ namespace AgroBase.Services
private void DefinirDispositivo()
{
if (!SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Can))
if (!IsConnected || !Iniciado)
return;
SerialService.AdicionarOuAtualizarDispositivoMapeado(new DispositivoDetalhesModel
{
if (IsConnected && Iniciado)
{
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
{
Dispositivo = T_Code.Can,
Endereco = IpAddress,
Versao = ("1").ToString()
});
}
}
Dispositivo = T_Code.Can,
Endereco = IpAddress,
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,7 +709,7 @@ namespace AgroBase.Services
catch (Exception ex)
{
MostrarLog($"Erro RX: {ex.Message}");
IsConnected = false;
MarcarTransporteIndisponivel();
}
await Task.CompletedTask;
@ -703,54 +717,71 @@ namespace AgroBase.Services
private void OuvirTcp()
{
if (_tcpClient == null || _tcpStream == null || !_tcpClient.Connected)
lock (_socketLock)
{
IsConnected = false;
return;
}
if (_tcpClient == null || _tcpStream == null || !_tcpClient.Connected)
{
MarcarTransporteIndisponivel();
return;
}
while (_tcpStream.DataAvailable)
{
byte[] buffer = new byte[4096];
int lidos = _tcpStream.Read(buffer, 0, buffer.Length);
if (lidos <= 0)
break;
while (_tcpStream.DataAvailable)
{
byte[] buffer = new byte[4096];
for (int i = 0; i < lidos; i++)
TcpRxBuffer.Add(buffer[i]);
}
int lidos = _tcpStream.Read(buffer, 0, buffer.Length);
while (TcpRxBuffer.Count >= USR_FRAME_SIZE)
{
byte[] packet = TcpRxBuffer.Take(USR_FRAME_SIZE).ToArray();
TcpRxBuffer.RemoveRange(0, USR_FRAME_SIZE);
ProcessarPacoteUsrStandard(packet);
if (lidos <= 0)
break;
for (int i = 0; i < lidos; i++)
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()
{
if (_udpClient == null)
lock (_socketLock)
{
IsConnected = false;
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)
if (_udpClient == null)
{
byte[] frame = new byte[USR_FRAME_SIZE];
Buffer.BlockCopy(pacote, offset, frame, 0, USR_FRAME_SIZE);
ProcessarPacoteUsrStandard(frame);
offset += USR_FRAME_SIZE;
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;
}
}
}
}
@ -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();

View File

@ -445,14 +445,14 @@ namespace AgroBase.Services
if (leitura == null)
continue;
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
SerialService.AdicionarOuAtualizarDispositivoMapeado(new DispositivoDetalhesModel()
{
Dispositivo = T_Code.Dir,
Endereco = CanManager.CanService._portName,
Versao = (leitura.FV ?? "").ToString(),
Mod_ID = modId,
});
op.Sensoriamento.Operacao.ModsCalibragem.TryGetValue(T_Code.Dir, out var m);
if (m == null)

View File

@ -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,

View File

@ -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,
@ -1273,13 +1255,13 @@ namespace AgroBase.Services.Operadores
pressao_desejada_fisica = bomba.UltimaPressaoDesejada,
ultimo_comando_ms = idadeUltimoComandoBombaMs,
ultima_resposta_ms = idadeUltimaRespostaBombaMs,
telemetria_pos_comando =
bomba.UltimoComandoEnviado != DateTime.MinValue &&
bomba.UltimaLeitura != DateTime.MinValue &&
bomba.UltimaLeitura >= bomba.UltimoComandoEnviado,
telemetria_pos_comando =
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;

View File

@ -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;

View File

@ -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,
"estado_on": estado_on,
"usa_servo": usa_servo,
"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),
}
# ============================================================
# Telemetria velha
#
# A avaliação de resposta do componente já diagnosticará isso.
# Não devemos transformar uma leitura antiga em falha mecânica.
# ============================================================
if comando and not estado_on:
severidade = 85 if tem_idade_comando and ultimo_comando_ms >= self.COMANDO_FALHA_MS else 70
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",
"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)
if not telemetria_fresca:
self._atualizar_persistencia(
runtime,
"comandado_on_leitura_off_desde",
False,
agora,
)
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})")
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",
"acoes": [
"Verificar travamento da solenoide.",
"Verificar se o comando de desligamento chegou ao ATU.",
],
})
self._atualizar_persistencia(
runtime,
"angulo_divergente_desde",
False,
agora,
)
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):
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",
"acoes": [
"Verificar servo/posição mecânica do bico.",
"Conferir neutro e ângulo de abertura configurados.",
],
})
score = min(score, 78)
divergencia_ligar = (
comando and not estado_on
)
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_atuar": comando,
"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": 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
@ -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):
"""

View File

@ -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 = {
"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"]
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
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
runtime["coerencia_desde"] = None
# Não existe evidência de que o estado desejado foi enviado.
if not possui_comando:
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 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:
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",
"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"
)
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.",
@ -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):

View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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,21 +521,33 @@ void setup() {
sLED_Manual.Inicializar();
sLED_Automatico.Inicializar();
sLED_Operacao.Inicializar();
sNTC_MotorET.Inicializar();
sNTC_MotorDT.Inicializar();
sNTC_MotorEF.Inicializar();
sNTC_MotorDF.Inicializar();
sCOR_3V0.Inicializar();
sCOR_5V0.Inicializar();
sCOR_7V2.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();
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();
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_Automatico);
@ -439,9 +630,33 @@ void enviarDadosSensores(uint8_t id_num, CanMessagePosicaoDados posicao) {
bool todosIDs = id_num == canService.ID_Num_sTOD;
if (id_num == canService.ID_Num_sMOD) {
EnviarDadosCAN(canService.MontarFrameReqStatusMod(D_Code, cfg.conectado, VERSION));
if (PublicacaoMutex != nullptr) xSemaphoreGive(PublicacaoMutex);
return;
switch (posicao) {
case CanMessagePosicaoDados::Status:
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)