robustez nas métricas de saúde dos módulos Mov, Dir, Sen e Atu
This commit is contained in:
parent
2d6300d621
commit
97c9a49f3e
|
|
@ -475,7 +475,7 @@ namespace AgroBase.Forms.IHM
|
|||
|
||||
await EnviarComandoBico(B01, true, 3000, Testes[idx].ValorPadrao, Dispositivo.Dados);
|
||||
|
||||
double Fluxo = FluxoInicializado ? Dispositivo.Dados.VazaoFluxoMLpSInstantanea : -1;
|
||||
double Fluxo = FluxoInicializado ? Dispositivo.Dados.VazaoFluxoMLpSInstantaneaCorrigida : -1;
|
||||
idx = PreencheDadosLinha(grid, Testes, idx, Fluxo);
|
||||
|
||||
bool statusB01_1 = B01Inicializado && B01._EstadoLeitura == Estado.Ligado;
|
||||
|
|
@ -550,7 +550,7 @@ namespace AgroBase.Forms.IHM
|
|||
{
|
||||
await Task.Delay(100);
|
||||
|
||||
if (disp != null && disp.VazaoFluxoMLpSInstantanea >= ValorComparar)
|
||||
if (disp != null && disp.VazaoFluxoMLpSInstantaneaCorrigida >= ValorComparar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,13 +32,13 @@ namespace AgroBase.Forms
|
|||
switch (comando.Dispositivo)
|
||||
{
|
||||
case T_Code.Mov:
|
||||
GeneralJoystick.ProcessarDadosControle(comando.botaoJoy, false, key: comando.key, valorDesejado: Variaveis.OperacaoEmAndamento.Parametros.Controle.MovVelocidadeCErvasPercent);
|
||||
GeneralJoystick.ProcessarDadosControle(comando.botaoJoy, false, key: comando.key, valorDesejado: Variaveis.OperacaoEmAndamento.Parametros.Controle.MovVelocidadeCErvasPercent, ForcarComando: true);
|
||||
break;
|
||||
case T_Code.Dir:
|
||||
GeneralJoystick.ProcessarDadosControle(comando.botaoJoy, false, key: comando.key, valorDesejado: Variaveis.OperacaoEmAndamento.Parametros.Controle.DirAnguloMaximo);
|
||||
GeneralJoystick.ProcessarDadosControle(comando.botaoJoy, false, key: comando.key, valorDesejado: Variaveis.OperacaoEmAndamento.Parametros.Controle.DirAnguloMaximo, ForcarComando: true);
|
||||
break;
|
||||
case T_Code.Atu:
|
||||
GeneralJoystick.ProcessarDadosControle(comando.botaoJoy, false, key: comando.key);
|
||||
GeneralJoystick.ProcessarDadosControle(comando.botaoJoy, false, key: comando.key, ForcarComando: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ namespace AgroBase.Forms
|
|||
{
|
||||
var comando = GeneralJoystick.DeParaComandos.FirstOrDefault(x => x.key == e.KeyCode);
|
||||
if (comando == null) return;
|
||||
GeneralJoystick.ProcessarDadosControle(comando.botaoJoy, true, key: comando.key);
|
||||
GeneralJoystick.ProcessarDadosControle(comando.botaoJoy, true, key: comando.key, ForcarComando: true);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Windows.Forms;
|
||||
using System;
|
||||
using static AgroBase.Models.Enums;
|
||||
using AgroBase.Forms;
|
||||
using AgroBase.Forms;
|
||||
using AgroBase.Models;
|
||||
using System.Threading.Tasks;
|
||||
using AgroBase.Models.Modules;
|
||||
using SharpDX.DirectInput;
|
||||
using AgroBase.Services;
|
||||
using SharpDX.DirectInput;
|
||||
using StackExchange.Redis;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Forms;
|
||||
using static AgroBase.Models.Enums;
|
||||
|
||||
namespace AgroBase
|
||||
{
|
||||
|
|
@ -927,6 +928,8 @@ namespace AgroBase
|
|||
{
|
||||
Sinaleiro.Comportamento = (SensorSinaleiroComportamentoModel)Status;
|
||||
(id_num, posicao, data) = Sinaleiro.ProtocoloComando();
|
||||
Sinaleiro.UltimoComandoEnviado = DateTime.Now;
|
||||
Sinaleiro.UltimoComportamentoDesejado = (SensorSinaleiroComportamentoModel)Status;
|
||||
}
|
||||
break;
|
||||
case S_Code.sRLE:
|
||||
|
|
@ -935,6 +938,8 @@ namespace AgroBase
|
|||
{
|
||||
Rele.StatusControle = (Estado)Status;
|
||||
(id_num, posicao, data) = Rele.ProtocoloComando();
|
||||
Rele.UltimoComandoEnviado = DateTime.Now;
|
||||
Rele.UltimoEstadoDesejado = (Estado)Status;
|
||||
}
|
||||
break;
|
||||
case S_Code.sSRV:
|
||||
|
|
@ -943,6 +948,8 @@ namespace AgroBase
|
|||
{
|
||||
ServoFreio._AnguloControle = (int)Status;
|
||||
(id_num, posicao, data) = ServoFreio.ProtocoloComando();
|
||||
ServoFreio.UltimoComandoEnviado = DateTime.Now;
|
||||
ServoFreio.UltimoAnguloDesejado = (int)Status;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -121,10 +121,14 @@ namespace AgroBase.Models.Modules
|
|||
[JsonIgnore]
|
||||
private SensorModel SensorFluxoLinha => Sensores.FirstOrDefault(x => x.Componente == S_Code.sFLX && x.ID == "FLXLN");
|
||||
|
||||
const double FatorCalibracaoFluxo = 0.776;
|
||||
|
||||
public double VazaoMaximaSensor_LMin { get; set; } = 20.0;
|
||||
|
||||
public double VazaoFluxoMLpSInstantanea => SensorFluxoLinha?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Fluxo)?.atual?.valor ?? 0;
|
||||
|
||||
public double VazaoFluxoMLpSInstantaneaCorrigida => VazaoFluxoMLpSInstantanea * FatorCalibracaoFluxo;
|
||||
|
||||
public double VazaoFluxoMLpSInstantaneaCalc
|
||||
{
|
||||
get
|
||||
|
|
@ -140,8 +144,6 @@ namespace AgroBase.Models.Modules
|
|||
if (pulsos <= 0 || periodo <= 0)
|
||||
return 0.0;
|
||||
|
||||
const double FatorCalibracaoFluxo = 0.776;
|
||||
|
||||
double frequencia = (double)pulsos / periodo * 1000.0;
|
||||
|
||||
double q_L_min = (frequencia + 3.0) / 8.1;
|
||||
|
|
@ -978,9 +980,9 @@ namespace AgroBase.Models.Modules
|
|||
posicao = CanMessagePosicaoDados.Dados1,
|
||||
indice = 1,
|
||||
analise_health = true,
|
||||
maximo = 35,
|
||||
minimo = 0,
|
||||
nominal = 30,
|
||||
maximo = VariaveisEquipamento.CapacidadeReservatorio + 10.0,
|
||||
minimo = VariaveisEquipamento.PercentualReservatorioMinCritio,
|
||||
nominal = VariaveisEquipamento.CapacidadeReservatorio,
|
||||
unidade_medida = "Kg"
|
||||
}
|
||||
}
|
||||
|
|
@ -2049,6 +2051,9 @@ namespace AgroBase.Models.Modules
|
|||
};
|
||||
|
||||
dispAtu.AdicionarMensagemFila(bico.ID_Num, CanMessagePosicaoDados.Command1, CanMessagePosicaoDados.Command1, data, false);
|
||||
bico.UltimoComandoEnviado = DateTime.Now;
|
||||
bico.UltimoStatusDesejado = status;
|
||||
bico.UltimoAnguloDesejado = angulo;
|
||||
}
|
||||
|
||||
private void EnviarComandoFisicoBomba(AtuadorBombaModel bomba, bool status, double potencia, double pressao)
|
||||
|
|
@ -2074,6 +2079,11 @@ namespace AgroBase.Models.Modules
|
|||
};
|
||||
|
||||
dispAtu.AdicionarMensagemFila(bomba.ID_Num, CanMessagePosicaoDados.Command1, CanMessagePosicaoDados.Command1, data, false);
|
||||
|
||||
bomba.UltimoComandoEnviado = DateTime.Now;
|
||||
bomba.UltimoStatusDesejado = status;
|
||||
bomba.UltimaPotenciaDesejada = potencia;
|
||||
bomba.UltimaPressaoDesejada = pressao;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -2295,6 +2305,9 @@ namespace AgroBase.Models.Modules
|
|||
public Estado _EstadoLeitura => (Estado)(ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.EstadoLeitura)?.atual?.valor ?? 0);
|
||||
public double _AnguloBicoLeitura => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.ServoAnguloLeitura)?.atual? .valor ?? 0;
|
||||
|
||||
public DateTime UltimoComandoEnviado { get; set; } = DateTime.MinValue;
|
||||
public bool UltimoStatusDesejado { get; set; } = false;
|
||||
public double UltimoAnguloDesejado { get; set; } = 0;
|
||||
|
||||
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
||||
{
|
||||
|
|
@ -2529,6 +2542,11 @@ namespace AgroBase.Models.Modules
|
|||
public double PotenciaMediaAtiva { get; set; }
|
||||
|
||||
|
||||
public DateTime UltimoComandoEnviado { get; set; } = DateTime.MinValue;
|
||||
public bool UltimoStatusDesejado { get; set; } = false;
|
||||
public double UltimaPotenciaDesejada { get; set; } = 0;
|
||||
public double UltimaPressaoDesejada { get; set; } = 0;
|
||||
|
||||
|
||||
|
||||
public List<(CanMessagePosicaoDados, byte[])> ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
||||
|
|
|
|||
|
|
@ -266,6 +266,13 @@ namespace AgroBase.Models.Modules
|
|||
public MvdServoFreioModel ConfigFreio { get; set; }
|
||||
|
||||
|
||||
public DateTime UltimoComandoEnviado { get; set; } = DateTime.MinValue;
|
||||
public OidFuncCode UltimoModoControleEnviado { get; set; }
|
||||
public double UltimoSetPointEnviado { get; set; } = 0;
|
||||
public Direcao UltimaDirecao { get; set; } = Direcao.Parado;
|
||||
public bool UltimoFreioSP { get; set; } = false;
|
||||
|
||||
|
||||
public static MovimentacaoModel CarregarParametrosIniciais(string Mod_ID, byte _EnderecoTx, byte _EnderecoRx)
|
||||
{
|
||||
return new MovimentacaoModel()
|
||||
|
|
@ -450,6 +457,11 @@ namespace AgroBase.Models.Modules
|
|||
OIDCanService.ComandoControle(_EnderecoCAN_Tx, _EnderecoCAN_Rx, modoControle, setPoint);
|
||||
|
||||
LeituraRPM = RPM_SP;
|
||||
UltimoComandoEnviado = DateTime.Now;
|
||||
UltimoModoControleEnviado = modoControle;
|
||||
UltimoSetPointEnviado = setPoint;
|
||||
UltimaDirecao = DirecaoAtual;
|
||||
UltimoFreioSP = Freado;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -238,7 +238,6 @@ namespace AgroBase.Models.Modules
|
|||
|
||||
private DateTime _ultimoEnvioControleMov = DateTime.MinValue;
|
||||
private DateTime _ultimoRegistroLogMov = DateTime.MinValue;
|
||||
|
||||
private static readonly TimeSpan IntervaloControleMov = TimeSpan.FromSeconds(1);
|
||||
private static readonly TimeSpan IntervaloLogMov = TimeSpan.FromSeconds(1);
|
||||
private async Task tmrRegistradorMOV_Tick()
|
||||
|
|
@ -340,10 +339,8 @@ namespace AgroBase.Models.Modules
|
|||
|
||||
private DateTime _ultimoEnvioControleDir = DateTime.MinValue;
|
||||
private DateTime _ultimoSensoresExternosDir = DateTime.MinValue;
|
||||
|
||||
private static readonly TimeSpan IntervaloControleDir = TimeSpan.FromMilliseconds(250);
|
||||
private static readonly TimeSpan IntervaloSensoresExternosDir = TimeSpan.FromMilliseconds(500);
|
||||
|
||||
private async Task tmrRegistradorDIR_Tick()
|
||||
{
|
||||
if (Variaveis.Fechando)
|
||||
|
|
@ -351,11 +348,9 @@ namespace AgroBase.Models.Modules
|
|||
|
||||
DateTime agora = DateTime.Now;
|
||||
|
||||
if (!MKS057DCanService.Iniciado &&
|
||||
SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Dir))
|
||||
if (!MKS057DCanService.Iniciado && SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Dir))
|
||||
{
|
||||
foreach (var leitura in MKS057DCanService.DadosLeitura
|
||||
.Where(x => x.UltimoComandoRecebido.AddSeconds(10) < agora))
|
||||
foreach (var leitura in MKS057DCanService.DadosLeitura.Where(x => x.UltimoComandoRecebido.AddSeconds(10) < agora))
|
||||
{
|
||||
/*
|
||||
* Poke leve para tentar recuperar leitura.
|
||||
|
|
|
|||
|
|
@ -1818,6 +1818,7 @@ namespace AgroBase.Models.Modules
|
|||
},
|
||||
AnguloNeutro = 0,
|
||||
_AnguloControle = 0,
|
||||
UltimoAnguloDesejado = 0,
|
||||
Incremental = true,
|
||||
ValoresLeituras = new List<SensorValoresLeituraModel>()
|
||||
{
|
||||
|
|
@ -1850,6 +1851,7 @@ namespace AgroBase.Models.Modules
|
|||
},
|
||||
AnguloNeutro = 0,
|
||||
_AnguloControle = 0,
|
||||
UltimoAnguloDesejado = 0,
|
||||
Incremental = true,
|
||||
ValoresLeituras = new List<SensorValoresLeituraModel>()
|
||||
{
|
||||
|
|
@ -1882,6 +1884,7 @@ namespace AgroBase.Models.Modules
|
|||
},
|
||||
AnguloNeutro = 180,
|
||||
_AnguloControle = 180,
|
||||
UltimoAnguloDesejado = 180,
|
||||
Incremental = false,
|
||||
ValoresLeituras = new List<SensorValoresLeituraModel>()
|
||||
{
|
||||
|
|
@ -1914,6 +1917,7 @@ namespace AgroBase.Models.Modules
|
|||
},
|
||||
AnguloNeutro = 180,
|
||||
_AnguloControle = 180,
|
||||
UltimoAnguloDesejado = 180,
|
||||
Incremental = false,
|
||||
ValoresLeituras = new List<SensorValoresLeituraModel>()
|
||||
{
|
||||
|
|
@ -2487,6 +2491,10 @@ namespace AgroBase.Models.Modules
|
|||
public int _StatusLedId => (int)(ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.StatusLedID)?.atual?.valor ?? 99);
|
||||
public StatusLED _StatusLed => (StatusLED)(ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.StatusLedLeitura)?.atual?.valor ?? StatusLED.Apagado);
|
||||
|
||||
|
||||
public DateTime UltimoComandoEnviado { get; set; } = DateTime.MinValue;
|
||||
public SensorSinaleiroComportamentoModel UltimoComportamentoDesejado { get; set; }
|
||||
|
||||
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
||||
{
|
||||
var pino = pinout.Pinos.FirstOrDefault(p => p.ComponenteID == ID);
|
||||
|
|
@ -2547,6 +2555,10 @@ namespace AgroBase.Models.Modules
|
|||
public Estado _EstadoLeitura => ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.EstadoLeitura)?.atual?.valor ?? Estado.Desligado;
|
||||
|
||||
|
||||
public DateTime UltimoComandoEnviado { get; set; } = DateTime.MinValue;
|
||||
public Estado UltimoEstadoDesejado { get; set; } = Estado.Desligado;
|
||||
|
||||
|
||||
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
||||
{
|
||||
var pino = pinout.Pinos.FirstOrDefault(p => p.ComponenteID == ID);
|
||||
|
|
@ -2607,6 +2619,9 @@ namespace AgroBase.Models.Modules
|
|||
}
|
||||
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; }
|
||||
|
||||
public DateTime UltimoComandoEnviado { get; set; } = DateTime.MinValue;
|
||||
public double UltimoAnguloDesejado { get; set; } = 0;
|
||||
|
||||
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
|
||||
{
|
||||
var pino = pinout.Pinos.FirstOrDefault(p => p.ComponenteID == ID);
|
||||
|
|
|
|||
|
|
@ -3038,7 +3038,7 @@ namespace AgroBase.Models
|
|||
PressaoLinha = Variaveis.OperacaoEmAndamento.DispAtu.Dados.PressaoLinha,
|
||||
PotenciaBomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora?._Potencia ?? 0,
|
||||
BombaLigada = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora?.ComandoAtuar ?? false,
|
||||
VazaoFluxoMLpSInstantanea = Variaveis.OperacaoEmAndamento.DispAtu.Dados.VazaoFluxoMLpSInstantanea,
|
||||
VazaoFluxoMLpSInstantanea = Variaveis.OperacaoEmAndamento.DispAtu.Dados.VazaoFluxoMLpSInstantaneaCorrigida,
|
||||
VazaoFluxoMLpSMedia = Variaveis.OperacaoEmAndamento.DispAtu.Dados.VazaoMediaMLs,
|
||||
VolumeVazaoML = Variaveis.OperacaoEmAndamento.DispAtu.Dados.VolumeVazadoML,
|
||||
QuantidadeBicos = Variaveis.OperacaoEmAndamento.Parametros.QtdBicos ?? VariaveisEquipamento.QuantidadeBicosPulverizadores,
|
||||
|
|
@ -3665,19 +3665,26 @@ namespace AgroBase.Models
|
|||
|
||||
public void InserirLog(T_Code dispositivo, StatusModulo status, double saude, string mensagem, string id = null, string condicoes = null)
|
||||
{
|
||||
if (Logs == null)
|
||||
Logs = new List<OperacaoSensoriamentoLogErrosModel>();
|
||||
|
||||
Logs.Add(new OperacaoSensoriamentoLogErrosModel()
|
||||
try
|
||||
{
|
||||
Momento = DateTime.Now,
|
||||
Dispositivo = dispositivo,
|
||||
ID = id,
|
||||
Status = status,
|
||||
Saude = saude,
|
||||
Mensagem = mensagem,
|
||||
Condicoes = condicoes
|
||||
});
|
||||
if (Logs == null)
|
||||
Logs = new List<OperacaoSensoriamentoLogErrosModel>();
|
||||
|
||||
Logs.Add(new OperacaoSensoriamentoLogErrosModel()
|
||||
{
|
||||
Momento = DateTime.Now,
|
||||
Dispositivo = dispositivo,
|
||||
ID = id,
|
||||
Status = status,
|
||||
Saude = saude,
|
||||
Mensagem = mensagem,
|
||||
Condicoes = condicoes
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Variaveis.MostrarLog($"[OperacaoModel.InserirLog] Erro ao inserir log: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -3736,7 +3743,7 @@ namespace AgroBase.Models
|
|||
MassaReservatorio = _Reservatorio.MassaAtual_Kg,
|
||||
PressaoLinha = _DispAtu.Dados.PressaoLinha,
|
||||
PressaoLinhaCalc = _DispAtu.Dados.PressaoLinhaCalc,
|
||||
VazaoInstantanea = _DispAtu.Dados.VazaoFluxoMLpSInstantanea,
|
||||
VazaoInstantanea = _DispAtu.Dados.VazaoFluxoMLpSInstantaneaCorrigida,
|
||||
VazaoInstantaneaCalc = _DispAtu.Dados.VazaoFluxoMLpSInstantaneaCalc,
|
||||
VazaoMedia = _DispAtu.Dados.VazaoMediaMLs,
|
||||
TempoBombaAtuada = _DispAtu.Dados.TempoAtuado,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -112,7 +112,7 @@ namespace AgroBase.Services
|
|||
Parametro = OidParametros.VelocidadeAtual,
|
||||
RedisKey = "rpm",
|
||||
FreqBaseHz = 1.0,
|
||||
FreqMinHz = 0.45,
|
||||
FreqMinHz = 0.35,
|
||||
EmUsoHealth = true
|
||||
}
|
||||
},
|
||||
|
|
@ -123,7 +123,7 @@ namespace AgroBase.Services
|
|||
Parametro = OidParametros.CorrenteMotor,
|
||||
RedisKey = "corrente_motor",
|
||||
FreqBaseHz = 1.0,
|
||||
FreqMinHz = 0.45,
|
||||
FreqMinHz = 0.35,
|
||||
EmUsoHealth = true
|
||||
}
|
||||
},
|
||||
|
|
@ -134,7 +134,7 @@ namespace AgroBase.Services
|
|||
Parametro = OidParametros.CorrenteBarramento,
|
||||
RedisKey = "corrente_barramento",
|
||||
FreqBaseHz = 1.0,
|
||||
FreqMinHz = 0.45,
|
||||
FreqMinHz = 0.35,
|
||||
EmUsoHealth = true
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
using AgroBase.Models;
|
||||
using AgroBase.Models.Operadores;
|
||||
using Newtonsoft.Json;
|
||||
using OpenHardwareMonitor.Hardware;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
|
@ -417,6 +418,7 @@ namespace AgroBase.Services.Operadores
|
|||
if (Variaveis.OperacaoEmAndamento.DispSen == null) return;
|
||||
|
||||
var _Sensoriamento = Variaveis.OperacaoEmAndamento.Sensoriamento;
|
||||
var agora_dt = DateTime.Now;
|
||||
var agora = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
|
||||
|
||||
//Console.WriteLine(RedisService.Get(RedisService.ModKey(Enums.T_Code.Gps)));
|
||||
|
|
@ -431,27 +433,90 @@ namespace AgroBase.Services.Operadores
|
|||
|
||||
var dirAtivos = new List<int>();
|
||||
var movAtivos = new List<int>();
|
||||
|
||||
foreach (var mod in Variaveis.OperacaoEmAndamento.DispMvd.Dados.Modulos)
|
||||
{
|
||||
double erroAnguloDir = Math.Abs(mod.DirMotor.AnguloLeitura - mod.DirMotor.Angulo_SP);
|
||||
|
||||
double idadeUltimoComandoMs =
|
||||
mod.DirMotor.UltimoComandoEnviado == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - mod.DirMotor.UltimoComandoEnviado).TotalMilliseconds;
|
||||
|
||||
double idadeUltimaRespostaMs =
|
||||
mod.DirMotor.Leitura?.UltimoComandoRecebido == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - (mod.DirMotor.Leitura?.UltimoComandoRecebido ?? DateTime.MinValue)).TotalMilliseconds;
|
||||
|
||||
int endereco_dir = mod.DirMotor._EnderecoCAN_Tx;
|
||||
RedisService.AtualizarCampos(
|
||||
RedisService.ModKey(Enums.T_Code.Dir),
|
||||
var camposDir = new List<(string campo, object valor)>
|
||||
{
|
||||
($"{endereco_dir}.timestamp", agora),
|
||||
($"{endereco_dir}.conectado", mod.DirMotor.Inicializado),
|
||||
|
||||
// legado
|
||||
($"{endereco_dir}.freq_base", Variaveis.OperacaoEmAndamento.DispMvd.Dados.tmrRegistradorDIR.IntervalHz),
|
||||
|
||||
($"{endereco_dir}.endereco", mod.DirMotor._EnderecoCAN_Tx),
|
||||
($"{endereco_dir}.mod_id", mod.Modulo_ID),
|
||||
|
||||
($"{endereco_dir}.angulo", mod.DirMotor.AnguloLeitura),
|
||||
($"{endereco_dir}.angulo_sp", mod.DirMotor.Angulo_SP),
|
||||
($"{endereco_dir}.comandar", mod.DirMotor.Comandar),
|
||||
($"{endereco_dir}.referenciado", mod.DirMotor.Referenciado),
|
||||
|
||||
($"{endereco_dir}.in1_atuado", mod.DirMotor.IN1_Atuado),
|
||||
($"{endereco_dir}.sentido", mod.DirMotor.Sentido),
|
||||
($"{endereco_dir}.sentido_sp", mod.DirMotor.Sentido_SP),
|
||||
($"{endereco_dir}.sentido_real", mod.DirMotor.SentidoReal),
|
||||
($"{endereco_dir}.freq.pulsos", mod.DirMotor.Leitura?.NumeroPulsos?.frequencia ?? 0),
|
||||
($"{endereco_dir}.freq.io", mod.DirMotor.Leitura?.EntradaIN1?.frequencia ?? 0)
|
||||
);
|
||||
|
||||
($"{endereco_dir}.erro_angulo", erroAnguloDir),
|
||||
($"{endereco_dir}.atingiu_angulo_sp", mod.DirMotor.AtingiuAnguloSP),
|
||||
($"{endereco_dir}.ultimo_comando_ms", idadeUltimoComandoMs),
|
||||
($"{endereco_dir}.ultima_resposta_ms", idadeUltimaRespostaMs),
|
||||
($"{endereco_dir}.ultima_direcao", mod.DirMotor.UltimaDirecao),
|
||||
($"{endereco_dir}.status_movimento", mod.DirMotor.Leitura?.StatusMovimento),
|
||||
($"{endereco_dir}.rpm", mod.DirMotor.RPM),
|
||||
($"{endereco_dir}.em_movimento", mod.DirMotor.EmMovimento),
|
||||
($"{endereco_dir}.retornando_a_zero", mod.DirMotor.RetornandoAzero),
|
||||
|
||||
// frequências reais medidas
|
||||
($"{endereco_dir}.freq.pulsosrecebidos", mod.DirMotor.Leitura?.NumeroPulsos?.frequencia ?? 0),
|
||||
($"{endereco_dir}.freq.io", mod.DirMotor.Leitura?.EntradaIN1?.frequencia ?? 0),
|
||||
($"{endereco_dir}.freq.rpm", mod.DirMotor.Leitura?.RPM?.frequencia ?? 0),
|
||||
|
||||
// mantém disponíveis para diagnóstico futuro, mesmo que não estejam no polling normal
|
||||
($"{endereco_dir}.freq.erro", 0),
|
||||
($"{endereco_dir}.freq.encoder_carregado", 0),
|
||||
($"{endereco_dir}.freq.encoder_acumulado", 0),
|
||||
($"{endereco_dir}.freq.versao", 0),
|
||||
};
|
||||
|
||||
camposDir.AddRange(MKS057DCanService.GerarCamposRedisHealthBase(endereco_dir));
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Dir), camposDir.ToArray());
|
||||
|
||||
|
||||
double erroRpmMov = Math.Abs(mod.MovMotor.RPM_Roda - mod.MovMotor.RPM_SP_Leitura);
|
||||
|
||||
double erroRpmPctMov =
|
||||
Math.Abs(mod.MovMotor.RPM_SP_Leitura) > 1
|
||||
? erroRpmMov / Math.Abs(mod.MovMotor.RPM_SP_Leitura)
|
||||
: 0;
|
||||
|
||||
bool atingiuRpmSp =
|
||||
Math.Abs(mod.MovMotor.RPM_SP_Leitura) < 25
|
||||
? Math.Abs(mod.MovMotor.RPM_Roda) < 35
|
||||
: erroRpmPctMov <= 0.45 || erroRpmMov <= 45;
|
||||
|
||||
double idadeUltimoComandoMovMs =
|
||||
mod.MovMotor.UltimoComandoEnviado == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - mod.MovMotor.UltimoComandoEnviado).TotalMilliseconds;
|
||||
|
||||
double idadeUltimaRespostaMovMs =
|
||||
mod.MovMotor.Leitura?.UltimoComandoRecebido == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - (mod.MovMotor.Leitura?.UltimoComandoRecebido ?? DateTime.MinValue)).TotalMilliseconds;
|
||||
|
||||
int endereco_mov = mod.MovMotor._EnderecoCAN_Tx;
|
||||
var camposMov = new List<(string campo, object valor)>
|
||||
|
|
@ -460,10 +525,7 @@ namespace AgroBase.Services.Operadores
|
|||
($"{endereco_mov}.conectado", mod.MovMotor.Inicializado),
|
||||
|
||||
// legado
|
||||
($"{endereco_mov}.freq_base",
|
||||
OIDCanService.ObterFreqBaseHz(
|
||||
OIDCanService.OidHandler.OidParametros.VelocidadeAtual
|
||||
)),
|
||||
($"{endereco_mov}.freq_base", OIDCanService.ObterFreqBaseHz(OIDCanService.OidHandler.OidParametros.VelocidadeAtual)),
|
||||
|
||||
($"{endereco_mov}.endereco", mod.MovMotor._EnderecoCAN_Tx),
|
||||
($"{endereco_mov}.mod_id", mod.Modulo_ID),
|
||||
|
|
@ -487,26 +549,60 @@ namespace AgroBase.Services.Operadores
|
|||
|
||||
($"{endereco_mov}.freq.ciclo_trabalho", mod.MovMotor.Leitura?.CicloTrabalho?.frequencia ?? 0),
|
||||
($"{endereco_mov}.freq.potencia", mod.MovMotor.Leitura?.Potencia?.frequencia ?? 0),
|
||||
|
||||
|
||||
($"{endereco_mov}.comandar", mod.MovMotor.Comandar),
|
||||
($"{endereco_mov}.comando_ligado", mod.MovMotor.ComandoLigado),
|
||||
($"{endereco_mov}.leitura_ligado", mod.MovMotor.LeituraLigado),
|
||||
($"{endereco_mov}.alarme_motor", mod.MovMotor.AlarmeMotor),
|
||||
($"{endereco_mov}.alarme_comando", mod.MovMotor.AlarmeComando),
|
||||
($"{endereco_mov}.alarme_sensores", mod.MovMotor.AlarmeSensores),
|
||||
|
||||
($"{endereco_mov}.sentido", mod.MovMotor.Sentido),
|
||||
($"{endereco_mov}.sentido_sp", mod.MovMotor.Sentido_SP),
|
||||
|
||||
($"{endereco_mov}.erro_rpm", erroRpmMov),
|
||||
($"{endereco_mov}.erro_rpm_pct", erroRpmPctMov),
|
||||
($"{endereco_mov}.atingiu_rpm_sp", atingiuRpmSp),
|
||||
|
||||
($"{endereco_mov}.ultimo_comando_ms", idadeUltimoComandoMovMs),
|
||||
($"{endereco_mov}.ultima_resposta_ms", idadeUltimaRespostaMovMs),
|
||||
|
||||
($"{endereco_mov}.freio_sp", mod.MovMotor.UltimoFreioSP),
|
||||
($"{endereco_mov}.modo_controle", mod.MovMotor.Leitura?.ModoControle),
|
||||
($"{endereco_mov}.ultimo_modo_controle", mod.MovMotor.UltimoModoControleEnviado),
|
||||
($"{endereco_mov}.ultimo_setpoint", mod.MovMotor.UltimoSetPointEnviado),
|
||||
};
|
||||
|
||||
camposMov.AddRange(OIDCanService.GerarCamposRedisHealthBase(endereco_mov));
|
||||
RedisService.AtualizarCampos(
|
||||
RedisService.ModKey(Enums.T_Code.Mov),
|
||||
camposMov.ToArray()
|
||||
);
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Mov), camposMov.ToArray());
|
||||
|
||||
dirAtivos.Add(endereco_dir);
|
||||
movAtivos.Add(endereco_mov);
|
||||
}
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Dir), ($"ativos", dirAtivos.ToList()));
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Mov), ($"ativos", movAtivos.ToList()));
|
||||
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Dir), ("ativos", dirAtivos.ToList()));
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Mov), ("ativos", movAtivos.ToList()));
|
||||
|
||||
|
||||
|
||||
var DadosSen = Variaveis.OperacaoEmAndamento.DispSen.Dados;
|
||||
//Console.WriteLine("[HEALTH SEN] " + DadosSen.Conectado);
|
||||
DateTime ultimaRespostaSen =
|
||||
DadosSen.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
||||
|
||||
double idadeUltimaRespostaSenMs =
|
||||
ultimaRespostaSen == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - ultimaRespostaSen).TotalMilliseconds;
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen),
|
||||
("timestamp", agora),
|
||||
("conectado", DadosSen.Conectado),
|
||||
("freq_base", DadosSen.FrequenciaBase),
|
||||
("bms_ligado", _Sensoriamento.Bateria?.BmsLigado ?? false)
|
||||
("bms_ligado", _Sensoriamento.Bateria?.BmsLigado ?? false),
|
||||
|
||||
("ultima_resposta_ms", idadeUltimaRespostaSenMs),
|
||||
("latencia_loop_ms", DadosSen.DadosLeitura?.LatenciaLoop ?? 0),
|
||||
("percentual_bateria", _Sensoriamento.Bateria?.PercentualBateria ?? 0)
|
||||
);
|
||||
var senSensoresAtualizados = new List<(string caminho, object valor)>();
|
||||
var senSensoresAtivos = new List<string>();
|
||||
|
|
@ -527,10 +623,16 @@ namespace AgroBase.Services.Operadores
|
|||
maximo = y.maximo,
|
||||
minimo = y.minimo,
|
||||
atual = y.atual.valor,
|
||||
freq = y.atual.frequencia
|
||||
freq = y.atual.frequencia,
|
||||
unidade = y.unidade_medida,
|
||||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimaRespostaSensorMs =
|
||||
sensor.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - sensor.UltimaLeitura).TotalMilliseconds;
|
||||
|
||||
var sensorObj = new
|
||||
{
|
||||
conectado = sensor.Inicializado,
|
||||
|
|
@ -541,7 +643,9 @@ namespace AgroBase.Services.Operadores
|
|||
mandatorio = sensor.Mandatorio,
|
||||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { }
|
||||
saude = saude ?? new { },
|
||||
|
||||
ultima_resposta_ms = idadeUltimaRespostaSensorMs,
|
||||
};
|
||||
|
||||
senSensoresAtivos.Add($"{tipo}.{id}");
|
||||
|
|
@ -570,6 +674,16 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimoComandoServoMs =
|
||||
servo.UltimoComandoEnviado == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - servo.UltimoComandoEnviado).TotalMilliseconds;
|
||||
|
||||
double idadeUltimaRespostaServoMs =
|
||||
servo.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - servo.UltimaLeitura).TotalMilliseconds;
|
||||
|
||||
var servoObj = new
|
||||
{
|
||||
conectado = servo.Inicializado,
|
||||
|
|
@ -581,7 +695,12 @@ namespace AgroBase.Services.Operadores
|
|||
angulo_sp = servo._AnguloControle,
|
||||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { }
|
||||
saude = saude ?? new { },
|
||||
|
||||
angulo_desejado = servo.UltimoAnguloDesejado,
|
||||
angulo_leitura = servo._AnguloLeiutra,
|
||||
ultimo_comando_ms = idadeUltimoComandoServoMs,
|
||||
ultima_resposta_ms = idadeUltimaRespostaServoMs,
|
||||
};
|
||||
|
||||
senServosAtivos.Add($"{tipo}.{id}");
|
||||
|
|
@ -610,6 +729,16 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimoComandoReleMs =
|
||||
rele.UltimoComandoEnviado == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - rele.UltimoComandoEnviado).TotalMilliseconds;
|
||||
|
||||
double idadeUltimaRespostaReleMs =
|
||||
rele.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - rele.UltimaLeitura).TotalMilliseconds;
|
||||
|
||||
var releObj = new
|
||||
{
|
||||
conectado = rele.Inicializado,
|
||||
|
|
@ -620,7 +749,12 @@ namespace AgroBase.Services.Operadores
|
|||
mandatorio = rele.Mandatorio,
|
||||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { }
|
||||
saude = saude ?? new { },
|
||||
|
||||
estado_desejado = rele.UltimoEstadoDesejado,
|
||||
estado_leitura = rele._EstadoLeitura,
|
||||
ultimo_comando_ms = idadeUltimoComandoReleMs,
|
||||
ultima_resposta_ms = idadeUltimaRespostaReleMs,
|
||||
};
|
||||
|
||||
senRelesAtivos.Add($"{tipo}.{id}");
|
||||
|
|
@ -649,6 +783,16 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimoComandoLedMs =
|
||||
led.UltimoComandoEnviado == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - led.UltimoComandoEnviado).TotalMilliseconds;
|
||||
|
||||
double idadeUltimaRespostaLedMs =
|
||||
led.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - led.UltimaLeitura).TotalMilliseconds;
|
||||
|
||||
var ledObj = new
|
||||
{
|
||||
conectado = led.Inicializado,
|
||||
|
|
@ -659,7 +803,14 @@ namespace AgroBase.Services.Operadores
|
|||
mandatorio = led.Mandatorio,
|
||||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { }
|
||||
saude = saude ?? new { },
|
||||
|
||||
status_led_id_sp = led.UltimoComportamentoDesejado?.id ?? -1,
|
||||
status_led_sp = led.UltimoComportamentoDesejado?.statusLED,
|
||||
status_led_id = led._StatusLedId,
|
||||
status_led_leitura = led._StatusLed,
|
||||
ultimo_comando_ms = idadeUltimoComandoLedMs,
|
||||
ultima_resposta_ms = idadeUltimaRespostaLedMs,
|
||||
};
|
||||
|
||||
senLedsAtivos.Add($"{tipo}.{id}");
|
||||
|
|
@ -669,11 +820,55 @@ namespace AgroBase.Services.Operadores
|
|||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Sen), senLedsAtualizados.ToArray());
|
||||
|
||||
|
||||
|
||||
var DadosAtu = Variaveis.OperacaoEmAndamento.DispAtu.Dados;
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Atu),
|
||||
DateTime ultimaRespostaAtu =
|
||||
DadosAtu.DadosLeitura?.UltimoComandoRespondido ?? DateTime.MinValue;
|
||||
|
||||
double idadeUltimaRespostaAtuMs =
|
||||
ultimaRespostaAtu == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - ultimaRespostaAtu).TotalMilliseconds;
|
||||
|
||||
bool pulverizadorAutomatico =
|
||||
Variaveis.OperacaoEmAndamento.Parametros?.Controle?.PulverizadorAutomatico ?? false;
|
||||
|
||||
double pressaoAlvo =
|
||||
Variaveis.OperacaoEmAndamento.Parametros?.Controle?.AtuPressaoLinha ?? 0;
|
||||
|
||||
double pressaoLinha =
|
||||
DadosAtu.PressaoLinha;
|
||||
|
||||
bool pressaoLinhaRegular =
|
||||
pressaoAlvo <= 0 || pressaoLinha >= pressaoAlvo - 20;
|
||||
RedisService.AtualizarCampos(
|
||||
RedisService.ModKey(Enums.T_Code.Atu),
|
||||
("timestamp", agora),
|
||||
("conectado", DadosAtu.Conectado),
|
||||
("freq_base", DadosAtu.FrequenciaBase)
|
||||
("freq_base", DadosAtu.FrequenciaBase),
|
||||
|
||||
("ultima_resposta_ms", idadeUltimaRespostaAtuMs),
|
||||
("latencia_loop_ms", DadosAtu.DadosLeitura?.LatenciaLoop ?? 0),
|
||||
|
||||
("pulverizador_automatico", pulverizadorAutomatico),
|
||||
("pulverizador_liberado", DadosAtu.ContadorCarga.PulverizadorLiberado),
|
||||
|
||||
("operacao_iniciada", Variaveis.OperacaoEmAndamento.Iniciado),
|
||||
("calibrando", Variaveis.OperacaoEmAndamento.Calibrando),
|
||||
("pausa", Variaveis.OperacaoEmAndamento.Pausa),
|
||||
("emergencia", Variaveis.OperacaoEmAndamento.Emergencia),
|
||||
("finalizando", Variaveis.OperacaoEmAndamento.Finalizando),
|
||||
|
||||
("percentual_reservatorio", DadosAtu.ContadorCarga.PercentualAtual),
|
||||
("pressao_linha", pressaoLinha),
|
||||
("pressao_linha_regular", pressaoLinhaRegular),
|
||||
("vazao_linha_mls", DadosAtu.VazaoFluxoMLpSReferencia),
|
||||
|
||||
("tempo_atuado", DadosAtu.TempoAtuado),
|
||||
("volume_vazado_ml", DadosAtu.VolumeVazadoML),
|
||||
("vazao_media_mls", DadosAtu.VazaoMediaMLs),
|
||||
|
||||
("bomba_cavitada", _Sensoriamento.Atuador?.BombaCavitada ?? false)
|
||||
);
|
||||
var atuSensoresAtualizados = new List<(string caminho, object valor)>();
|
||||
var atuSensoresAtivos = new List<string>();
|
||||
|
|
@ -698,6 +893,11 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimaRespostaSensorMs =
|
||||
sensor.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - sensor.UltimaLeitura).TotalMilliseconds;
|
||||
|
||||
var sensorObj = new
|
||||
{
|
||||
conectado = sensor.Inicializado,
|
||||
|
|
@ -708,7 +908,8 @@ namespace AgroBase.Services.Operadores
|
|||
mandatorio = sensor.Mandatorio,
|
||||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { }
|
||||
saude = saude ?? new { },
|
||||
ultima_resposta_ms = idadeUltimaRespostaSensorMs,
|
||||
};
|
||||
|
||||
atuSensoresAtivos.Add($"{tipo}.{id}");
|
||||
|
|
@ -740,6 +941,16 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimoComandoBicoMs =
|
||||
bico.UltimoComandoEnviado == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - bico.UltimoComandoEnviado).TotalMilliseconds;
|
||||
|
||||
double idadeUltimaRespostaBicoMs =
|
||||
bico.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - bico.UltimaLeitura).TotalMilliseconds;
|
||||
|
||||
var bicoObj = new
|
||||
{
|
||||
conectado = bico.Inicializado,
|
||||
|
|
@ -750,7 +961,29 @@ namespace AgroBase.Services.Operadores
|
|||
mandatorio = bico.Mandatorio,
|
||||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { }
|
||||
saude = saude ?? new { },
|
||||
|
||||
comando_atuar = bico.ComandoAtuar,
|
||||
estado_desejado = bico.UltimoStatusDesejado,
|
||||
angulo_desejado = bico.UltimoAnguloDesejado,
|
||||
|
||||
estado_leitura = bico._EstadoLeitura,
|
||||
angulo_leitura = bico._AnguloBicoLeitura,
|
||||
|
||||
angulo_neutro = bico.AnguloNeutro,
|
||||
angulo_controle = bico.AnguloControle,
|
||||
angulo_abertura = bico.AnguloAbertura,
|
||||
|
||||
ultimo_comando_ms = idadeUltimoComandoBicoMs,
|
||||
ultima_resposta_ms = idadeUltimaRespostaBicoMs,
|
||||
|
||||
atuacoes = bico.Atuacoes,
|
||||
tempo_atuado = bico.TempoAtuado,
|
||||
tempo_atuado_em_movimento = bico.TempoAtuadoEmMovimento,
|
||||
tempo_com_fluxo = bico.TempoComFluxo,
|
||||
vazao_instantanea_mls = bico.VazaoInstantaneaMLs,
|
||||
vazao_media_mls = bico.VazaoMediaMLs,
|
||||
volume_vazado_ml = bico.VolumeVazadoML,
|
||||
};
|
||||
|
||||
atuBicosAtualizados.Add(($"{basePath}", bicoObj));
|
||||
|
|
@ -782,6 +1015,16 @@ namespace AgroBase.Services.Operadores
|
|||
})
|
||||
.ToList();
|
||||
|
||||
double idadeUltimoComandoBombaMs =
|
||||
bomba.UltimoComandoEnviado == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - bomba.UltimoComandoEnviado).TotalMilliseconds;
|
||||
|
||||
double idadeUltimaRespostaBombaMs =
|
||||
bomba.UltimaLeitura == DateTime.MinValue
|
||||
? 999999
|
||||
: (agora_dt - bomba.UltimaLeitura).TotalMilliseconds;
|
||||
|
||||
var sensorObj = new
|
||||
{
|
||||
conectado = bomba.Inicializado,
|
||||
|
|
@ -792,7 +1035,34 @@ namespace AgroBase.Services.Operadores
|
|||
mandatorio = bomba.Mandatorio,
|
||||
dados,
|
||||
timestamp = agora,
|
||||
saude = saude ?? new { }
|
||||
saude = saude ?? new { },
|
||||
|
||||
comando_atuar = bomba.ComandoAtuar,
|
||||
estado_desejado = bomba.UltimoStatusDesejado,
|
||||
potencia_desejada = bomba.UltimaPotenciaDesejada,
|
||||
pressao_desejada = bomba.UltimaPressaoDesejada,
|
||||
|
||||
estado_leitura = bomba._EstadoLeitura,
|
||||
bomba_ligada = bomba._BombaLigada,
|
||||
|
||||
potencia_sp_model = bomba.PotenciaSP,
|
||||
pressao_sp_model = bomba.PressaoSP,
|
||||
|
||||
potencia_sp = bomba._PotenciaSP,
|
||||
potencia = bomba._Potencia,
|
||||
pressao_sp = bomba._PressaoSP,
|
||||
pressao = bomba._PressaoAtual,
|
||||
|
||||
modo_controle = bomba.Controle?.Modo,
|
||||
sensor_id_num = bomba.Sensor_ID_Num,
|
||||
|
||||
ultimo_comando_ms = idadeUltimoComandoBombaMs,
|
||||
ultima_resposta_ms = idadeUltimaRespostaBombaMs,
|
||||
|
||||
atuacoes = bomba.Atuacoes,
|
||||
tempo_atuado = bomba.TempoAtuado,
|
||||
tempo_com_potencia = bomba.TempoComPotencia,
|
||||
potencia_media_ativa = bomba.PotenciaMediaAtiva,
|
||||
};
|
||||
|
||||
atuBombasAtivos.Add($"{tipo}.{id}");
|
||||
|
|
@ -801,6 +1071,8 @@ namespace AgroBase.Services.Operadores
|
|||
atuBombasAtualizados.Add(("bombas.ativos", atuBombasAtivos));
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Atu), atuBombasAtualizados.ToArray());
|
||||
|
||||
|
||||
|
||||
var DadosLra = Variaveis.LoraService;
|
||||
RedisService.AtualizarCampos(RedisService.ModKey(Enums.T_Code.Lra),
|
||||
("timestamp", agora),
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,126 +3,812 @@ from shared.utils import penalizar_frequencia
|
|||
from shared.contexto_global_redis import ContextoGlobalRedis
|
||||
from health_worker.modulos.base import ModuloDiagnosticoBase
|
||||
|
||||
|
||||
class ModuloDirecional(ModuloDiagnosticoBase):
|
||||
"""
|
||||
Health inteligente do módulo direcional/MKS.
|
||||
|
||||
Nova filosofia:
|
||||
- A frequência dos parâmetros é diagnóstico de telemetria, não juiz principal.
|
||||
- O que mais importa é: driver vivo, leitura útil recente e comando sendo obedecido.
|
||||
- Condição operacional com severidade > 90 só aparece quando existe risco real
|
||||
para a operação, porque isso pode bloquear a operação liberada.
|
||||
"""
|
||||
|
||||
SAUDE_MIN_ALERTA = 80
|
||||
|
||||
RESPOSTA_RECENTE_MS = 3000.0
|
||||
RESPOSTA_LENTA_MS = 1800.0
|
||||
|
||||
TOLERANCIA_ANGULO_PADRAO_GRAUS = 2.0
|
||||
COMANDO_GRACE_MS = 900.0
|
||||
COMANDO_ALERTA_MS = 3000.0
|
||||
COMANDO_FALHA_MS = 6500.0
|
||||
ERRO_ALERTA_GRAUS = 5.0
|
||||
ERRO_FALHA_GRAUS = 10.0
|
||||
|
||||
# Penalidade de telemetria deve ser pequena.
|
||||
# Frequência baixa precisa aparecer no motivo, mas não derrubar módulo obediente.
|
||||
PENALIDADE_FREQ_MAX_PADRAO = 4
|
||||
PENALIDADE_FREQ_MAX_CRITICA = 8
|
||||
|
||||
def __init__(self):
|
||||
self.t_code = T_Code.Dir
|
||||
self.nome = "Direcional"
|
||||
self.timeout = 3 # segundos
|
||||
self.timeout = 3
|
||||
|
||||
def atualizar_saude(self):
|
||||
try:
|
||||
saude_total = 0
|
||||
total_drivers_em_uso = 0
|
||||
motivos_dos_ativos = []
|
||||
dados_dir = ContextoGlobalRedis.get_modulo(self.t_code) or {}
|
||||
ativos = self._normalizar_ativos(dados_dir.get("ativos", []))
|
||||
|
||||
saude_individual = []
|
||||
updates_drivers = {}
|
||||
|
||||
SAUDE_MIN_ALERTA = 80
|
||||
mod_conectado = False
|
||||
motivos_gerais = []
|
||||
condicoes_gerais = []
|
||||
saude_total_ativos = 0.0
|
||||
total_drivers_em_uso = 0
|
||||
algum_conectado = False
|
||||
|
||||
dados_dir = ContextoGlobalRedis.get_modulo(self.t_code)
|
||||
if not ativos:
|
||||
saude_geral = self._montar_saude_geral(
|
||||
conectado=False,
|
||||
status=StatusModulo.DESCONECTADO,
|
||||
saude=0,
|
||||
motivos=["Nenhum driver direcional ativo publicado no Redis"],
|
||||
saude_individual=[],
|
||||
condicoes_operacionais=[],
|
||||
)
|
||||
self._publicar_saude(saude_geral, {})
|
||||
return
|
||||
|
||||
ativos = dados_dir.get("ativos", [])
|
||||
for endereco in ativos:
|
||||
endereco = str(endereco) # Redis costuma serializar chave como string
|
||||
modulo = dados_dir.get(endereco)
|
||||
endereco_str = str(endereco)
|
||||
modulo = self._as_dict(
|
||||
dados_dir.get(endereco_str) or dados_dir.get(endereco)
|
||||
)
|
||||
|
||||
if not modulo:
|
||||
continue # ativo listado, mas sem dados (race condition)
|
||||
saude_mod = self._driver_sem_payload(endereco_str)
|
||||
saude_individual.append(saude_mod)
|
||||
updates_drivers[f"{endereco_str}.saude"] = saude_mod
|
||||
continue
|
||||
|
||||
saude_mod = self._avaliar_driver(endereco_str, modulo)
|
||||
|
||||
#for endereco_str, modulo in dados_dir.items():
|
||||
# try:
|
||||
# endereco = int(endereco_str)
|
||||
# except ValueError:
|
||||
# continue
|
||||
mod_id = modulo.get("mod_id")
|
||||
#print(f"atualizando saude do modulo {mod_id} com endereco {endereco}")
|
||||
FREQ_BASE = modulo.get("freq_base", 1)
|
||||
FREQ_MIN = FREQ_BASE * 0.5
|
||||
saude = 100
|
||||
motivos = []
|
||||
condicoes_operacionais = []
|
||||
conectado = modulo.get("conectado", False)
|
||||
comandar = modulo.get("comandar", True)
|
||||
freqs = modulo.get("freq", {})
|
||||
maior_freq = max(freqs.values() if freqs != {} else [0])
|
||||
if not conectado:
|
||||
saude = 0
|
||||
motivos.append("Desconectado")
|
||||
elif maior_freq == 0:
|
||||
saude = 0
|
||||
motivos.append("Sem resposta de todos os parametros (freq = 0 Hz)")
|
||||
else:
|
||||
for label, freq in freqs.items():
|
||||
pen = penalizar_frequencia(freq, FREQ_BASE, FREQ_MIN, 10)
|
||||
saude -= pen
|
||||
if freq < FREQ_MIN:
|
||||
motivos.append(f"Frequencia {label} baixa ({freq:.2f}/{FREQ_BASE} Hz, penalidade {pen}%)")
|
||||
referenciado = modulo.get("referenciado", False)
|
||||
angulo = modulo.get("angulo", 0.0)
|
||||
angulo_sp = modulo.get("angulo_sp", 0.0)
|
||||
sentido = modulo.get("sentido", Sentido.PARADO.value)
|
||||
sentido_real = modulo.get("sentido_real", Sentido.PARADO.value)
|
||||
if referenciado is False:
|
||||
condicoes_operacionais.append({
|
||||
"valor": 0,
|
||||
"severidade": 50,
|
||||
"descricao": "Driver nunca foi referenciado"
|
||||
})
|
||||
if angulo_sp != angulo:
|
||||
erro_angulo = abs(angulo - angulo_sp)
|
||||
condicoes_operacionais.append({
|
||||
"valor": angulo,
|
||||
"severidade": erro_angulo,
|
||||
"descricao": f"Angulo incoerente (Atual: {angulo}°, SP: {angulo_sp}°)"
|
||||
})
|
||||
if sentido != Sentido.PARADO.value and sentido != sentido_real:
|
||||
condicoes_operacionais.append({
|
||||
"valor": angulo,
|
||||
"severidade": (10 + angulo),
|
||||
"descricao": f"Direção incoerente (real: {Sentido(sentido_real).name}, esperada: {Sentido(sentido).name}, ângulo: {angulo:.2f}°)"
|
||||
})
|
||||
saude = max(saude, 0)
|
||||
saude_mod = {
|
||||
"id": endereco,
|
||||
"label": mod_id,
|
||||
"status": (StatusModulo.DESCONECTADO if not conectado else StatusModulo.OPERANTE if saude >= SAUDE_MIN_ALERTA else StatusModulo.ALERTA if saude > 0 else StatusModulo.FALHA).value,
|
||||
"saude": saude,
|
||||
"motivos": motivos,
|
||||
"condicoes_operacionais": condicoes_operacionais,
|
||||
"em_uso": comandar
|
||||
}
|
||||
saude_individual.append(saude_mod)
|
||||
# Se o driver está sendo usado, entra na média geral
|
||||
if comandar is True:
|
||||
saude_total += saude
|
||||
total_drivers_em_uso += 1
|
||||
motivos_dos_ativos.extend([f"Driver {endereco}: {m}" for m in motivos])
|
||||
modulo["saude"] = saude_mod
|
||||
updates_drivers[f"{endereco_str}.saude"] = saude_mod
|
||||
|
||||
conectado = self._bool(modulo.get("conectado", False))
|
||||
em_uso = self._bool(saude_mod.get("em_uso", True))
|
||||
|
||||
if conectado:
|
||||
mod_conectado = True
|
||||
algum_conectado = True
|
||||
|
||||
saude_individual.sort(key=lambda x: x["id"])
|
||||
if em_uso:
|
||||
saude_total_ativos += self._float(saude_mod.get("saude", 0), 0)
|
||||
total_drivers_em_uso += 1
|
||||
|
||||
saude_final = (saude_total // total_drivers_em_uso) if total_drivers_em_uso > 0 else 0
|
||||
for motivo in saude_mod.get("motivos", []) or []:
|
||||
motivos_gerais.append(f"Driver {endereco_str}: {motivo}")
|
||||
|
||||
status = StatusModulo.OPERANTE
|
||||
if not mod_conectado:
|
||||
status = StatusModulo.DESCONECTADO
|
||||
elif saude_final <= 0:
|
||||
status = StatusModulo.FALHA
|
||||
elif saude_final < SAUDE_MIN_ALERTA or any(saude["saude"] < SAUDE_MIN_ALERTA for saude in saude_individual):
|
||||
status = StatusModulo.ALERTA
|
||||
for cond in saude_mod.get("condicoes_operacionais", []) or []:
|
||||
condicoes_gerais.append({
|
||||
**cond,
|
||||
"driver": endereco_str,
|
||||
"label": saude_mod.get("label"),
|
||||
})
|
||||
|
||||
saude_geral = {
|
||||
"conectado": mod_conectado,
|
||||
"status": status.value,
|
||||
"saude": saude_final,
|
||||
"motivos": motivos_dos_ativos,
|
||||
"saude_individual": saude_individual
|
||||
}
|
||||
saude_individual.sort(key=lambda x: self._safe_int(x.get("id"), 0))
|
||||
|
||||
ContextoGlobalRedis.atualizar_ctx_dict(
|
||||
ContextoGlobalRedis.ModKey(self.t_code),
|
||||
saude=saude_geral
|
||||
if total_drivers_em_uso > 0:
|
||||
saude_final = int(round(saude_total_ativos / total_drivers_em_uso))
|
||||
else:
|
||||
saude_final = 0
|
||||
if algum_conectado:
|
||||
motivos_gerais.append("Nenhum driver direcional marcado como em uso")
|
||||
|
||||
status_geral = self._resolver_status_geral(
|
||||
conectado=algum_conectado,
|
||||
saude_final=saude_final,
|
||||
saude_individual=saude_individual,
|
||||
total_drivers_em_uso=total_drivers_em_uso,
|
||||
)
|
||||
|
||||
saude_geral = self._montar_saude_geral(
|
||||
conectado=algum_conectado,
|
||||
status=status_geral,
|
||||
saude=saude_final,
|
||||
motivos=self._dedupe(motivos_gerais),
|
||||
saude_individual=saude_individual,
|
||||
condicoes_operacionais=condicoes_gerais,
|
||||
)
|
||||
|
||||
self._publicar_saude(saude_geral, updates_drivers)
|
||||
|
||||
except Exception as e:
|
||||
print(f"Erro ao atualizar saude do modulo {self.t_code.name}: {e}")
|
||||
try:
|
||||
self._publicar_saude(
|
||||
{
|
||||
"conectado": False,
|
||||
"status": StatusModulo.FALHA.value,
|
||||
"saude": 0,
|
||||
"motivos": [f"Erro interno no health do direcional: {e}"],
|
||||
"saude_individual": [],
|
||||
"condicoes_operacionais": [],
|
||||
},
|
||||
{},
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# ============================================================
|
||||
# Avaliação por driver
|
||||
# ============================================================
|
||||
|
||||
def _avaliar_driver(self, endereco_str, modulo):
|
||||
mod_id = modulo.get("mod_id")
|
||||
conectado = self._bool(modulo.get("conectado", False))
|
||||
comandar = self._bool(modulo.get("comandar", True))
|
||||
referenciando = self._bool(modulo.get("referenciando", False))
|
||||
|
||||
motivos = []
|
||||
condicoes = []
|
||||
|
||||
if not conectado:
|
||||
return {
|
||||
"id": endereco_str,
|
||||
"label": mod_id,
|
||||
"status": StatusModulo.DESCONECTADO.value,
|
||||
"saude": 0,
|
||||
"motivos": ["Desconectado"],
|
||||
"condicoes_operacionais": [],
|
||||
"em_uso": comandar,
|
||||
"componentes": {
|
||||
"conectividade": 0,
|
||||
"feedback": 0,
|
||||
"seguimento_comando": 0,
|
||||
"telemetria": 0,
|
||||
"coerencia": 0,
|
||||
},
|
||||
"parametros": {},
|
||||
"debug": self._snapshot_debug(modulo),
|
||||
}
|
||||
|
||||
leitura = self._avaliar_resposta_recente(modulo, motivos, condicoes)
|
||||
feedback = self._avaliar_feedback(modulo, motivos, condicoes, referenciando)
|
||||
comando = self._avaliar_seguimento_comando(modulo, motivos, condicoes)
|
||||
coerencia = self._avaliar_coerencia_operacional(modulo, motivos, condicoes)
|
||||
telemetria = self._avaliar_telemetria(modulo, motivos, referenciando)
|
||||
|
||||
componentes = {
|
||||
"conectividade": leitura["score"],
|
||||
"feedback": feedback["score"],
|
||||
"seguimento_comando": comando["score"],
|
||||
"coerencia": coerencia["score"],
|
||||
"telemetria": telemetria["score"],
|
||||
}
|
||||
|
||||
# Pesos intencionais:
|
||||
# - A telemetria pesa pouco.
|
||||
# - Se o driver está vivo e obedecendo, frequência baixa vira motivo/alerta local,
|
||||
# mas não derruba o módulo inteiro.
|
||||
saude = (
|
||||
componentes["conectividade"] * 0.25 +
|
||||
componentes["feedback"] * 0.25 +
|
||||
componentes["seguimento_comando"] * 0.35 +
|
||||
componentes["coerencia"] * 0.10 +
|
||||
componentes["telemetria"] * 0.05
|
||||
)
|
||||
|
||||
# Travas de realidade: alguns casos não podem ser mascarados por média ponderada.
|
||||
if leitura["sem_resposta"]:
|
||||
saude = min(saude, 20)
|
||||
|
||||
if feedback["sem_pulsos"]:
|
||||
saude = min(saude, 35)
|
||||
|
||||
if comando["falha_seguimento"]:
|
||||
saude = min(saude, 45)
|
||||
|
||||
if coerencia["falha_coerencia"]:
|
||||
saude = min(saude, 65)
|
||||
|
||||
saude = int(max(0, min(100, round(saude))))
|
||||
|
||||
status = self._resolver_status_driver(
|
||||
conectado=conectado,
|
||||
saude=saude,
|
||||
condicoes_operacionais=condicoes,
|
||||
)
|
||||
|
||||
return {
|
||||
"id": endereco_str,
|
||||
"label": mod_id,
|
||||
"status": status.value,
|
||||
"saude": saude,
|
||||
"motivos": self._dedupe(motivos),
|
||||
"condicoes_operacionais": condicoes,
|
||||
"em_uso": comandar,
|
||||
"referenciando": referenciando,
|
||||
"componentes": componentes,
|
||||
"parametros": telemetria["parametros"],
|
||||
"debug": self._snapshot_debug(modulo),
|
||||
}
|
||||
|
||||
def _driver_sem_payload(self, endereco_str):
|
||||
return {
|
||||
"id": endereco_str,
|
||||
"label": None,
|
||||
"status": StatusModulo.DESCONECTADO.value,
|
||||
"saude": 0,
|
||||
"motivos": ["Driver ativo listado, mas sem payload no Redis"],
|
||||
"condicoes_operacionais": [],
|
||||
"em_uso": False,
|
||||
"componentes": {
|
||||
"conectividade": 0,
|
||||
"feedback": 0,
|
||||
"seguimento_comando": 0,
|
||||
"telemetria": 0,
|
||||
"coerencia": 0,
|
||||
},
|
||||
"parametros": {},
|
||||
"debug": {},
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Componentes de saúde
|
||||
# ============================================================
|
||||
|
||||
def _avaliar_resposta_recente(self, modulo, motivos, condicoes):
|
||||
ultima_resposta_ms = self._float(modulo.get("ultima_resposta_ms", 999999), 999999)
|
||||
|
||||
if ultima_resposta_ms > self.RESPOSTA_RECENTE_MS:
|
||||
motivos.append(
|
||||
f"Sem resposta recente do MKS ({ultima_resposta_ms:.0f} ms)"
|
||||
)
|
||||
condicoes.append({
|
||||
"valor": ultima_resposta_ms,
|
||||
"severidade": 95,
|
||||
"descricao": f"Driver direcional sem resposta recente ({ultima_resposta_ms:.0f} ms)",
|
||||
})
|
||||
return {
|
||||
"score": 0,
|
||||
"sem_resposta": True,
|
||||
}
|
||||
|
||||
if ultima_resposta_ms > self.RESPOSTA_LENTA_MS:
|
||||
motivos.append(
|
||||
f"Resposta CAN lenta ({ultima_resposta_ms:.0f} ms)"
|
||||
)
|
||||
return {
|
||||
"score": 75,
|
||||
"sem_resposta": False,
|
||||
}
|
||||
|
||||
return {
|
||||
"score": 100,
|
||||
"sem_resposta": False,
|
||||
}
|
||||
|
||||
def _avaliar_feedback(self, modulo, motivos, condicoes, referenciando):
|
||||
freqs = self._normalizar_dict_chaves(modulo.get("freq", {}))
|
||||
freq_pulsos = self._float(freqs.get("pulsosrecebidos", 0.0), 0.0)
|
||||
freq_io = self._float(freqs.get("io", 0.0), 0.0)
|
||||
|
||||
angulo = self._float(modulo.get("angulo", 0.0), 0.0)
|
||||
angulo_sp = self._float(modulo.get("angulo_sp", 0.0), 0.0)
|
||||
|
||||
# Pulsos é o feedback central do MKS.
|
||||
if freq_pulsos <= 0:
|
||||
motivos.append("Sem feedback de PulsosRecebidos")
|
||||
condicoes.append({
|
||||
"valor": freq_pulsos,
|
||||
"severidade": 92,
|
||||
"descricao": "Sem leitura de PulsosRecebidos, não há feedback confiável de ângulo",
|
||||
})
|
||||
return {
|
||||
"score": 0,
|
||||
"sem_pulsos": True,
|
||||
}
|
||||
|
||||
score = 100
|
||||
|
||||
# IO é crítico durante referenciamento, mas só auxiliar na operação.
|
||||
if referenciando and freq_io <= 0:
|
||||
motivos.append("Referenciando sem leitura de IO")
|
||||
condicoes.append({
|
||||
"valor": freq_io,
|
||||
"severidade": 91,
|
||||
"descricao": "Referenciamento sem leitura de IO do sensor do rasgo",
|
||||
})
|
||||
score = min(score, 30)
|
||||
elif freq_io <= 0:
|
||||
motivos.append("Sem leitura de IO; sentido real pode ficar desatualizado")
|
||||
score = min(score, 85)
|
||||
|
||||
return {
|
||||
"score": score,
|
||||
"sem_pulsos": False,
|
||||
"angulo": angulo,
|
||||
"angulo_sp": angulo_sp,
|
||||
}
|
||||
|
||||
def _avaliar_seguimento_comando(self, modulo, motivos, condicoes):
|
||||
comandar = self._bool(modulo.get("comandar", True))
|
||||
atingiu = self._bool(modulo.get("atingiu_angulo_sp", False))
|
||||
retornando_a_zero = self._bool(modulo.get("retornando_a_zero", False))
|
||||
|
||||
erro_angulo = self._float(
|
||||
modulo.get(
|
||||
"erro_angulo",
|
||||
abs(
|
||||
self._float(modulo.get("angulo", 0.0), 0.0) -
|
||||
self._float(modulo.get("angulo_sp", 0.0), 0.0)
|
||||
)
|
||||
),
|
||||
0.0,
|
||||
)
|
||||
|
||||
ultimo_comando_ms = self._float(modulo.get("ultimo_comando_ms", 999999), 999999)
|
||||
ultima_direcao = self._safe_int(
|
||||
modulo.get("ultima_direcao", self._direcao_parada_value()),
|
||||
self._direcao_parada_value(),
|
||||
)
|
||||
|
||||
if not comandar:
|
||||
# Driver conectado, mas não participa do comando atual.
|
||||
return {
|
||||
"score": 100,
|
||||
"falha_seguimento": False,
|
||||
}
|
||||
|
||||
if atingiu:
|
||||
return {
|
||||
"score": 100,
|
||||
"falha_seguimento": False,
|
||||
}
|
||||
|
||||
if ultimo_comando_ms < self.COMANDO_GRACE_MS:
|
||||
return {
|
||||
"score": 96,
|
||||
"falha_seguimento": False,
|
||||
}
|
||||
|
||||
if retornando_a_zero and erro_angulo <= self.ERRO_ALERTA_GRAUS:
|
||||
motivos.append(
|
||||
f"Retornando a zero com erro residual ({erro_angulo:.2f}°)"
|
||||
)
|
||||
return {
|
||||
"score": 88,
|
||||
"falha_seguimento": False,
|
||||
}
|
||||
|
||||
# Aqui começa a diferença entre telemetria lenta e comando que não pegou.
|
||||
if ultimo_comando_ms >= self.COMANDO_FALHA_MS and erro_angulo >= self.ERRO_FALHA_GRAUS:
|
||||
motivos.append(
|
||||
f"Comando direcional não convergiu "
|
||||
f"(erro={erro_angulo:.2f}°, comando há {ultimo_comando_ms:.0f} ms)"
|
||||
)
|
||||
condicoes.append({
|
||||
"valor": erro_angulo,
|
||||
"severidade": 92,
|
||||
"descricao": (
|
||||
"Driver direcional não acompanhou o comando "
|
||||
f"(erro={erro_angulo:.2f}°, comando há {ultimo_comando_ms:.0f} ms)"
|
||||
),
|
||||
})
|
||||
return {
|
||||
"score": 20,
|
||||
"falha_seguimento": True,
|
||||
}
|
||||
|
||||
if ultimo_comando_ms >= self.COMANDO_ALERTA_MS and erro_angulo >= self.ERRO_ALERTA_GRAUS:
|
||||
motivos.append(
|
||||
f"Ângulo ainda distante do SP "
|
||||
f"(erro={erro_angulo:.2f}°, comando há {ultimo_comando_ms:.0f} ms)"
|
||||
)
|
||||
condicoes.append({
|
||||
"valor": erro_angulo,
|
||||
"severidade": min(89, max(40, erro_angulo * 5)),
|
||||
"descricao": (
|
||||
"Ângulo direcional distante do SP, mas ainda dentro de janela de alerta "
|
||||
f"(erro={erro_angulo:.2f}°, comando há {ultimo_comando_ms:.0f} ms)"
|
||||
),
|
||||
})
|
||||
return {
|
||||
"score": 70,
|
||||
"falha_seguimento": False,
|
||||
}
|
||||
|
||||
if erro_angulo > self.TOLERANCIA_ANGULO_PADRAO_GRAUS:
|
||||
motivos.append(
|
||||
f"Ângulo fora da tolerância momentaneamente (erro={erro_angulo:.2f}°)"
|
||||
)
|
||||
return {
|
||||
"score": 90,
|
||||
"falha_seguimento": False,
|
||||
}
|
||||
|
||||
return {
|
||||
"score": 100,
|
||||
"falha_seguimento": False,
|
||||
}
|
||||
|
||||
def _avaliar_coerencia_operacional(self, modulo, motivos, condicoes):
|
||||
referenciado = self._bool(modulo.get("referenciado", False))
|
||||
referenciando = self._bool(modulo.get("referenciando", False))
|
||||
|
||||
sentido = self._safe_int(
|
||||
modulo.get("sentido", self._sentido_parado_value()),
|
||||
self._sentido_parado_value(),
|
||||
)
|
||||
sentido_real = self._safe_int(
|
||||
modulo.get("sentido_real", self._sentido_parado_value()),
|
||||
self._sentido_parado_value(),
|
||||
)
|
||||
sentido_sp = self._safe_int(
|
||||
modulo.get("sentido_sp", self._sentido_parado_value()),
|
||||
self._sentido_parado_value(),
|
||||
)
|
||||
|
||||
score = 100
|
||||
falha_coerencia = False
|
||||
|
||||
if not referenciado and not referenciando:
|
||||
motivos.append("Driver ainda não foi referenciado")
|
||||
condicoes.append({
|
||||
"valor": 0,
|
||||
"severidade": 50,
|
||||
"descricao": "Driver direcional ainda não foi referenciado",
|
||||
})
|
||||
score = min(score, 75)
|
||||
|
||||
# Sentido real vem do sensor do rasgo.
|
||||
# Como esse sensor pode ter borda/transição, não vamos bloquear operação por isso sozinho.
|
||||
if sentido != self._sentido_parado_value() and sentido != sentido_real:
|
||||
motivos.append(
|
||||
f"Sentido real incoerente "
|
||||
f"(real={self._nome_sentido(sentido_real)}, esperado={self._nome_sentido(sentido)})"
|
||||
)
|
||||
condicoes.append({
|
||||
"valor": sentido_real,
|
||||
"severidade": 70,
|
||||
"descricao": (
|
||||
"Sentido real difere do sentido calculado "
|
||||
f"(real={self._nome_sentido(sentido_real)}, "
|
||||
f"esperado={self._nome_sentido(sentido)}, "
|
||||
f"SP={self._nome_sentido(sentido_sp)})"
|
||||
),
|
||||
})
|
||||
score = min(score, 70)
|
||||
|
||||
return {
|
||||
"score": score,
|
||||
"falha_coerencia": falha_coerencia,
|
||||
}
|
||||
|
||||
def _avaliar_telemetria(self, modulo, motivos, referenciando):
|
||||
parametros = self._resolver_parametros_monitorados(modulo)
|
||||
parametros_debug = {}
|
||||
|
||||
if not parametros:
|
||||
motivos.append("Contrato de telemetria sem parâmetros em uso")
|
||||
return {
|
||||
"score": 70,
|
||||
"parametros": {},
|
||||
}
|
||||
|
||||
scores = []
|
||||
|
||||
for parametro in parametros:
|
||||
avaliacao = self._avaliar_frequencia_parametro(modulo, parametro, referenciando)
|
||||
parametros_debug[parametro] = avaliacao
|
||||
|
||||
freq = avaliacao["freq"]
|
||||
freq_min = avaliacao["freq_min"]
|
||||
freq_base = avaliacao["freq_base"]
|
||||
score_param = avaliacao["score"]
|
||||
|
||||
scores.append(score_param)
|
||||
|
||||
if freq <= 0:
|
||||
motivos.append(f"Telemetria {parametro} ausente (0 Hz)")
|
||||
elif freq < freq_min:
|
||||
motivos.append(
|
||||
f"Telemetria {parametro} baixa "
|
||||
f"({freq:.2f}/{freq_base:.2f} Hz, min={freq_min:.2f} Hz)"
|
||||
)
|
||||
|
||||
score = sum(scores) / len(scores) if scores else 70
|
||||
|
||||
return {
|
||||
"score": int(max(0, min(100, round(score)))),
|
||||
"parametros": parametros_debug,
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Frequência / contrato
|
||||
# ============================================================
|
||||
|
||||
def _resolver_parametros_monitorados(self, modulo):
|
||||
freq_uso = self._normalizar_dict_chaves(modulo.get("freq_uso", {}))
|
||||
polling = self._as_dict(modulo.get("polling", {}))
|
||||
|
||||
parametros = [
|
||||
parametro
|
||||
for parametro, em_uso in freq_uso.items()
|
||||
if self._bool(em_uso)
|
||||
]
|
||||
|
||||
if parametros:
|
||||
return sorted(parametros)
|
||||
|
||||
ordem = polling.get("ordem", [])
|
||||
if isinstance(ordem, list) and ordem:
|
||||
return sorted([
|
||||
self._canon(p)
|
||||
for p in ordem
|
||||
if str(p).strip()
|
||||
])
|
||||
|
||||
freqs = self._normalizar_dict_chaves(modulo.get("freq", {}))
|
||||
return sorted(freqs.keys())
|
||||
|
||||
def _avaliar_frequencia_parametro(self, modulo, parametro, referenciando):
|
||||
freqs = self._normalizar_dict_chaves(modulo.get("freq", {}))
|
||||
freq_base = self._normalizar_dict_chaves(modulo.get("freq_base", {}))
|
||||
freq_min = self._normalizar_dict_chaves(modulo.get("freq_min", {}))
|
||||
polling_ms = self._normalizar_dict_chaves(modulo.get("polling_ms", {}))
|
||||
|
||||
freq = self._float(freqs.get(parametro, 0.0), 0.0)
|
||||
base = self._float(freq_base.get(parametro, 0.0), 0.0)
|
||||
minimo = self._float(freq_min.get(parametro, 0.0), 0.0)
|
||||
ms = self._float(polling_ms.get(parametro, 0.0), 0.0)
|
||||
|
||||
if base <= 0 and ms > 0:
|
||||
# Fallback. O contrato novo deveria publicar freq_base.
|
||||
base = 1000.0 / ms
|
||||
|
||||
if base <= 0:
|
||||
base = 1.0
|
||||
|
||||
if minimo <= 0:
|
||||
minimo = base * 0.40
|
||||
|
||||
critico = parametro == "pulsosrecebidos" or (referenciando and parametro == "io")
|
||||
penalidade_max = self.PENALIDADE_FREQ_MAX_CRITICA if critico else self.PENALIDADE_FREQ_MAX_PADRAO
|
||||
|
||||
penalidade = float(
|
||||
penalizar_frequencia(freq, base, minimo, penalidade_max) or 0
|
||||
)
|
||||
|
||||
# Transformado em score local da telemetria. Não derruba sozinho o driver.
|
||||
score = max(0, min(100, 100 - (penalidade * 4)))
|
||||
|
||||
if freq <= 0:
|
||||
score = 0 if critico else 55
|
||||
|
||||
return {
|
||||
"freq": freq,
|
||||
"freq_base": base,
|
||||
"freq_min": minimo,
|
||||
"polling_ms": ms,
|
||||
"critico": critico,
|
||||
"penalidade": penalidade,
|
||||
"score": score,
|
||||
"ok": freq >= minimo,
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
# Status
|
||||
# ============================================================
|
||||
|
||||
def _resolver_status_driver(self, conectado, saude, condicoes_operacionais):
|
||||
if not conectado:
|
||||
return StatusModulo.DESCONECTADO
|
||||
|
||||
if any(self._float(c.get("severidade", 0), 0) > 90 for c in condicoes_operacionais):
|
||||
return StatusModulo.FALHA
|
||||
|
||||
if saude <= 0:
|
||||
return StatusModulo.FALHA
|
||||
|
||||
if saude < self.SAUDE_MIN_ALERTA:
|
||||
return StatusModulo.ALERTA
|
||||
|
||||
return StatusModulo.OPERANTE
|
||||
|
||||
def _resolver_status_geral(self, conectado, saude_final, saude_individual, total_drivers_em_uso):
|
||||
if not conectado:
|
||||
return StatusModulo.DESCONECTADO
|
||||
|
||||
if total_drivers_em_uso <= 0:
|
||||
return StatusModulo.ALERTA
|
||||
|
||||
in_uso = [
|
||||
item for item in saude_individual
|
||||
if self._bool(item.get("em_uso", False))
|
||||
]
|
||||
|
||||
if any(item.get("status") == StatusModulo.FALHA.value for item in in_uso):
|
||||
return StatusModulo.FALHA
|
||||
|
||||
if saude_final <= 0:
|
||||
return StatusModulo.FALHA
|
||||
|
||||
if any(item.get("status") == StatusModulo.ALERTA.value for item in in_uso):
|
||||
return StatusModulo.ALERTA
|
||||
|
||||
if saude_final < self.SAUDE_MIN_ALERTA:
|
||||
return StatusModulo.ALERTA
|
||||
|
||||
return StatusModulo.OPERANTE
|
||||
|
||||
def _montar_saude_geral(self, conectado, status, saude, motivos, saude_individual, condicoes_operacionais):
|
||||
return {
|
||||
"conectado": conectado,
|
||||
"status": status.value,
|
||||
"saude": int(max(0, min(100, saude))),
|
||||
"motivos": self._dedupe(motivos),
|
||||
"saude_individual": saude_individual,
|
||||
"condicoes_operacionais": condicoes_operacionais,
|
||||
}
|
||||
|
||||
def _publicar_saude(self, saude_geral, updates_drivers):
|
||||
payload = {"saude": saude_geral}
|
||||
payload.update(updates_drivers or {})
|
||||
|
||||
ContextoGlobalRedis.atualizar_ctx_dict(
|
||||
ContextoGlobalRedis.ModKey(self.t_code),
|
||||
**payload,
|
||||
)
|
||||
|
||||
# ============================================================
|
||||
# Debug / helpers
|
||||
# ============================================================
|
||||
|
||||
def _snapshot_debug(self, modulo):
|
||||
return {
|
||||
"angulo": self._float(modulo.get("angulo", 0.0), 0.0),
|
||||
"angulo_sp": self._float(modulo.get("angulo_sp", 0.0), 0.0),
|
||||
"erro_angulo": self._float(modulo.get("erro_angulo", 0.0), 0.0),
|
||||
"atingiu_angulo_sp": self._bool(modulo.get("atingiu_angulo_sp", False)),
|
||||
"ultimo_comando_ms": self._float(modulo.get("ultimo_comando_ms", 999999), 999999),
|
||||
"ultima_resposta_ms": self._float(modulo.get("ultima_resposta_ms", 999999), 999999),
|
||||
"ultima_direcao": modulo.get("ultima_direcao"),
|
||||
"status_movimento": modulo.get("status_movimento"),
|
||||
"rpm": self._float(modulo.get("rpm", 0.0), 0.0),
|
||||
"em_movimento": self._bool(modulo.get("em_movimento", False)),
|
||||
"retornando_a_zero": self._bool(modulo.get("retornando_a_zero", False)),
|
||||
"referenciado": self._bool(modulo.get("referenciado", False)),
|
||||
"referenciando": self._bool(modulo.get("referenciando", False)),
|
||||
"sentido": modulo.get("sentido"),
|
||||
"sentido_sp": modulo.get("sentido_sp"),
|
||||
"sentido_real": modulo.get("sentido_real"),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _normalizar_ativos(ativos):
|
||||
if not isinstance(ativos, list):
|
||||
return []
|
||||
|
||||
normalizados = []
|
||||
for item in ativos:
|
||||
try:
|
||||
normalizados.append(int(item))
|
||||
except Exception:
|
||||
try:
|
||||
normalizados.append(int(str(item).strip()))
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return normalizados
|
||||
|
||||
@staticmethod
|
||||
def _as_dict(valor):
|
||||
return valor if isinstance(valor, dict) else {}
|
||||
|
||||
@staticmethod
|
||||
def _canon(valor):
|
||||
texto = str(valor or "").strip().lower()
|
||||
return "".join(ch for ch in texto if ch.isalnum())
|
||||
|
||||
def _normalizar_dict_chaves(self, valor):
|
||||
if not isinstance(valor, dict):
|
||||
return {}
|
||||
|
||||
return {
|
||||
self._canon(k): v
|
||||
for k, v in valor.items()
|
||||
if str(k).strip()
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _bool(valor):
|
||||
try:
|
||||
if isinstance(valor, bool):
|
||||
return valor
|
||||
|
||||
if valor is None:
|
||||
return False
|
||||
|
||||
if isinstance(valor, (int, float)):
|
||||
return valor != 0
|
||||
|
||||
texto = str(valor).strip().lower()
|
||||
|
||||
if texto in ["true", "1", "sim", "yes", "y", "on"]:
|
||||
return True
|
||||
|
||||
if texto in ["false", "0", "nao", "não", "no", "n", "off", ""]:
|
||||
return False
|
||||
|
||||
return bool(valor)
|
||||
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _float(valor, padrao=0.0):
|
||||
try:
|
||||
if valor is None:
|
||||
return float(padrao)
|
||||
|
||||
return float(valor)
|
||||
except Exception:
|
||||
return float(padrao)
|
||||
|
||||
@staticmethod
|
||||
def _safe_int(valor, padrao=0):
|
||||
try:
|
||||
if valor is None:
|
||||
return int(padrao)
|
||||
|
||||
return int(valor)
|
||||
except Exception:
|
||||
return int(padrao)
|
||||
|
||||
@staticmethod
|
||||
def _dedupe(lista):
|
||||
saida = []
|
||||
vistos = set()
|
||||
|
||||
for item in lista or []:
|
||||
texto = str(item)
|
||||
|
||||
if texto in vistos:
|
||||
continue
|
||||
|
||||
vistos.add(texto)
|
||||
saida.append(item)
|
||||
|
||||
return saida
|
||||
|
||||
@staticmethod
|
||||
def _enum_value(enum_cls, nomes, padrao=0):
|
||||
for nome in nomes:
|
||||
try:
|
||||
if hasattr(enum_cls, nome):
|
||||
return getattr(enum_cls, nome).value
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return padrao
|
||||
|
||||
def _sentido_parado_value(self):
|
||||
return self._enum_value(Sentido, ["PARADO", "Parado"], 0)
|
||||
|
||||
def _direcao_parada_value(self):
|
||||
# Direcao não está importado neste módulo.
|
||||
# Nos enums do C# normalmente Parado = 0.
|
||||
return 0
|
||||
|
||||
@staticmethod
|
||||
def _nome_sentido(valor):
|
||||
try:
|
||||
return Sentido(valor).name
|
||||
except Exception:
|
||||
return str(valor)
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue