ajustado calculo de vazao por fluxo, protocolo daly bms, protocolo oid, can service robusto

This commit is contained in:
Diego Freitas 2026-06-22 16:09:41 -03:00
parent 824f2bad58
commit 92a845aec0
7 changed files with 2149 additions and 575 deletions

View File

@ -155,15 +155,13 @@ namespace AgroBase.Models.Modules
{ {
get get
{ {
double direta = VazaoFluxoMLpSInstantanea; return ObterVazaoAtualConfiavelMLs(DateTime.Now);
if (direta > 0)
return direta;
return VazaoFluxoMLpSInstantaneaCalc;
} }
} }
[JsonIgnore]
public double VazaoMaximaSensor_mLs => VazaoMaximaSensor_LMin * 1000.0 / 60.0;
public double VazaoMediaMLs { get; set; } public double VazaoMediaMLs { get; set; }
public double VolumeVazadoML { get; set; } public double VolumeVazadoML { get; set; }
public double TempoAtuado { get; set; } public double TempoAtuado { get; set; }
@ -226,25 +224,38 @@ namespace AgroBase.Models.Modules
{ {
var sensor = SensorFluxoLinha; var sensor = SensorFluxoLinha;
if (sensor == null || !sensor.Inicializado || sensor.ValoresLeituras == null) if (sensor == null ||
!sensor.Inicializado ||
sensor.ValoresLeituras == null)
{ {
return 0; return 0;
} }
var leituraFluxo = sensor.ValoresLeituras.FirstOrDefault(x => x.funcao == FuncoesPinout.Fluxo); double maximaMLs =
VazaoMaximaSensor_LMin *
1000.0 /
60.0;
var leituraFluxo =
sensor.ValoresLeituras.FirstOrDefault(
x => x.funcao == FuncoesPinout.Fluxo
);
/* /*
* A leitura direta é prioritária, inclusive quando vale zero. * IMPORTANTE:
* Zero recente é uma medição válida, não motivo para usar * A leitura direta FuncoesPinout.Fluxo está em mL/s no modelo.
* pulsos antigos como fallback. * Não converter novamente de L/min para mL/s.
*
* Zero recente é válido. Se a linha está sem fluxo, o sensor deve
* poder dizer zero sem cair no fallback por pulso antigo.
*/ */
if (LeituraSensorRecente(leituraFluxo, agora)) if (LeituraSensorRecente(leituraFluxo, agora))
{ {
double vazaoLMin; double vazaoMLs;
try try
{ {
vazaoLMin = vazaoMLs =
Convert.ToDouble( Convert.ToDouble(
leituraFluxo.atual.valor leituraFluxo.atual.valor
); );
@ -254,25 +265,23 @@ namespace AgroBase.Models.Modules
return 0; return 0;
} }
if (double.IsNaN(vazaoLMin) || if (double.IsNaN(vazaoMLs) ||
double.IsInfinity(vazaoLMin) || double.IsInfinity(vazaoMLs) ||
vazaoLMin < 0) vazaoMLs < 0)
{ {
return 0; return 0;
} }
vazaoLMin = FuncoesMatematicas.Clamp( return FuncoesMatematicas.Clamp(
vazaoLMin, vazaoMLs,
0.0, 0.0,
VazaoMaximaSensor_LMin maximaMLs
); );
return vazaoLMin * 1000.0 / 60.0;
} }
/* /*
* Somente usa o cálculo por pulsos se os dois valores * Fallback por pulso:
* necessários também forem recentes. * aqui sim a fórmula calcula q em L/min e depois converte para mL/s.
*/ */
var leituraPulsos = var leituraPulsos =
sensor.ValoresLeituras.FirstOrDefault( sensor.ValoresLeituras.FirstOrDefault(
@ -300,11 +309,6 @@ namespace AgroBase.Models.Modules
return 0; return 0;
} }
double maximaMLs =
VazaoMaximaSensor_LMin *
1000.0 /
60.0;
return FuncoesMatematicas.Clamp( return FuncoesMatematicas.Clamp(
calculadaMLs, calculadaMLs,
0.0, 0.0,
@ -639,13 +643,10 @@ namespace AgroBase.Models.Modules
volumeIntervaloML / volumeIntervaloML /
bicosDoIntervalo.Count; bicosDoIntervalo.Count;
foreach (var bico in foreach (var bico in bicosDoIntervalo)
bicosDoIntervalo)
{ {
bico.TempoComFluxo += dt; bico.TempoComFluxo += dt;
bico.VolumeVazadoML += volumePorBicoML;
bico.VolumeVazadoML +=
volumePorBicoML;
} }
} }

View File

@ -258,7 +258,8 @@ namespace AgroBase.Models
{ {
public byte EnderecoTx { get; set; } public byte EnderecoTx { get; set; }
public byte EnderecoRx { get; set; } public byte EnderecoRx { get; set; }
public bool Iniciado public bool Iniciado { get; set; }
public bool Iniciado_old
{ {
get get
{ {

View File

@ -129,6 +129,61 @@ namespace AgroBase.Services
return $"{Id}_{data[0]}_{parametro}"; return $"{Id}_{data[0]}_{parametro}";
} }
public static string GerarChaveCorrelacao(T_Code dispositivo, uint idTx, uint idRx, byte funcCodeTx, byte funcCodeRx, byte[] dataTx)
{
dataTx = dataTx ?? new byte[0];
switch (dispositivo)
{
case T_Code.Bat:
/*
* Daly usa DataID no CAN ID estendido.
* Então o idRx diferencia 0x90, 0x91, 0x95...
*/
return
$"{dispositivo}|tx:{IdKey(idTx)}|rx:{IdKey(idRx)}";
case T_Code.Oid:
/*
* OID consulta:
* TX/RX DATA[0] = 0x0F
* DATA[1] = parâmetro.
*/
return
$"{dispositivo}|tx:{IdKey(idTx)}|rx:{IdKey(idRx)}|fc:0x{funcCodeRx:X2}|param:{ByteKey(dataTx, 1)}";
case T_Code.Mks:
return
$"{dispositivo}|tx:{IdKey(idTx)}|rx:{IdKey(idRx)}|fc:0x{funcCodeRx:X2}";
case T_Code.Atu:
case T_Code.Sen:
/*
* Protocolo dos módulos usa posição/funcCode e normalmente
* ID_Num em DATA[1].
*/
return
$"{dispositivo}|tx:{IdKey(idTx)}|rx:{IdKey(idRx)}|fc:0x{funcCodeRx:X2}|id_num:{ByteKey(dataTx, 1)}";
default:
return
$"{dispositivo}|tx:{IdKey(idTx)}|rx:{IdKey(idRx)}|fc:0x{funcCodeRx:X2}|p1:{ByteKey(dataTx, 1)}";
}
}
private static string ByteKey(byte[] data, int index)
{
if (data == null || data.Length <= index)
return "--";
return "0x" + data[index].ToString("X2");
}
public static string IdKey(uint id)
{
return $"0x{id:X}";
}
} }
public class CanMessage public class CanMessage

View File

@ -107,7 +107,7 @@ namespace AgroBase.Services
{ {
return MensagensPendentes return MensagensPendentes
.Where(x => x.ComResposta && x.Enviado && !x.Respondido) .Where(x => x.ComResposta && x.Enviado && !x.Respondido)
.GroupBy(x => IdKey(x.IdRx)) .GroupBy(x => CanManager.IdKey(x.IdRx))
.ToDictionary(g => g.Key, g => g.Count()); .ToDictionary(g => g.Key, g => g.Count());
} }
} }
@ -139,6 +139,33 @@ namespace AgroBase.Services
private DateTime _inicioJanelaLatencia = DateTime.UtcNow; private DateTime _inicioJanelaLatencia = DateTime.UtcNow;
private double _latenciaMaxJanelaMs = 0; private double _latenciaMaxJanelaMs = 0;
private readonly ConcurrentDictionary<string, DateTime> _cooldownPorCanal =
new ConcurrentDictionary<string, DateTime>();
private readonly ConcurrentDictionary<string, DateTime> _ultimaFilaPorChave =
new ConcurrentDictionary<string, DateTime>();
private static readonly TimeSpan COOLDOWN_DEFAULT =
TimeSpan.FromMilliseconds(0);
private static readonly TimeSpan COOLDOWN_MKS =
TimeSpan.FromMilliseconds(20);
private static readonly TimeSpan COOLDOWN_OID =
TimeSpan.FromMilliseconds(8);
private static readonly TimeSpan COOLDOWN_BAT =
TimeSpan.FromMilliseconds(80);
private static readonly TimeSpan COOLDOWN_ATU_SEN =
TimeSpan.FromMilliseconds(4);
private static readonly TimeSpan DEDUPE_JANELA_GET =
TimeSpan.FromMilliseconds(250);
private static readonly TimeSpan DEDUPE_JANELA_SET =
TimeSpan.FromMilliseconds(60);
private static long NowUnixMs() private static long NowUnixMs()
{ {
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(); return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
@ -340,6 +367,16 @@ namespace AgroBase.Services
if (dados.Count > 8) if (dados.Count > 8)
throw new InvalidOperationException($"Payload CAN maior que 8 bytes. ID=0x{idTx:X}, Len={dados.Count}"); throw new InvalidOperationException($"Payload CAN maior que 8 bytes. ID=0x{idTx:X}, Len={dados.Count}");
string chaveCorrelacao =
CanManager.GerarChaveCorrelacao(
Dispositivo,
idTx,
idRx,
funcCodeTx,
funcCodeRx,
dados.ToArray()
);
var novaMensagem = new CanMessage var novaMensagem = new CanMessage
{ {
Momento = DateTime.Now, Momento = DateTime.Now,
@ -350,25 +387,70 @@ namespace AgroBase.Services
funcCodeRx = funcCodeRx, funcCodeRx = funcCodeRx,
DataTx = dados.ToArray(), DataTx = dados.ToArray(),
ComResposta = get, ComResposta = get,
ChaveInterna = CanManager.GerarChaveMensagem(Dispositivo, idRx, dados.ToArray()), ChaveInterna = chaveCorrelacao,
ContabilizarTimeoutHealth = contabilizarTimeoutHealth, ContabilizarTimeoutHealth = contabilizarTimeoutHealth,
MensagemDescoberta = mensagemDescoberta MensagemDescoberta = mensagemDescoberta
}; };
lock (_LockMensagens) lock (_LockMensagens)
{ {
bool mensagemNaFila = MensagensPendentes.Any(x => DateTime agora =
(!x.Enviado || (x.ComResposta && x.Enviado && !x.Respondido)) && DateTime.Now;
x.IdTx == idTx &&
x.IdRx == idRx &&
x.DataTx != null &&
x.DataTx.SequenceEqual(dados.ToArray()));
if (!mensagemNaFila) /*
* Para request com resposta, não deixa duas pendências iguais
* ficarem esperando a mesma resposta. Isso é vital para OID, porque
* todas as consultas respondem com funcCode 0x0F e diferem pelo param.
*/
bool mesmaPendenciaAberta =
MensagensPendentes.Any(x =>
x.ComResposta &&
!x.Respondido &&
x.ChaveInterna == chaveCorrelacao);
if (get && mesmaPendenciaAberta)
return;
/*
* Para comando sem resposta, evita empilhar comandos idênticos
* ainda não enviados. Depois de enviado, ele será limpo normalmente.
*/
bool comandoIdenticoNaoEnviado =
MensagensPendentes.Any(x =>
!x.ComResposta &&
!x.Enviado &&
x.IdTx == idTx &&
x.DataTx != null &&
x.DataTx.SequenceEqual(dados.ToArray()));
if (!get && comandoIdenticoNaoEnviado)
return;
/*
* Dedupe temporal extra, útil para comandos de controle que chegam
* repetidos por loop rápido.
*/
TimeSpan janela =
get
? DEDUPE_JANELA_GET
: DEDUPE_JANELA_SET;
if (_ultimaFilaPorChave.TryGetValue(
chaveCorrelacao,
out DateTime ultima) &&
agora - ultima < janela)
{ {
MensagensPendentes.Add(novaMensagem); return;
MostrarLog($"Mensagem adicionada. ID=0x{idTx:X}, Data={BitConverter.ToString(novaMensagem.DataTx)}", idTx);
} }
_ultimaFilaPorChave[chaveCorrelacao] = agora;
MensagensPendentes.Add(novaMensagem);
MostrarLog(
$"Mensagem adicionada. ID=0x{idTx:X}, Data={BitConverter.ToString(novaMensagem.DataTx)}",
idTx
);
} }
} }
@ -402,23 +484,62 @@ namespace AgroBase.Services
} }
private readonly ConcurrentDictionary<uint, DateTime> _cooldownPorId = new ConcurrentDictionary<uint, DateTime>(); private readonly ConcurrentDictionary<uint, DateTime> _cooldownPorId = new ConcurrentDictionary<uint, DateTime>();
private static readonly TimeSpan COOLDOWN_MKS = TimeSpan.FromMilliseconds(20);
private bool PodeEnviarAgora(CanMessage m) private bool PodeEnviarAgora(CanMessage m)
{ {
if (m.Dispositivo != T_Code.Mks) if (m == null)
return false;
TimeSpan cooldown =
ObterCooldownDispositivo(m.Dispositivo);
if (cooldown <= TimeSpan.Zero)
return true; return true;
var now = DateTime.UtcNow; string chave =
var due = _cooldownPorId.GetOrAdd(m.IdTx, DateTime.MinValue); $"{m.Dispositivo}|tx:{CanManager.IdKey(m.IdTx)}";
var now =
DateTime.UtcNow;
var due =
_cooldownPorCanal.GetOrAdd(
chave,
DateTime.MinValue
);
if (now < due) if (now < due)
return false; return false;
_cooldownPorId[m.IdTx] = now + COOLDOWN_MKS; _cooldownPorCanal[chave] =
now + cooldown;
return true; return true;
} }
private static TimeSpan ObterCooldownDispositivo(T_Code dispositivo)
{
switch (dispositivo)
{
case T_Code.Mks:
return COOLDOWN_MKS;
case T_Code.Oid:
return COOLDOWN_OID;
case T_Code.Bat:
return COOLDOWN_BAT;
case T_Code.Atu:
case T_Code.Sen:
return COOLDOWN_ATU_SEN;
default:
return COOLDOWN_DEFAULT;
}
}
private async Task EnviarCAN() private async Task EnviarCAN()
{ {
if (!IsConnected) return; if (!IsConnected) return;
@ -498,7 +619,7 @@ namespace AgroBase.Services
} }
_tcpStream.Write(packet, 0, packet.Length); _tcpStream.Write(packet, 0, packet.Length);
_tcpStream.Flush(); //_tcpStream.Flush();
} }
else else
{ {
@ -686,29 +807,17 @@ namespace AgroBase.Services
lock (_LockMensagens) lock (_LockMensagens)
{ {
mensagem = MensagensPendentes.FirstOrDefault(x => mensagem =
{ MensagensPendentes
if (!x.Enviado) return false; .Where(x =>
if (x.Respondido) return false; MensagemCorrespondeResposta(
if (x.Dispositivo != dispositivo) return false; x,
if (x.IdRx != id) return false; dispositivo,
if (x.DataTx == null || x.DataTx.Length == 0) return false; id,
data
if (db) ))
return true; .OrderBy(x => x.EnviadoEm)
.FirstOrDefault();
// MKS: a resposta deve casar pelo function code.
// TX: [code, ..., crc]
// RX: [code, ..., crc]
if (x.Dispositivo == T_Code.Mks)
return data.Length > 0 && x.funcCodeRx == data[0];
// Demais protocolos antigos
if (data.Length < 2 || x.DataTx.Length < 2)
return true;
return x.DataTx[1] == data[1];
});
} }
if (mensagem == null) if (mensagem == null)
@ -745,6 +854,112 @@ namespace AgroBase.Services
} }
} }
private bool MensagemCorrespondeResposta(CanMessage pendente, T_Code dispositivoResposta, uint idResposta, byte[] dataResposta)
{
if (pendente == null)
return false;
if (!pendente.Enviado)
return false;
if (pendente.Respondido)
return false;
if (!pendente.ComResposta)
return false;
if (pendente.Dispositivo != dispositivoResposta)
return false;
if (pendente.IdRx != idResposta)
return false;
byte[] dataTx =
pendente.DataTx ?? new byte[0];
byte[] dataRx =
dataResposta ?? new byte[0];
switch (pendente.Dispositivo)
{
case T_Code.Bat:
/*
* Daly: DataID vem no CAN ID, então idRx fechou.
*/
return true;
case T_Code.Mks:
return
dataRx.Length > 0 &&
pendente.funcCodeRx == dataRx[0];
case T_Code.Oid:
/*
* OID:
* consulta retorna DATA[0]=0x0F e DATA[1]=param.
*/
return
dataRx.Length >= 2 &&
dataTx.Length >= 2 &&
dataRx[0] == pendente.funcCodeRx &&
dataRx[1] == dataTx[1];
case T_Code.Atu:
case T_Code.Sen:
/*
* ATU/SEN:
* casa pelo func/posição e, quando existir, pelo ID_Num.
*/
if (dataRx.Length <= 0)
return false;
if (dataRx[0] != pendente.funcCodeRx)
return false;
if (dataRx.Length >= 2 && dataTx.Length >= 2)
return dataRx[1] == dataTx[1];
return true;
default:
if (dataRx.Length <= 0)
return false;
if (dataRx[0] != pendente.funcCodeRx)
return false;
if (dataRx.Length >= 2 && dataTx.Length >= 2)
return dataRx[1] == dataTx[1];
return true;
}
}
private static TimeSpan TimeoutResposta(CanMessage msg)
{
if (msg == null)
return TimeSpan.FromSeconds(2);
switch (msg.Dispositivo)
{
case T_Code.Oid:
return TimeSpan.FromMilliseconds(900);
case T_Code.Bat:
return TimeSpan.FromMilliseconds(1500);
case T_Code.Mks:
return TimeSpan.FromMilliseconds(900);
case T_Code.Atu:
case T_Code.Sen:
return TimeSpan.FromMilliseconds(1200);
default:
return TimeSpan.FromSeconds(2);
}
}
private void LimparMensagensAntigas() private void LimparMensagensAntigas()
{ {
var agora = DateTime.Now; var agora = DateTime.Now;
@ -756,7 +971,8 @@ namespace AgroBase.Services
x.ComResposta && x.ComResposta &&
x.Enviado && x.Enviado &&
!x.Respondido && !x.Respondido &&
x.EnviadoEm < agora.AddSeconds(-2)) x.EnviadoEm != DateTime.MinValue &&
agora - x.EnviadoEm > TimeoutResposta(x))
.ToList(); .ToList();
foreach (var item in timeouts) foreach (var item in timeouts)
@ -765,12 +981,14 @@ namespace AgroBase.Services
MensagensPendentes.Remove(item); MensagensPendentes.Remove(item);
} }
var remover = MensagensPendentes var remover =
.Where(x => MensagensPendentes
(!x.ComResposta && x.Enviado) || .Where(x =>
(x.ComResposta && x.Respondido) || (!x.ComResposta && x.Enviado) ||
(x.Momento < agora.AddSeconds(-30))) (x.ComResposta && x.Respondido) ||
.ToList(); (x.Momento < agora.AddSeconds(-30)) ||
(!IsConnected && x.Enviado))
.ToList();
foreach (var item in remover) foreach (var item in remover)
MensagensPendentes.Remove(item); MensagensPendentes.Remove(item);
@ -836,6 +1054,8 @@ namespace AgroBase.Services
{ {
try try
{ {
LimparPendenciasEnviadasPorReconexao();
FecharConexaoSemLimparHandlers(); FecharConexaoSemLimparHandlers();
if (TransportMode == CanEthernetTransportMode.Tcp) if (TransportMode == CanEthernetTransportMode.Tcp)
@ -863,6 +1083,22 @@ namespace AgroBase.Services
} }
} }
private void LimparPendenciasEnviadasPorReconexao()
{
lock (_LockMensagens)
{
var remover =
MensagensPendentes
.Where(x =>
x.Enviado ||
x.Momento < DateTime.Now.AddSeconds(-2))
.ToList();
foreach (var item in remover)
MensagensPendentes.Remove(item);
}
}
private void FecharConexaoSemLimparHandlers() private void FecharConexaoSemLimparHandlers()
{ {
lock (_socketLock) lock (_socketLock)
@ -1032,17 +1268,12 @@ namespace AgroBase.Services
} }
} }
private static string IdKey(uint id)
{
return $"0x{id:X}";
}
private static string TimeoutChave(CanMessage msg) private static string TimeoutChave(CanMessage msg)
{ {
if (msg == null) if (msg == null)
return "desconhecido"; return "desconhecido";
string chave = $"{msg.Dispositivo}|tx:{IdKey(msg.IdTx)}|rx:{IdKey(msg.IdRx)}|fc_rx:0x{msg.funcCodeRx:X2}"; string chave = $"{msg.Dispositivo}|tx:{CanManager.IdKey(msg.IdTx)}|rx:{CanManager.IdKey(msg.IdRx)}|fc_rx:0x{msg.funcCodeRx:X2}";
if (new List<T_Code>() { T_Code.Atu, T_Code.Sen }.Contains(msg.Dispositivo) && msg.DataTx != null && msg.DataTx.Length > 1) if (new List<T_Code>() { T_Code.Atu, T_Code.Sen }.Contains(msg.Dispositivo) && msg.DataTx != null && msg.DataTx.Length > 1)
{ {
@ -1054,6 +1285,11 @@ namespace AgroBase.Services
chave += $"|fc_tx:0x{msg.DataTx[0]:X2}"; chave += $"|fc_tx:0x{msg.DataTx[0]:X2}";
} }
if (!string.IsNullOrWhiteSpace(msg.ChaveInterna))
{
chave += $"|key:{msg.ChaveInterna}";
}
return chave; return chave;
} }
@ -1075,7 +1311,7 @@ namespace AgroBase.Services
TimeoutsRespostaTotal++; TimeoutsRespostaTotal++;
string idRx = IdKey(msg.IdRx); string idRx = CanManager.IdKey(msg.IdRx);
string dispositivo = msg.Dispositivo.ToString(); string dispositivo = msg.Dispositivo.ToString();
string chave = TimeoutChave(msg); string chave = TimeoutChave(msg);

File diff suppressed because it is too large Load Diff

View File

@ -144,14 +144,6 @@ namespace AgroBase.Services
public void IniciarOperacao(double distanciaTotalMetrosAtual, double tempoTotalSegundosAtual) public void IniciarOperacao(double distanciaTotalMetrosAtual, double tempoTotalSegundosAtual)
{ {
/*
* Antes, se o contador ainda não tivesse recebido uma leitura
* válida do reservatório, o início era ignorado.
*
* Isso fazia VolumeConsumidoOperacao_L ficar zero durante toda
* a operação quando a primeira leitura válida chegava depois
* do comando de iniciar.
*/
if (!_inicializado) if (!_inicializado)
{ {
OperacaoAtiva = true; OperacaoAtiva = true;
@ -229,19 +221,24 @@ namespace AgroBase.Services
if (!_inicializado) if (!_inicializado)
{ {
Inicializar(distanciaTotalMetrosAtual, tempoTotalSegundosAtual); Inicializar(
distanciaTotalMetrosAtual,
tempoTotalSegundosAtual
);
if (OperacaoAtiva || _inicioOperacaoPendente) if (OperacaoAtiva || _inicioOperacaoPendente)
{ {
/* /*
* Primeira leitura válida depois do comando de iniciar. * Primeira leitura válida depois do início da operação.
* A operação passa a usar o volume atual como referência * Usa o volume atual como baseline da operação para começar
* inicial, sem perder o estado de OperacaoAtiva. * PercentualConsumidoOperacao em zero.
*/ */
_volumeInicialOperacao_L = VolumeAtual_L; _volumeInicialOperacao_L = VolumeAtual_L;
_distanciaTotalAnterior_m = distanciaTotalMetrosAtual; _distanciaTotalAnterior_m =
_tempoTotalAnterior_s = tempoTotalSegundosAtual; distanciaTotalMetrosAtual;
_tempoTotalAnterior_s =
tempoTotalSegundosAtual;
OperacaoAtiva = true; OperacaoAtiva = true;
OperacaoFinalizada = false; OperacaoFinalizada = false;
@ -306,15 +303,24 @@ namespace AgroBase.Services
private double ObterVazaoFluxo_mLs() private double ObterVazaoFluxo_mLs()
{ {
var dados = Variaveis.OperacaoEmAndamento.DispAtu?.Dados; var dados =
Variaveis
.OperacaoEmAndamento
.DispAtu?
.Dados;
if (dados == null) if (dados == null)
return 0; return 0;
double vazao = dados.VazaoFluxoMLpSReferencia; double vazao =
dados.VazaoFluxoMLpSReferencia;
if (vazao < 0) if (double.IsNaN(vazao) ||
double.IsInfinity(vazao) ||
vazao < 0)
{
return 0; return 0;
}
return vazao; return vazao;
} }
@ -399,11 +405,15 @@ namespace AgroBase.Services
?? 0; ?? 0;
if (qtdAtuacoes > 0) if (qtdAtuacoes > 0)
{
HerbicidaPorAtuacao_L = HerbicidaPorAtuacao_L =
VolumeConsumidoOperacao_L / VolumeConsumidoOperacao_L /
qtdAtuacoes; qtdAtuacoes;
}
else else
{
HerbicidaPorAtuacao_L = 0; HerbicidaPorAtuacao_L = 0;
}
} }
private void AtualizarAutonomia(double velocidadeAtual_m_s) private void AtualizarAutonomia(double velocidadeAtual_m_s)

File diff suppressed because it is too large Load Diff