ajustado calculo de vazao por fluxo, protocolo daly bms, protocolo oid, can service robusto
This commit is contained in:
parent
824f2bad58
commit
92a845aec0
|
|
@ -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 já 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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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 já 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
|
||||||
|
|
|
||||||
|
|
@ -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 já 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
|
|
@ -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
Loading…
Reference in New Issue