revisão tipo movimento lateral e rotacionar no eixo, novas métricas can, comunicacao can mov e dir, compensacao de rpm em curvas
This commit is contained in:
parent
66f019b6e8
commit
3891aaf819
|
|
@ -416,7 +416,7 @@ namespace AgroBase.Forms.Operacoes
|
||||||
$"{id} Fator Curva",
|
$"{id} Fator Curva",
|
||||||
LogsMovimentacao,
|
LogsMovimentacao,
|
||||||
id,
|
id,
|
||||||
x => x.FatorCompensacaoCurva,
|
x => x.FatorCompensacao,
|
||||||
visivel: false
|
visivel: false
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -696,7 +696,8 @@ namespace AgroBase
|
||||||
if (cmd == null) return;
|
if (cmd == null) return;
|
||||||
cmd.CalcularValor(direcao == Direcao.Parado, valorJoy: valor);
|
cmd.CalcularValor(direcao == Direcao.Parado, valorJoy: valor);
|
||||||
bool mudou = direcao != _ultimaDirecaoMovEnviada || double.IsNaN(_ultimoValorMovEnviado) || Math.Abs(cmd.ValorAplicar - _ultimoValorMovEnviado) >= VariacaoMinimaMov;
|
bool mudou = direcao != _ultimaDirecaoMovEnviada || double.IsNaN(_ultimoValorMovEnviado) || Math.Abs(cmd.ValorAplicar - _ultimoValorMovEnviado) >= VariacaoMinimaMov;
|
||||||
bool heartbeat = (DateTime.Now - _ultimoEnvioMov).TotalMilliseconds >= HeartbeatComandoMs;
|
bool combinado = MovimentosCombinados.Contains(Variaveis.OperacaoEmAndamento.Controle.TipoMovimento);
|
||||||
|
bool heartbeat = !combinado && (DateTime.Now - _ultimoEnvioDir).TotalMilliseconds >= HeartbeatComandoMs;
|
||||||
if (!mudou && !heartbeat) return;
|
if (!mudou && !heartbeat) return;
|
||||||
EnviarComandoMotorAtualizado(cmd, ForcarComando: false);
|
EnviarComandoMotorAtualizado(cmd, ForcarComando: false);
|
||||||
_ultimaDirecaoMovEnviada = direcao; _ultimoValorMovEnviado = cmd.ValorAplicar; _ultimoEnvioMov = DateTime.Now;
|
_ultimaDirecaoMovEnviada = direcao; _ultimoValorMovEnviado = cmd.ValorAplicar; _ultimoEnvioMov = DateTime.Now;
|
||||||
|
|
|
||||||
|
|
@ -101,23 +101,44 @@ namespace AgroBase.Models
|
||||||
MomentoUltimaLeitura = agora;
|
MomentoUltimaLeitura = agora;
|
||||||
}
|
}
|
||||||
public double AnguloEstimado()
|
public double AnguloEstimado()
|
||||||
|
{
|
||||||
|
return AnguloEstimadoEm(DateTime.Now);
|
||||||
|
}
|
||||||
|
|
||||||
|
public double AnguloEstimadoEm(DateTime momento, double maxExtrapolacaoMs = 500)
|
||||||
{
|
{
|
||||||
if (MomentoUltimaLeitura == DateTime.MinValue)
|
if (MomentoUltimaLeitura == DateTime.MinValue)
|
||||||
return AnguloAtual;
|
return AnguloAtual;
|
||||||
|
|
||||||
double dtMs = (DateTime.Now - MomentoUltimaLeitura).TotalMilliseconds;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Durante referenciamento PulsosRecebidos trabalha rápido.
|
* Positivo:
|
||||||
* Não deixamos uma leitura velha continuar "viajando" indefinidamente.
|
* o evento aconteceu depois da última leitura de pulsos.
|
||||||
|
*
|
||||||
|
* Negativo:
|
||||||
|
* já recebemos uma leitura de pulsos posterior ao evento
|
||||||
|
* e estamos estimando para trás.
|
||||||
*/
|
*/
|
||||||
dtMs = Math.Max(0, Math.Min(dtMs, 150));
|
double dtMs = (momento - MomentoUltimaLeitura).TotalMilliseconds;
|
||||||
|
|
||||||
|
if (Math.Abs(dtMs) > maxExtrapolacaoMs)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[MKS057DModel] [AnguloEstimadoEm] [ADDR {EnderecoTx}] " +
|
||||||
|
$"Estimativa recusada | " +
|
||||||
|
$"delta={dtMs:F0}ms > limite={maxExtrapolacaoMs:F0}ms"
|
||||||
|
);
|
||||||
|
|
||||||
|
return double.NaN;
|
||||||
|
}
|
||||||
|
|
||||||
double estimado = AnguloAtual + (VelocidadeEstimada * dtMs);
|
double estimado = AnguloAtual + (VelocidadeEstimada * dtMs);
|
||||||
|
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog(
|
||||||
$"[MKS057DModel] [AnguloEstimado] [ADDR {EnderecoTx}] " +
|
$"[MKS057DModel] [AnguloEstimadoEm] [ADDR {EnderecoTx}] " +
|
||||||
$"{AnguloAtual:F2}° + ({VelocidadeEstimada:F4} * {dtMs:F0}ms) = {estimado:F2}°"
|
$"base={AnguloAtual:F3}° | " +
|
||||||
|
$"vel={VelocidadeEstimada:F5}°/ms | " +
|
||||||
|
$"delta={dtMs:F0}ms | " +
|
||||||
|
$"estimado={estimado:F3}°"
|
||||||
);
|
);
|
||||||
|
|
||||||
return estimado;
|
return estimado;
|
||||||
|
|
|
||||||
|
|
@ -496,73 +496,282 @@ namespace AgroBase.Models.Modules
|
||||||
* dos quatro conjuntos.
|
* dos quatro conjuntos.
|
||||||
*/
|
*/
|
||||||
private const double FolgaMinimaReferencia = 0.05;
|
private const double FolgaMinimaReferencia = 0.05;
|
||||||
|
private const double FolgaAlertaReferencia = 6.0;
|
||||||
private const double FolgaMaximaReferencia = 8.0;
|
private const double FolgaMaximaReferencia = 8.0;
|
||||||
private static readonly TimeSpan TimeoutPrimeiraBorda = TimeSpan.FromSeconds(120);
|
private static readonly TimeSpan TimeoutPrimeiraBorda = TimeSpan.FromSeconds(120);
|
||||||
private static readonly TimeSpan TimeoutSegundaBorda = TimeSpan.FromSeconds(30);
|
private static readonly TimeSpan TimeoutSegundaBorda = TimeSpan.FromSeconds(30);
|
||||||
private static readonly TimeSpan TimeoutCentro = TimeSpan.FromSeconds(30);
|
private static readonly TimeSpan TimeoutCentro = TimeSpan.FromSeconds(30);
|
||||||
|
private const int TentativasLeituraReferencia = 3;
|
||||||
|
private const int TimeoutLeituraReferenciaMs = 900;
|
||||||
|
private const int EsperaEntreTentativasLeituraMs = 80;
|
||||||
|
private const double DeltaIdealIoPulsoMs = 350;
|
||||||
|
private const double DeltaMaximoIoPulsoMs = 500;
|
||||||
|
private const int TentativasReferenciamento = 3;
|
||||||
|
private const int EsperaEntreTentativasReferenciaMs = 500;
|
||||||
|
private bool _falhaCriticaReferenciamento = false;
|
||||||
|
private const double MovimentoMaximoConsideradoParado = 0.12;
|
||||||
|
private const int TentativasParadaReferencia = 3;
|
||||||
|
private static readonly TimeSpan IntervaloConfirmacaoParada = TimeSpan.FromMilliseconds(250);
|
||||||
|
|
||||||
private async Task<bool> AtualizarPulsosAgoraAsync(int timeoutMs = 800)
|
private async Task<bool> AtualizarPulsosAgoraAsync(int timeoutMs = TimeoutLeituraReferenciaMs, int tentativas = TentativasLeituraReferencia)
|
||||||
|
{
|
||||||
|
for (int tentativa = 1;
|
||||||
|
tentativa <= tentativas;
|
||||||
|
tentativa++)
|
||||||
|
{
|
||||||
|
if (!Inicializado || Leitura == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DateTime leituraAnterior =
|
||||||
|
Leitura.NumeroPulsos.lidoEm;
|
||||||
|
|
||||||
|
MKS057DCanService.RequisitarDadoForcado(
|
||||||
|
_EnderecoCAN_Tx,
|
||||||
|
_EnderecoCAN_Rx,
|
||||||
|
MKS057DCanService.MksHandler.MksFuncCode.PulsosRecebidos
|
||||||
|
);
|
||||||
|
|
||||||
|
bool NovaLeitura()
|
||||||
|
{
|
||||||
|
var l = Leitura;
|
||||||
|
|
||||||
|
return
|
||||||
|
l != null &&
|
||||||
|
l.NumeroPulsos.lidoEm > leituraAnterior;
|
||||||
|
}
|
||||||
|
|
||||||
|
await FuncoesGlobais.AguardarCondicaoAsync(
|
||||||
|
NovaLeitura,
|
||||||
|
timeoutMs,
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
if (NovaLeitura())
|
||||||
|
{
|
||||||
|
if (tentativa > 1)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"PULSOS recuperados na tentativa " +
|
||||||
|
$"{tentativa}/{tentativas} | " +
|
||||||
|
$"dt={Leitura.NumeroPulsos.dt * 1000.0:F0}ms | " +
|
||||||
|
$"freq={Leitura.NumeroPulsos.frequencia:F1}Hz"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"PULSOS sem resposta nova | " +
|
||||||
|
$"tentativa={tentativa}/{tentativas} | " +
|
||||||
|
$"idade={ObterIdadePulsosMs():F0}ms"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tentativa < tentativas)
|
||||||
|
await Task.Delay(EsperaEntreTentativasLeituraMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<bool> AtualizarIOAgoraAsync(int timeoutMs = TimeoutLeituraReferenciaMs, int tentativas = TentativasLeituraReferencia)
|
||||||
|
{
|
||||||
|
for (int tentativa = 1;
|
||||||
|
tentativa <= tentativas;
|
||||||
|
tentativa++)
|
||||||
|
{
|
||||||
|
if (!Inicializado || Leitura == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
DateTime leituraAnterior =
|
||||||
|
Leitura.EntradaIN1.lidoEm;
|
||||||
|
|
||||||
|
MKS057DCanService.RequisitarDadoForcado(
|
||||||
|
_EnderecoCAN_Tx,
|
||||||
|
_EnderecoCAN_Rx,
|
||||||
|
MKS057DCanService.MksHandler.MksFuncCode.IO
|
||||||
|
);
|
||||||
|
|
||||||
|
bool NovaLeitura()
|
||||||
|
{
|
||||||
|
var l = Leitura;
|
||||||
|
|
||||||
|
return
|
||||||
|
l != null &&
|
||||||
|
l.EntradaIN1.lidoEm > leituraAnterior;
|
||||||
|
}
|
||||||
|
|
||||||
|
await FuncoesGlobais.AguardarCondicaoAsync(
|
||||||
|
NovaLeitura,
|
||||||
|
timeoutMs,
|
||||||
|
20
|
||||||
|
);
|
||||||
|
|
||||||
|
if (NovaLeitura())
|
||||||
|
{
|
||||||
|
if (tentativa > 1)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"IO recuperado na tentativa " +
|
||||||
|
$"{tentativa}/{tentativas} | " +
|
||||||
|
$"IN1={IN1_Atuado} | " +
|
||||||
|
$"dt={Leitura.EntradaIN1.dt * 1000.0:F0}ms | " +
|
||||||
|
$"freq={Leitura.EntradaIN1.frequencia:F1}Hz"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
double idade =
|
||||||
|
Leitura.EntradaIN1.lidoEm == DateTime.MinValue
|
||||||
|
? double.PositiveInfinity
|
||||||
|
: (DateTime.Now -
|
||||||
|
Leitura.EntradaIN1.lidoEm)
|
||||||
|
.TotalMilliseconds;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"IO sem resposta nova | " +
|
||||||
|
$"tentativa={tentativa}/{tentativas} | " +
|
||||||
|
$"idade={idade:F0}ms"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (tentativa < tentativas)
|
||||||
|
await Task.Delay(EsperaEntreTentativasLeituraMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private double ObterIdadePulsosMs()
|
||||||
{
|
{
|
||||||
var leitura = Leitura;
|
var leitura = Leitura;
|
||||||
|
|
||||||
if (leitura == null || !Inicializado)
|
if (leitura == null ||
|
||||||
return false;
|
leitura.NumeroPulsos.lidoEm == DateTime.MinValue)
|
||||||
|
|
||||||
DateTime leituraAnterior = leitura.NumeroPulsos.lidoEm;
|
|
||||||
|
|
||||||
MKS057DCanService.RequisitarDadoForcado(_EnderecoCAN_Tx, _EnderecoCAN_Rx, MKS057DCanService.MksHandler.MksFuncCode.PulsosRecebidos);
|
|
||||||
|
|
||||||
bool NovaLeitura()
|
|
||||||
{
|
{
|
||||||
var l = Leitura;
|
return double.PositiveInfinity;
|
||||||
|
|
||||||
return
|
|
||||||
l != null &&
|
|
||||||
l.NumeroPulsos.lidoEm > leituraAnterior;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await FuncoesGlobais.AguardarCondicaoAsync(NovaLeitura, timeoutMs, 20);
|
return
|
||||||
|
(DateTime.Now -
|
||||||
return NovaLeitura();
|
leitura.NumeroPulsos.lidoEm)
|
||||||
|
.TotalMilliseconds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<bool> AtualizarIOAgoraAsync(int timeoutMs = 800)
|
private async Task<double?> ObterAnguloNaBordaAsync(DateTime momentoIO, string etapa)
|
||||||
{
|
{
|
||||||
var leitura = Leitura;
|
if (!Inicializado || Leitura == null)
|
||||||
|
return null;
|
||||||
|
|
||||||
if (leitura == null || !Inicializado)
|
DateTime momentoPulsos =
|
||||||
return false;
|
Leitura.NumeroPulsos.lidoEm;
|
||||||
|
|
||||||
DateTime leituraAnterior = leitura.EntradaIN1.lidoEm;
|
double deltaIoPulsoMs =
|
||||||
|
momentoPulsos == DateTime.MinValue
|
||||||
|
? double.PositiveInfinity
|
||||||
|
: (momentoIO - momentoPulsos)
|
||||||
|
.TotalMilliseconds;
|
||||||
|
|
||||||
MKS057DCanService.RequisitarDadoForcado(_EnderecoCAN_Tx, _EnderecoCAN_Rx, MKS057DCanService.MksHandler.MksFuncCode.IO);
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: sincronismo inicial | " +
|
||||||
|
$"IO={momentoIO:HH:mm:ss.fff} | " +
|
||||||
|
$"PULSOS={momentoPulsos:HH:mm:ss.fff} | " +
|
||||||
|
$"delta_io_pulso={deltaIoPulsoMs:F0}ms | " +
|
||||||
|
$"idade_pulsos_agora={ObterIdadePulsosMs():F0}ms | " +
|
||||||
|
$"dt_pulsos={Leitura.NumeroPulsos.dt * 1000.0:F0}ms | " +
|
||||||
|
$"freq_pulsos={Leitura.NumeroPulsos.frequencia:F1}Hz"
|
||||||
|
);
|
||||||
|
|
||||||
bool NovaLeitura()
|
/*
|
||||||
|
* A posição está relativamente próxima do evento.
|
||||||
|
* Podemos estimar diretamente.
|
||||||
|
*/
|
||||||
|
if (Math.Abs(deltaIoPulsoMs) <= DeltaIdealIoPulsoMs)
|
||||||
{
|
{
|
||||||
var l = Leitura;
|
double angulo =
|
||||||
|
Leitura.AnguloEstimadoEm(
|
||||||
|
momentoIO,
|
||||||
|
DeltaMaximoIoPulsoMs
|
||||||
|
);
|
||||||
|
|
||||||
return
|
if (!double.IsNaN(angulo))
|
||||||
l != null &&
|
return angulo;
|
||||||
l.EntradaIN1.lidoEm > leituraAnterior;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await FuncoesGlobais.AguardarCondicaoAsync(NovaLeitura, timeoutMs, 20);
|
/*
|
||||||
|
* A última posição ficou velha demais.
|
||||||
|
*
|
||||||
|
* Não descartamos imediatamente a borda.
|
||||||
|
* Buscamos uma leitura nova de pulsos e então estimamos
|
||||||
|
* para trás até o timestamp exato do IO.
|
||||||
|
*/
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: posição distante do evento; " +
|
||||||
|
$"solicitando PULSOS para reconstruir a borda."
|
||||||
|
);
|
||||||
|
|
||||||
return NovaLeitura();
|
bool atualizou =
|
||||||
}
|
await AtualizarPulsosAgoraAsync(
|
||||||
|
timeoutMs: 600,
|
||||||
|
tentativas: 2
|
||||||
|
);
|
||||||
|
|
||||||
private bool PulsosEstaoRecentes(double maxIdadeMs = 200)
|
if (!atualizou || Leitura == null)
|
||||||
{
|
|
||||||
var leitura = Leitura;
|
|
||||||
|
|
||||||
if (leitura == null || leitura.NumeroPulsos.lidoEm == DateTime.MinValue)
|
|
||||||
{
|
{
|
||||||
return false;
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: não foi possível obter pulsos " +
|
||||||
|
$"para reconstruir a borda."
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (DateTime.Now - leitura.NumeroPulsos.lidoEm).TotalMilliseconds <= maxIdadeMs;
|
momentoPulsos =
|
||||||
}
|
Leitura.NumeroPulsos.lidoEm;
|
||||||
|
|
||||||
|
deltaIoPulsoMs =
|
||||||
|
(momentoIO - momentoPulsos)
|
||||||
|
.TotalMilliseconds;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: sincronismo após atualização | " +
|
||||||
|
$"delta_io_pulso={deltaIoPulsoMs:F0}ms | " +
|
||||||
|
$"angulo_pulsos={AnguloDriver:F3}°"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (Math.Abs(deltaIoPulsoMs) >
|
||||||
|
DeltaMaximoIoPulsoMs)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: borda recusada | " +
|
||||||
|
$"|delta_io_pulso|={Math.Abs(deltaIoPulsoMs):F0}ms > " +
|
||||||
|
$"{DeltaMaximoIoPulsoMs:F0}ms"
|
||||||
|
);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
double estimado =
|
||||||
|
Leitura.AnguloEstimadoEm(
|
||||||
|
momentoIO,
|
||||||
|
DeltaMaximoIoPulsoMs
|
||||||
|
);
|
||||||
|
|
||||||
|
if (double.IsNaN(estimado))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return estimado;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public async Task<bool> ReferenciaMotor()
|
public async Task<bool> ReferenciaMotor()
|
||||||
|
|
@ -579,71 +788,201 @@ namespace AgroBase.Models.Modules
|
||||||
Referenciando = true;
|
Referenciando = true;
|
||||||
Referenciado = false;
|
Referenciado = false;
|
||||||
|
|
||||||
DateTime inicio =
|
DateTime inicioGeral =
|
||||||
DateTime.Now;
|
DateTime.Now;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
/*
|
for (int tentativa = 1;
|
||||||
* Atualmente o produto usa somente referência relativa MKS.
|
tentativa <= TentativasReferenciamento;
|
||||||
*
|
tentativa++)
|
||||||
* Quando o sensor absoluto voltar para o produto,
|
|
||||||
* tratamos o outro fluxo com o mesmo rigor.
|
|
||||||
*/
|
|
||||||
bool sucesso =
|
|
||||||
await ReferenciaMotorRelativo();
|
|
||||||
|
|
||||||
Referenciado =
|
|
||||||
sucesso;
|
|
||||||
|
|
||||||
if (!sucesso)
|
|
||||||
{
|
{
|
||||||
|
_falhaCriticaReferenciamento = false;
|
||||||
|
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog(
|
||||||
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
||||||
$"Referenciamento FALHOU."
|
$"===== TENTATIVA {tentativa}/{TentativasReferenciamento} ====="
|
||||||
);
|
);
|
||||||
|
|
||||||
op.Sensoriamento.InserirLog(
|
DateTime inicioTentativa =
|
||||||
T_Code.Dir,
|
DateTime.Now;
|
||||||
StatusModulo.Falha,
|
|
||||||
0,
|
|
||||||
"Referenciamento Falhou!",
|
|
||||||
id: Mod_ID
|
|
||||||
);
|
|
||||||
|
|
||||||
return false;
|
bool sucesso =
|
||||||
|
await ReferenciaMotorRelativo();
|
||||||
|
|
||||||
|
if (sucesso)
|
||||||
|
{
|
||||||
|
Referenciado = true;
|
||||||
|
|
||||||
|
UltimoReferenciamento =
|
||||||
|
DateTime.Now;
|
||||||
|
|
||||||
|
MKS057DCanService.UltimoReferenciamento =
|
||||||
|
UltimoReferenciamento;
|
||||||
|
|
||||||
|
double tempoTentativa =
|
||||||
|
(DateTime.Now - inicioTentativa)
|
||||||
|
.TotalSeconds;
|
||||||
|
|
||||||
|
double tempoTotal =
|
||||||
|
(DateTime.Now - inicioGeral)
|
||||||
|
.TotalSeconds;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
||||||
|
$"REFERENCIAMENTO CONCLUÍDO E CONFIRMADO | " +
|
||||||
|
$"tentativa={tentativa}/{TentativasReferenciamento} | " +
|
||||||
|
$"tempo_tentativa={tempoTentativa:F1}s | " +
|
||||||
|
$"tempo_total={tempoTotal:F1}s | " +
|
||||||
|
$"folga={FolgaMecanica:F3}°"
|
||||||
|
);
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(
|
||||||
|
T_Code.Dir,
|
||||||
|
StatusModulo.Operante,
|
||||||
|
100,
|
||||||
|
$"Referenciamento concluído na tentativa " +
|
||||||
|
$"{tentativa}/{TentativasReferenciamento}.",
|
||||||
|
id: Mod_ID
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Referenciado = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Driver desapareceu durante o processo.
|
||||||
|
*
|
||||||
|
* Não faz sentido ficar tentando referência
|
||||||
|
* sem comunicação.
|
||||||
|
*/
|
||||||
|
if (!Inicializado)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
||||||
|
$"Retry CANCELADO: driver não está inicializado."
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Falha física/lógica grave.
|
||||||
|
*
|
||||||
|
* Exemplo: eixo movimentou no sentido contrário
|
||||||
|
* ao comando.
|
||||||
|
*/
|
||||||
|
if (_falhaCriticaReferenciamento)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
||||||
|
$"Retry CANCELADO por falha crítica."
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tentativa <
|
||||||
|
TentativasReferenciamento)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
||||||
|
$"Tentativa {tentativa} falhou. " +
|
||||||
|
$"Preparando retry automático..."
|
||||||
|
);
|
||||||
|
|
||||||
|
bool parou =
|
||||||
|
await PararEConfirmarAsync(
|
||||||
|
$"RETRY_{tentativa}_PARADA"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!parou)
|
||||||
|
{
|
||||||
|
_falhaCriticaReferenciamento = true;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
||||||
|
$"Retry CANCELADO: não foi possível " +
|
||||||
|
$"comprovar parada do eixo."
|
||||||
|
);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Task.Delay(
|
||||||
|
EsperaEntreTentativasReferenciaMs
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Pequeno precheck antes de começar outra tentativa.
|
||||||
|
*/
|
||||||
|
bool ioOk =
|
||||||
|
await AtualizarIOAgoraAsync(
|
||||||
|
timeoutMs: 700,
|
||||||
|
tentativas: 2
|
||||||
|
);
|
||||||
|
|
||||||
|
bool pulsosOk =
|
||||||
|
await AtualizarPulsosAgoraAsync(
|
||||||
|
timeoutMs: 700,
|
||||||
|
tentativas: 2
|
||||||
|
);
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
||||||
|
$"PRECHECK RETRY | " +
|
||||||
|
$"IO={ioOk} | " +
|
||||||
|
$"PULSOS={pulsosOk} | " +
|
||||||
|
$"IN1={IN1_Atuado} | " +
|
||||||
|
$"angulo={AnguloDriver:F3}°"
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!ioOk ||
|
||||||
|
!pulsosOk)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
||||||
|
$"Precheck do retry incompleto. " +
|
||||||
|
$"A próxima tentativa ainda fará seu próprio precheck."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UltimoReferenciamento =
|
double tempoTotalFalha =
|
||||||
DateTime.Now;
|
(DateTime.Now - inicioGeral)
|
||||||
|
|
||||||
/*
|
|
||||||
* Se essa propriedade global ainda for usada em algum lugar,
|
|
||||||
* agora ela representa um referenciamento REALMENTE concluído.
|
|
||||||
*/
|
|
||||||
MKS057DCanService.UltimoReferenciamento =
|
|
||||||
UltimoReferenciamento;
|
|
||||||
|
|
||||||
double tempo =
|
|
||||||
(DateTime.Now - inicio)
|
|
||||||
.TotalSeconds;
|
.TotalSeconds;
|
||||||
|
|
||||||
|
bool paradaFinal =
|
||||||
|
await PararEConfirmarAsync(
|
||||||
|
"FALHA_FINAL_PARADA"
|
||||||
|
);
|
||||||
|
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog(
|
||||||
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
||||||
$"Referenciamento CONCLUÍDO E CONFIRMADO | " +
|
$"Estado seguro após falha | " +
|
||||||
$"tempo={tempo:F1}s | " +
|
$"parada_confirmada={paradaFinal} | " +
|
||||||
$"folga={FolgaMecanica:F3}°"
|
$"angulo={AnguloDriver:F3}°"
|
||||||
|
);
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [ReferenciaMotor] [DIR {Mod_ID}] " +
|
||||||
|
$"REFERENCIAMENTO FALHOU DEFINITIVAMENTE | " +
|
||||||
|
$"tentativas={TentativasReferenciamento} | " +
|
||||||
|
$"tempo={tempoTotalFalha:F1}s"
|
||||||
);
|
);
|
||||||
|
|
||||||
op.Sensoriamento.InserirLog(
|
op.Sensoriamento.InserirLog(
|
||||||
T_Code.Dir,
|
T_Code.Dir,
|
||||||
StatusModulo.Operante,
|
StatusModulo.Falha,
|
||||||
100,
|
0,
|
||||||
"Referenciamento Concluído!",
|
$"Referenciamento falhou após " +
|
||||||
|
$"{TentativasReferenciamento} tentativa(s).",
|
||||||
id: Mod_ID
|
id: Mod_ID
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return false;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -837,21 +1176,27 @@ namespace AgroBase.Models.Modules
|
||||||
* ============================================================
|
* ============================================================
|
||||||
*/
|
*/
|
||||||
|
|
||||||
double folga =
|
double folga = Math.Abs(borda1.Value - borda2.Value);
|
||||||
Math.Abs(
|
|
||||||
borda1.Value -
|
|
||||||
borda2.Value
|
|
||||||
);
|
|
||||||
|
|
||||||
if (folga < FolgaMinimaReferencia ||
|
if (folga < FolgaMinimaReferencia || folga > FolgaMaximaReferencia)
|
||||||
folga > FolgaMaximaReferencia)
|
|
||||||
{
|
{
|
||||||
return FalharReferencia(
|
return FalharReferencia(
|
||||||
$"Folga medida fora da faixa esperada: {folga:F3}° " +
|
$"Folga medida fora da faixa física admissível: {folga:F3}° " +
|
||||||
$"(esperado {FolgaMinimaReferencia:F2}..{FolgaMaximaReferencia:F2}°)."
|
$"(esperado {FolgaMinimaReferencia:F2}..{FolgaMaximaReferencia:F2}°)."
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (folga > FolgaAlertaReferencia)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"ALERTA DE FOLGA ELEVADA | " +
|
||||||
|
$"folga={folga:F3}° | " +
|
||||||
|
$"limite_alerta={FolgaAlertaReferencia:F2}° | " +
|
||||||
|
$"limite_max={FolgaMaximaReferencia:F2}°"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
double centro =
|
double centro =
|
||||||
((borda1.Value + borda2.Value) / 2.0)
|
((borda1.Value + borda2.Value) / 2.0)
|
||||||
+ OffsetReferenciamento;
|
+ OffsetReferenciamento;
|
||||||
|
|
@ -951,43 +1296,65 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
private async Task<bool> SetZeroConfirmadoAsync(string etapa, int tentativas = 3)
|
private async Task<bool> SetZeroConfirmadoAsync(string etapa, int tentativas = 3)
|
||||||
{
|
{
|
||||||
for (int tentativa = 1; tentativa <= tentativas; tentativa++)
|
/*
|
||||||
|
* ZERO somente com motor comprovadamente imóvel.
|
||||||
|
*/
|
||||||
|
if (!await PararEConfirmarAsync(
|
||||||
|
etapa + "_PRE_ZERO"))
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: SetZero ABORTADO porque " +
|
||||||
|
$"a parada não foi confirmada."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int tentativa = 1;
|
||||||
|
tentativa <= tentativas;
|
||||||
|
tentativa++)
|
||||||
{
|
{
|
||||||
if (!Inicializado)
|
if (!Inicializado)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog(
|
||||||
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
$"{etapa}: SetZero tentativa {tentativa}/{tentativas}"
|
$"{etapa}: SetZero tentativa " +
|
||||||
|
$"{tentativa}/{tentativas}"
|
||||||
);
|
);
|
||||||
|
|
||||||
MKS057DCanService.SetZero(_EnderecoCAN_Tx, _EnderecoCAN_Rx);
|
MKS057DCanService.SetZero(
|
||||||
|
_EnderecoCAN_Tx,
|
||||||
|
_EnderecoCAN_Rx
|
||||||
|
);
|
||||||
|
|
||||||
/*
|
|
||||||
* Deixa o comando entrar na fila/processar.
|
|
||||||
* Depois solicitamos explicitamente uma posição nova.
|
|
||||||
*/
|
|
||||||
await Task.Delay(100);
|
await Task.Delay(100);
|
||||||
|
|
||||||
bool recebeuPulsos = await AtualizarPulsosAgoraAsync();
|
bool recebeuPulsos =
|
||||||
|
await AtualizarPulsosAgoraAsync();
|
||||||
|
|
||||||
if (!recebeuPulsos)
|
if (!recebeuPulsos)
|
||||||
{
|
{
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog(
|
||||||
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
$"{etapa}: nenhuma leitura nova de pulsos após SetZero."
|
$"{etapa}: nenhuma leitura nova " +
|
||||||
|
$"de pulsos após SetZero."
|
||||||
);
|
);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
double angulo = AnguloDriver;
|
double angulo =
|
||||||
|
AnguloDriver;
|
||||||
|
|
||||||
if (Math.Abs(angulo) <= ToleranciaZeroReferenciamento)
|
if (Math.Abs(angulo) <=
|
||||||
|
ToleranciaZeroReferenciamento)
|
||||||
{
|
{
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog(
|
||||||
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
$"{etapa}: SetZero CONFIRMADO | angulo={angulo:F3}°"
|
$"{etapa}: SetZero CONFIRMADO | " +
|
||||||
|
$"angulo={angulo:F3}°"
|
||||||
);
|
);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -995,7 +1362,8 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog(
|
||||||
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
$"{etapa}: SetZero NÃO confirmado | angulo={angulo:F3}°"
|
$"{etapa}: SetZero NÃO confirmado | " +
|
||||||
|
$"angulo={angulo:F3}°"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1068,10 +1436,14 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
if (Math.Sign(delta) != Math.Sign(deltaEsperado))
|
if (Math.Sign(delta) != Math.Sign(deltaEsperado))
|
||||||
{
|
{
|
||||||
|
_falhaCriticaReferenciamento = true;
|
||||||
|
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog(
|
||||||
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
$"{etapa}: movimento no sentido INESPERADO | " +
|
$"{etapa}: movimento no sentido INESPERADO | " +
|
||||||
$"delta={delta:F3}° esperado={Math.Sign(deltaEsperado)}"
|
$"delta={delta:F3}° | " +
|
||||||
|
$"delta_esperado={deltaEsperado:F3}° | " +
|
||||||
|
$"FALHA CRÍTICA, retry bloqueado"
|
||||||
);
|
);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -1093,6 +1465,174 @@ namespace AgroBase.Models.Modules
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<bool> PararEConfirmarAsync(string etapa, int tentativas = TentativasParadaReferencia)
|
||||||
|
{
|
||||||
|
if (!Inicializado || Leitura == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
for (int tentativa = 1;
|
||||||
|
tentativa <= tentativas;
|
||||||
|
tentativa++)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Primeiro tentamos o comando de parada normal do protocolo.
|
||||||
|
*/
|
||||||
|
MKS057DCanService.Parar(
|
||||||
|
_EnderecoCAN_Tx,
|
||||||
|
_EnderecoCAN_Rx
|
||||||
|
);
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: PARAR enviado | " +
|
||||||
|
$"tentativa={tentativa}/{tentativas}"
|
||||||
|
);
|
||||||
|
|
||||||
|
await Task.Delay(150);
|
||||||
|
|
||||||
|
if (!await AtualizarPulsosAgoraAsync(
|
||||||
|
timeoutMs: 600,
|
||||||
|
tentativas: 2))
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: sem posição após PARAR."
|
||||||
|
);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double angulo1 =
|
||||||
|
AnguloDriver;
|
||||||
|
|
||||||
|
await Task.Delay(
|
||||||
|
IntervaloConfirmacaoParada
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!await AtualizarPulsosAgoraAsync(
|
||||||
|
timeoutMs: 600,
|
||||||
|
tentativas: 2))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double angulo2 =
|
||||||
|
AnguloDriver;
|
||||||
|
|
||||||
|
double deslocamento =
|
||||||
|
Math.Abs(
|
||||||
|
angulo2 -
|
||||||
|
angulo1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deslocamento <=
|
||||||
|
MovimentoMaximoConsideradoParado)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: PARADA CONFIRMADA | " +
|
||||||
|
$"a1={angulo1:F3}° | " +
|
||||||
|
$"a2={angulo2:F3}° | " +
|
||||||
|
$"delta={deslocamento:F3}°"
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: motor ainda em movimento após PARAR | " +
|
||||||
|
$"a1={angulo1:F3}° | " +
|
||||||
|
$"a2={angulo2:F3}° | " +
|
||||||
|
$"delta={deslocamento:F3}°"
|
||||||
|
);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Fallback robusto:
|
||||||
|
*
|
||||||
|
* se o "Parar" do protocolo não matou a trajetória,
|
||||||
|
* substituímos o alvo antigo pela posição atual.
|
||||||
|
*
|
||||||
|
* Sabemos que o modo de posição absoluta funciona.
|
||||||
|
*/
|
||||||
|
double anguloHold =
|
||||||
|
angulo2;
|
||||||
|
|
||||||
|
MKS057DCanService.Movimento(
|
||||||
|
_EnderecoCAN_Tx,
|
||||||
|
_EnderecoCAN_Rx,
|
||||||
|
anguloHold,
|
||||||
|
VelocidadeReferenciamento,
|
||||||
|
Sentido.Parado
|
||||||
|
);
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: HOLD POSITION enviado | " +
|
||||||
|
$"alvo={anguloHold:F3}°"
|
||||||
|
);
|
||||||
|
|
||||||
|
await Task.Delay(250);
|
||||||
|
|
||||||
|
if (!await AtualizarPulsosAgoraAsync(
|
||||||
|
timeoutMs: 600,
|
||||||
|
tentativas: 2))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double hold1 =
|
||||||
|
AnguloDriver;
|
||||||
|
|
||||||
|
await Task.Delay(
|
||||||
|
IntervaloConfirmacaoParada
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!await AtualizarPulsosAgoraAsync(
|
||||||
|
timeoutMs: 600,
|
||||||
|
tentativas: 2))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
double hold2 =
|
||||||
|
AnguloDriver;
|
||||||
|
|
||||||
|
double deslocamentoHold =
|
||||||
|
Math.Abs(
|
||||||
|
hold2 -
|
||||||
|
hold1
|
||||||
|
);
|
||||||
|
|
||||||
|
if (deslocamentoHold <=
|
||||||
|
MovimentoMaximoConsideradoParado)
|
||||||
|
{
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: PARADA CONFIRMADA VIA HOLD | " +
|
||||||
|
$"a1={hold1:F3}° | " +
|
||||||
|
$"a2={hold2:F3}° | " +
|
||||||
|
$"delta={deslocamentoHold:F3}°"
|
||||||
|
);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: HOLD não estabilizou o eixo | " +
|
||||||
|
$"delta={deslocamentoHold:F3}°"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
$"{etapa}: NÃO foi possível confirmar parada."
|
||||||
|
);
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private async Task<double?> AguardarTransicaoIN1Async(bool estadoOrigem, double anguloLimite, TimeSpan timeout, string etapa)
|
private async Task<double?> AguardarTransicaoIN1Async(bool estadoOrigem, double anguloLimite, TimeSpan timeout, string etapa)
|
||||||
{
|
{
|
||||||
DateTime limite = DateTime.Now.Add(timeout);
|
DateTime limite = DateTime.Now.Add(timeout);
|
||||||
|
|
@ -1132,7 +1672,7 @@ namespace AgroBase.Models.Modules
|
||||||
* Se chegou ao limite de busca sem mudar IN1,
|
* Se chegou ao limite de busca sem mudar IN1,
|
||||||
* não existe motivo para continuar esperando.
|
* não existe motivo para continuar esperando.
|
||||||
*/
|
*/
|
||||||
if (PulsosEstaoRecentes(300) && Math.Abs(AnguloDriver - anguloLimite) <= 1.0)
|
if (ObterIdadePulsosMs() <= DeltaMaximoIoPulsoMs && Math.Abs(AnguloDriver - anguloLimite) <= 1.0)
|
||||||
{
|
{
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog(
|
||||||
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
|
|
@ -1152,21 +1692,37 @@ namespace AgroBase.Models.Modules
|
||||||
* Só aceitamos a borda se a posição estiver recente.
|
* Só aceitamos a borda se a posição estiver recente.
|
||||||
* Nada de calcular referência com dado velho.
|
* Nada de calcular referência com dado velho.
|
||||||
*/
|
*/
|
||||||
if (!PulsosEstaoRecentes())
|
/*
|
||||||
|
* IMPORTANTE:
|
||||||
|
*
|
||||||
|
* Capturamos primeiro o timestamp EXATO da leitura
|
||||||
|
* em que detectamos a mudança do IN1.
|
||||||
|
*/
|
||||||
|
DateTime ioCandidato =
|
||||||
|
Leitura.EntradaIN1.lidoEm;
|
||||||
|
|
||||||
|
bool estadoCandidato =
|
||||||
|
estadoAtual;
|
||||||
|
|
||||||
|
double? anguloBorda =
|
||||||
|
await ObterAnguloNaBordaAsync(
|
||||||
|
ioCandidato,
|
||||||
|
etapa
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!anguloBorda.HasValue)
|
||||||
{
|
{
|
||||||
Variaveis.MostrarLog(
|
Variaveis.MostrarLog(
|
||||||
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
$"[DirecionalModel] [REFERENCIA] [DIR {Mod_ID}] " +
|
||||||
$"{etapa}: IN1 mudou, mas posição está desatualizada."
|
$"{etapa}: transição válida de IN1, " +
|
||||||
|
$"mas não foi possível reconstruir posição confiável."
|
||||||
);
|
);
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
double anguloCandidato = Leitura.AnguloEstimado();
|
double anguloCandidato =
|
||||||
|
anguloBorda.Value;
|
||||||
bool estadoCandidato = estadoAtual;
|
|
||||||
|
|
||||||
DateTime ioCandidato = Leitura.EntradaIN1.lidoEm;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Debounce lógico:
|
* Debounce lógico:
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace AgroBase.Models.Modules
|
||||||
return Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos?.FirstOrDefault(x => x.MovMotor._EnderecoCAN_Tx == _EnderecoCAN_Tx)?.Modulo_ID ?? "";
|
return Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos?.FirstOrDefault(x => x.MovMotor._EnderecoCAN_Tx == _EnderecoCAN_Tx)?.Modulo_ID ?? "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
public double LeituraRPM { get; set; } = 0;
|
public double UltimoRPMComandado { get; set; } = 0;
|
||||||
public bool ComandoLigado
|
public bool ComandoLigado
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -304,65 +304,7 @@ namespace AgroBase.Models.Modules
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CompensacaoNecessaria
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (ComandoLigado)
|
|
||||||
{
|
|
||||||
var op = Variaveis.OperacaoEmAndamento;
|
|
||||||
|
|
||||||
var _Controle = op.Controle;
|
|
||||||
var _ControleDir = _Controle.TiposControle.FirstOrDefault(x => x.Tipo == T_Code.Dir);
|
|
||||||
|
|
||||||
bool movimentoCompensado =
|
|
||||||
(_Controle.TipoMovimento == TipoMovimentoDirecional.MovimentoArco) ||
|
|
||||||
(_Controle.TipoMovimento == TipoMovimentoDirecional.RodasDianteiras && Mod_ID.Contains("F")) ||
|
|
||||||
(_Controle.TipoMovimento == TipoMovimentoDirecional.RodasTraseiras && Mod_ID.Contains("T"));
|
|
||||||
bool emCurva = _ControleDir.UltimaDirecao != Direcao.Parado;
|
|
||||||
double anguloDir = Math.Abs(op.DispMvd?.Dados?.Modulos?.FirstOrDefault(x => x.Modulo_ID == Mod_ID)?.DirMotor?.Angulo_SP ?? 0);
|
|
||||||
bool direcionalAtuado = anguloDir > 0.0;
|
|
||||||
|
|
||||||
if (movimentoCompensado && emCurva && direcionalAtuado)
|
|
||||||
{
|
|
||||||
if (
|
|
||||||
(_ControleDir.UltimaDirecao == Direcao.Esquerda && Mod_ID.Contains("D")) ||
|
|
||||||
(_ControleDir.UltimaDirecao == Direcao.Direita && Mod_ID.Contains("E"))
|
|
||||||
)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private float FatorCompensacaoCurvas
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (CompensacaoNecessaria)
|
|
||||||
{
|
|
||||||
//double compensacao = 1 + FuncoesMatematicas.Map(Math.Abs(Variaveis.OperacaoEmAndamento.Controle.Angulo), 0.0, Variaveis.OperacaoEmAndamento.Controle.Angulo_Max, 0.0, 0.3);
|
|
||||||
double compensacao = MovimentacaoMovimentoCompensadoModel.compensacoes[Mod_ID];
|
|
||||||
return (float)compensacao;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private float FatorCompensacaoCurvasAnterior { get; set; } = 1.0f;
|
|
||||||
public bool NovoRPMNecessario
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
return FatorCompensacaoCurvas != FatorCompensacaoCurvasAnterior;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Dictionary<TipoMovimentoDirecional, ConfiguracaoSentidoMotor> ConfigSentidos { get; set; }
|
public Dictionary<TipoMovimentoDirecional, ConfiguracaoSentidoMotor> ConfigSentidos { get; set; }
|
||||||
public List<FuncoesPinout> Funcoes { get; set; }
|
public List<FuncoesPinout> Funcoes { get; set; }
|
||||||
|
|
@ -377,6 +319,8 @@ namespace AgroBase.Models.Modules
|
||||||
public double UltimoSetPointEnviado { get; set; } = 0;
|
public double UltimoSetPointEnviado { get; set; } = 0;
|
||||||
public Direcao UltimaDirecao { get; set; } = Direcao.Parado;
|
public Direcao UltimaDirecao { get; set; } = Direcao.Parado;
|
||||||
public bool UltimoFreioSP { get; set; } = false;
|
public bool UltimoFreioSP { get; set; } = false;
|
||||||
|
private static readonly TimeSpan IntervaloReafirmacaoControle = TimeSpan.FromSeconds(1.5);
|
||||||
|
private const int DeltaMinimoRpmNovoComando = 3;
|
||||||
|
|
||||||
|
|
||||||
public static MovimentacaoModel CarregarParametrosIniciais(string Mod_ID, byte _EnderecoTx, byte _EnderecoRx)
|
public static MovimentacaoModel CarregarParametrosIniciais(string Mod_ID, byte _EnderecoTx, byte _EnderecoRx)
|
||||||
|
|
@ -504,14 +448,17 @@ namespace AgroBase.Models.Modules
|
||||||
}
|
}
|
||||||
|
|
||||||
RPM_SP_Controle = _Controle.RPM_Motor;
|
RPM_SP_Controle = _Controle.RPM_Motor;
|
||||||
RPM_SP =
|
|
||||||
DirecaoAtual == Direcao.Parado || Freado ? 0 :
|
|
||||||
RPM_SP_Controle;
|
|
||||||
|
|
||||||
int RpmCorrigido = Math.Min((int)(VariaveisEquipamento.RPM_Max_Roda * VariaveisEquipamento.RelacaoRPM), Math.Max(RPM_SP != 0 ? (int)(VariaveisEquipamento.RPM_Min_Roda * VariaveisEquipamento.RelacaoRPM) : 0, Convert.ToInt32(RPM_SP * FatorCompensacaoCurvas)));
|
if (DirecaoAtual == Direcao.Parado || Freado)
|
||||||
//Console.WriteLine($"[{Mod_ID}] Direcao: {DirecaoAtual}, RPM_SP: {RPM_SP / VariaveisEquipamento.RelacaoRPM}, FC: {FatorCompensacaoCurvas.ToString("0.00")}, Novo RPM: {RpmCorrigido / VariaveisEquipamento.RelacaoRPM}");
|
{
|
||||||
RPM_SP = Math.Abs(RpmCorrigido);
|
RPM_SP = 0;
|
||||||
FatorCompensacaoCurvasAnterior = FatorCompensacaoCurvas;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int rpmMinMotor = Convert.ToInt32(VariaveisEquipamento.RPM_Min_Roda * VariaveisEquipamento.RelacaoRPM);
|
||||||
|
int rpmMaxMotor = Convert.ToInt32(VariaveisEquipamento.RPM_Max_Roda * VariaveisEquipamento.RelacaoRPM);
|
||||||
|
RPM_SP = MovimentacaoMovimentoCompensadoModel.CalcularRpmMotor(Mod_ID, RPM_SP_Controle, rpmMinMotor, rpmMaxMotor);
|
||||||
|
}
|
||||||
|
|
||||||
op.SimulacaoRpmControle = _Controle.PercentualVelocidadeSP;
|
op.SimulacaoRpmControle = _Controle.PercentualVelocidadeSP;
|
||||||
//Console.WriteLine($"RPM Atualizado para {Variaveis.OperacaoEmAndamento.SimulacaoRpmControle}");
|
//Console.WriteLine($"RPM Atualizado para {Variaveis.OperacaoEmAndamento.SimulacaoRpmControle}");
|
||||||
|
|
@ -536,11 +483,20 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
bool PrimeiraLeitura = Leitura == null;
|
bool PrimeiraLeitura = Leitura == null;
|
||||||
bool SentidoAlterado = (Leitura?.Direcao ?? Sentido.Parado) != Sentido_SP;
|
bool SentidoAlterado = (Leitura?.Direcao ?? Sentido.Parado) != Sentido_SP;
|
||||||
bool RPMAlterado = LeituraRPM != RPM_SP;
|
bool RPMAlterado = Math.Abs(UltimoRPMComandado - RPM_SP) >= DeltaMinimoRpmNovoComando;
|
||||||
bool LigadoAlterado = LeituraLigado != ComandoLigado;
|
bool LigadoAlterado = LeituraLigado != ComandoLigado;
|
||||||
bool FreioAlterado = (Leitura?.Freado ?? false) != Freado;
|
bool FreioAlterado = (Leitura?.Freado ?? false) != Freado;
|
||||||
bool EnviaComando = PrimeiraLeitura || SentidoAlterado || RPMAlterado || LigadoAlterado || FreioAlterado;
|
|
||||||
|
|
||||||
|
bool ReafirmacaoNecessaria = UltimoComandoEnviado == DateTime.MinValue || DateTime.Now - UltimoComandoEnviado >= IntervaloReafirmacaoControle;
|
||||||
|
|
||||||
|
bool EnviaComando =
|
||||||
|
PrimeiraLeitura ||
|
||||||
|
SentidoAlterado ||
|
||||||
|
RPMAlterado ||
|
||||||
|
LigadoAlterado ||
|
||||||
|
FreioAlterado ||
|
||||||
|
ReafirmacaoNecessaria;
|
||||||
|
|
||||||
OidFuncCode modoControle =
|
OidFuncCode modoControle =
|
||||||
Freado ? OidFuncCode.ComandoCorrenteFreio :
|
Freado ? OidFuncCode.ComandoCorrenteFreio :
|
||||||
ComandoLigado ? OidFuncCode.ComandoVelocidade :
|
ComandoLigado ? OidFuncCode.ComandoVelocidade :
|
||||||
|
|
@ -565,7 +521,7 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
OIDCanService.ComandoControle(_EnderecoCAN_Tx, _EnderecoCAN_Rx, modoControle, setPoint);
|
OIDCanService.ComandoControle(_EnderecoCAN_Tx, _EnderecoCAN_Rx, modoControle, setPoint);
|
||||||
|
|
||||||
LeituraRPM = RPM_SP;
|
UltimoRPMComandado = RPM_SP;
|
||||||
UltimoComandoEnviado = DateTime.Now;
|
UltimoComandoEnviado = DateTime.Now;
|
||||||
UltimoModoControleEnviado = modoControle;
|
UltimoModoControleEnviado = modoControle;
|
||||||
UltimoSetPointEnviado = setPoint;
|
UltimoSetPointEnviado = setPoint;
|
||||||
|
|
@ -771,192 +727,558 @@ namespace AgroBase.Models.Modules
|
||||||
public double CicloTrabalho { get; set; }
|
public double CicloTrabalho { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MovimentacaoMovimentoCompensadoModel
|
public static class MovimentacaoMovimentoCompensadoModel
|
||||||
{
|
{
|
||||||
static List<TipoMovimentoDirecional> MovimentosCompensar = new List<TipoMovimentoDirecional>() { TipoMovimentoDirecional.RodasDianteiras, TipoMovimentoDirecional.RodasTraseiras, TipoMovimentoDirecional.MovimentoArco };
|
private static readonly object _sync = new object();
|
||||||
static GeometriaRobo g = new GeometriaRobo { L = VariaveisEquipamento.DistanciaEntreEixosCm / 100.0, W = VariaveisEquipamento.LarguraEquipamentoMm / 1000.0 };
|
|
||||||
public static Dictionary<string, double> compensacoes { get; set; } = new Dictionary<string, double>();
|
private static readonly HashSet<TipoMovimentoDirecional> ModosCompensados = new HashSet<TipoMovimentoDirecional>
|
||||||
public static Dictionary<string, double> _compAnt { get; set; }
|
{
|
||||||
|
TipoMovimentoDirecional.RodasDianteiras,
|
||||||
|
TipoMovimentoDirecional.RodasTraseiras,
|
||||||
|
TipoMovimentoDirecional.MovimentoArco
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Proteção contra geometria patológica.
|
||||||
|
* Depois podemos derivar esse teto exatamente do ângulo máximo
|
||||||
|
* permitido pelo rover.
|
||||||
|
*/
|
||||||
|
private const double FatorMaximo = 2.50;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tempo máximo para considerar posição do MKS fresca.
|
||||||
|
*/
|
||||||
|
private const double IdadeMaximaAnguloMs = 1000.0;
|
||||||
|
|
||||||
|
private static DateTime _ultimoCalculo = DateTime.MinValue;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Filtro no ÂNGULO, não no fator.
|
||||||
|
*
|
||||||
|
* Isso mantém todos os fatores pertencendo à mesma geometria.
|
||||||
|
*/
|
||||||
|
private const double TauFiltroAnguloSeg = 0.15;
|
||||||
|
|
||||||
|
private static double _angFrenteFiltrado;
|
||||||
|
private static double _angTrasFiltrado;
|
||||||
|
|
||||||
|
private static Dictionary<string, EstadoCompensacaoRoda> _estado = new Dictionary<string, EstadoCompensacaoRoda>();
|
||||||
|
|
||||||
|
private const double AnguloEntradaCurvaGraus = 1.0;
|
||||||
|
private const double AnguloSaidaCurvaGraus = 0.5;
|
||||||
|
private static bool _curvaAtiva;
|
||||||
|
|
||||||
|
|
||||||
|
public class EstadoCompensacaoRoda
|
||||||
|
{
|
||||||
|
public string ModuloId { get; set; }
|
||||||
|
public double RaioM { get; set; }
|
||||||
|
public double Fator { get; set; } = 1.0;
|
||||||
|
public bool Saturado { get; set; }
|
||||||
|
public double AnguloFrenteGraus { get; set; }
|
||||||
|
public double AnguloTrasGraus { get; set; }
|
||||||
|
public double IccX { get; set; }
|
||||||
|
public double IccY { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private struct GeometriaRobo
|
||||||
|
{
|
||||||
|
public double L;
|
||||||
|
public double W;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static void AtualizarDados()
|
public static void AtualizarDados()
|
||||||
{
|
{
|
||||||
var op = Variaveis.OperacaoEmAndamento;
|
var op = Variaveis.OperacaoEmAndamento;
|
||||||
|
var modulos = op?.DispMvd?.Dados?.Modulos;
|
||||||
|
|
||||||
var ctrl = op?.Controle;
|
if (modulos == null || modulos.Count == 0)
|
||||||
var disp = op?.DispMvd?.Dados?.Modulos;
|
|
||||||
if (op?.Sensoriamento?.Operacao?.StatusOperacaoAtual != StatusOperacao.EmAndamento || ctrl == null || disp == null || !MovimentosCompensar.Contains(ctrl.TipoMovimento))
|
|
||||||
{
|
{
|
||||||
compensacoes = (disp ?? Enumerable.Empty<ModuloMvdModel>()).ToDictionary(x => x.Modulo_ID, x => 1.0);
|
ResetarTudo();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Coleta ângulos dos módulos
|
var controle = op?.Controle;
|
||||||
var frente = disp.Where(m => m.Modulo_ID?.Contains("F") == true).Select(m => (double)(m.DirMotor?.Angulo_SP ?? 0) * (m.DirMotor.Sentido_SP == Sentido.Antihorario ? -1 : 1));
|
|
||||||
var tras = disp.Where(m => m.Modulo_ID?.Contains("T") == true).Select(m => (double)(m.DirMotor?.Angulo_SP ?? 0) * (m.DirMotor.Sentido_SP == Sentido.Antihorario ? 1 : -1));
|
|
||||||
|
|
||||||
// Ângulo equivalente do eixo (média em tan-space)
|
if (controle == null || !ModosCompensados.Contains(controle.TipoMovimento))
|
||||||
double angFrenteDeg = EqAngleEixo(frente);
|
|
||||||
double angTrasDeg = EqAngleEixo(tras);
|
|
||||||
|
|
||||||
compensacoes = FatoresVelocidade(
|
|
||||||
ctrl.TipoMovimento,
|
|
||||||
angFrenteDeg,
|
|
||||||
angTrasDeg,
|
|
||||||
g,
|
|
||||||
PoliticaFator.ReferenciaNoCentro,
|
|
||||||
fatorMaximo: 1.6
|
|
||||||
);
|
|
||||||
|
|
||||||
const double alpha = 0.2;
|
|
||||||
if (_compAnt != null && _compAnt.Count == compensacoes.Count)
|
|
||||||
compensacoes = compensacoes.ToDictionary(kv => kv.Key,
|
|
||||||
kv => _compAnt.TryGetValue(kv.Key, out var prev) ? prev * (1 - alpha) + kv.Value * alpha : kv.Value);
|
|
||||||
_compAnt = new Dictionary<string, double>(compensacoes);
|
|
||||||
}
|
|
||||||
|
|
||||||
public struct GeometriaRobo
|
|
||||||
{
|
|
||||||
public double L; // entre-eixos (m)
|
|
||||||
public double W; // bitola (m)
|
|
||||||
}
|
|
||||||
|
|
||||||
// assuma ângulos em graus no seu sistema
|
|
||||||
static double Deg2Rad(double deg) => deg * Math.PI / 180.0;
|
|
||||||
|
|
||||||
static double EqAngleEixo(IEnumerable<double> angsDeg)
|
|
||||||
{
|
|
||||||
// Trata sequência vazia
|
|
||||||
if (angsDeg == null) return 0;
|
|
||||||
var list = angsDeg.ToList();
|
|
||||||
if (list.Count == 0) return 0;
|
|
||||||
|
|
||||||
// média de tan e volta pra graus
|
|
||||||
double mediaTan = list.Select(a => Math.Tan(a * Math.PI / 180.0)).Average();
|
|
||||||
return Math.Atan(mediaTan) * 180.0 / Math.PI;
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool TryCalcularRaioCentro(TipoMovimentoDirecional modo, double deltaFrontDeg, double deltaRearDeg, GeometriaRobo g, out double Rc)
|
|
||||||
{
|
|
||||||
double tf = Math.Tan(Deg2Rad(deltaFrontDeg));
|
|
||||||
double tr = Math.Tan(Deg2Rad(deltaRearDeg));
|
|
||||||
|
|
||||||
Rc = double.PositiveInfinity;
|
|
||||||
|
|
||||||
switch (modo)
|
|
||||||
{
|
{
|
||||||
case TipoMovimentoDirecional.RodasDianteiras:
|
ResetarTudo(modulos);
|
||||||
if (Math.Abs(tf) < 1e-6) return false; // reta
|
return;
|
||||||
Rc = g.L / tf;
|
}
|
||||||
return true;
|
|
||||||
|
|
||||||
case TipoMovimentoDirecional.RodasTraseiras:
|
GeometriaRobo g = ObterGeometriaAtual();
|
||||||
if (Math.Abs(tr) < 1e-6) return false;
|
|
||||||
Rc = g.L / tr;
|
|
||||||
return true;
|
|
||||||
|
|
||||||
case TipoMovimentoDirecional.MovimentoArco:
|
if (g.L <= 0 || g.W <= 0)
|
||||||
// uso geral: Rc = L / (tan(df) - tan(dr))
|
{
|
||||||
double denom = (tf - tr);
|
ResetarTudo(modulos);
|
||||||
if (Math.Abs(denom) < 1e-6) return false;
|
return;
|
||||||
Rc = g.L / denom;
|
}
|
||||||
return true;
|
|
||||||
|
/*
|
||||||
|
* Pega posição física REAL das rodas.
|
||||||
|
*/
|
||||||
|
double ef = 0;
|
||||||
|
double df = 0;
|
||||||
|
double et = 0;
|
||||||
|
double dt = 0;
|
||||||
|
|
||||||
|
bool angulosValidos =
|
||||||
|
TryObterAnguloCanonico(modulos, "EF", out ef) &&
|
||||||
|
TryObterAnguloCanonico(modulos, "DF", out df) &&
|
||||||
|
TryObterAnguloCanonico(modulos, "ET", out et) &&
|
||||||
|
TryObterAnguloCanonico(modulos, "DT", out dt);
|
||||||
|
|
||||||
|
if (!angulosValidos)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* Não inventa geometria quando não sabemos
|
||||||
|
* fisicamente onde as rodas estão.
|
||||||
|
*
|
||||||
|
* A saúde DIR decide em outro nível se a
|
||||||
|
* movimentação pode continuar.
|
||||||
|
*/
|
||||||
|
ResetarTudo(modulos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Transforma as duas rodas em um ângulo equivalente
|
||||||
|
* de cada eixo.
|
||||||
|
*
|
||||||
|
* Média em tan-space é melhor do que média simples
|
||||||
|
* para geometria de esterçamento.
|
||||||
|
*/
|
||||||
|
double angFrente = AnguloEquivalenteEixo(ef, df);
|
||||||
|
double angTras = AnguloEquivalenteEixo(et, dt);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Filtro temporal independente da frequência do timer.
|
||||||
|
*/
|
||||||
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
|
double dtSeg =
|
||||||
|
_ultimoCalculo == DateTime.MinValue
|
||||||
|
? 0
|
||||||
|
: (agora - _ultimoCalculo).TotalSeconds;
|
||||||
|
|
||||||
|
_ultimoCalculo = agora;
|
||||||
|
|
||||||
|
if (dtSeg <= 0 || dtSeg > 1.0)
|
||||||
|
{
|
||||||
|
_angFrenteFiltrado = angFrente;
|
||||||
|
_angTrasFiltrado = angTras;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
double alpha = 1.0 - Math.Exp(-dtSeg / TauFiltroAnguloSeg);
|
||||||
|
_angFrenteFiltrado += alpha * (angFrente - _angFrenteFiltrado);
|
||||||
|
_angTrasFiltrado += alpha * (angTras - _angTrasFiltrado);
|
||||||
|
}
|
||||||
|
|
||||||
|
double deltaCurvatura = Math.Abs(_angFrenteFiltrado - _angTrasFiltrado);
|
||||||
|
|
||||||
|
if (_curvaAtiva)
|
||||||
|
{
|
||||||
|
if (deltaCurvatura <= AnguloSaidaCurvaGraus)
|
||||||
|
{
|
||||||
|
_curvaAtiva = false;
|
||||||
|
ResetarCompensacao(modulos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (deltaCurvatura < AnguloEntradaCurvaGraus)
|
||||||
|
{
|
||||||
|
ResetarCompensacao(modulos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_curvaAtiva = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
double iccX;
|
||||||
|
double iccY;
|
||||||
|
|
||||||
|
if (!TryCalcularICC(_angFrenteFiltrado, _angTrasFiltrado, g, out iccX, out iccY))
|
||||||
|
{
|
||||||
|
ResetarTudo(modulos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var raios = CalcularRaios(g, iccX, iccY);
|
||||||
|
|
||||||
|
if (raios.Count == 0)
|
||||||
|
{
|
||||||
|
ResetarTudo(modulos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double raioMin = raios.Values.Min();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ICC praticamente sobre uma roda.
|
||||||
|
* Geometria extrema demais para nossa política.
|
||||||
|
*/
|
||||||
|
if (raioMin < 0.05)
|
||||||
|
{
|
||||||
|
ResetarTudo(modulos);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var novoEstado = new Dictionary<string, EstadoCompensacaoRoda>();
|
||||||
|
|
||||||
|
foreach (var kv in raios)
|
||||||
|
{
|
||||||
|
double fatorBruto = kv.Value / raioMin;
|
||||||
|
|
||||||
|
bool saturado = fatorBruto > FatorMaximo;
|
||||||
|
|
||||||
|
double fator = Math.Max(1.0, Math.Min(fatorBruto, FatorMaximo));
|
||||||
|
|
||||||
|
novoEstado[kv.Key] = new EstadoCompensacaoRoda
|
||||||
|
{
|
||||||
|
ModuloId = kv.Key,
|
||||||
|
RaioM = kv.Value,
|
||||||
|
Fator = fator,
|
||||||
|
Saturado = saturado,
|
||||||
|
|
||||||
|
AnguloFrenteGraus = _angFrenteFiltrado,
|
||||||
|
AnguloTrasGraus = _angTrasFiltrado,
|
||||||
|
|
||||||
|
IccX = iccX,
|
||||||
|
IccY = iccY
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_estado = novoEstado;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static GeometriaRobo ObterGeometriaAtual()
|
||||||
|
{
|
||||||
|
return new GeometriaRobo
|
||||||
|
{
|
||||||
|
L = VariaveisEquipamento.DistanciaEntreEixosCm / 100.0,
|
||||||
|
|
||||||
|
/*
|
||||||
|
* CRIAR esta propriedade.
|
||||||
|
* Centro da roda esquerda até centro
|
||||||
|
* da roda direita.
|
||||||
|
*/
|
||||||
|
W = VariaveisEquipamento.BitolaRodasCm / 100.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static bool TryObterAnguloCanonico(List<ModuloMvdModel> modulos, string modId, out double angulo)
|
||||||
|
{
|
||||||
|
angulo = 0.0;
|
||||||
|
|
||||||
|
var modulo = modulos.FirstOrDefault(x => x.Modulo_ID == modId);
|
||||||
|
|
||||||
|
var dir = modulo?.DirMotor;
|
||||||
|
|
||||||
|
if (dir == null ||
|
||||||
|
!dir.Inicializado ||
|
||||||
|
dir.Leitura == null ||
|
||||||
|
dir.Leitura.NumeroPulsos == null ||
|
||||||
|
dir.Leitura.NumeroPulsos.lidoEm == DateTime.MinValue)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
|
double idadeMs = (agora - dir.Leitura.NumeroPulsos.lidoEm).TotalMilliseconds;
|
||||||
|
|
||||||
|
if (idadeMs < 0 || idadeMs > IdadeMaximaAnguloMs)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
angulo = ConverterAnguloMotorParaVeiculo(modId, dir.AnguloLeitura);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static double ConverterAnguloMotorParaVeiculo(string modId, double anguloMotor)
|
||||||
|
{
|
||||||
|
switch (modId)
|
||||||
|
{
|
||||||
|
case "EF":
|
||||||
|
return -anguloMotor;
|
||||||
|
|
||||||
|
case "DF":
|
||||||
|
return anguloMotor;
|
||||||
|
|
||||||
|
case "ET":
|
||||||
|
return anguloMotor;
|
||||||
|
|
||||||
|
case "DT":
|
||||||
|
return -anguloMotor;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false;
|
return 0.0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Raio de trajetória por roda para curva à esquerda (Rc > 0) ou direita (Rc < 0)
|
|
||||||
// Posições das rodas em relação ao centro do chassi: x = ±L/2, y = ±W/2
|
private static double AnguloEquivalenteEixo(double esquerdo, double direito)
|
||||||
static void CalcularRaiosPorRoda(TipoMovimentoDirecional modo, double Rc, GeometriaRobo g, out double rFL, out double rFR, out double rRL, out double rRR)
|
|
||||||
{
|
{
|
||||||
// Por simplicidade, tratamos três casos:
|
double tanE = Math.Tan(Deg2Rad(esquerdo));
|
||||||
// 1) Dianteiras: ICC está sobre a linha do eixo traseiro
|
double tanD = Math.Tan(Deg2Rad(direito));
|
||||||
// 2) Traseiras: ICC está sobre a linha do eixo dianteiro
|
|
||||||
// 3) ArcoOposto (simétrico): ICC está próximo ao centro; distância axial = L/2 para ambos
|
|
||||||
double ay = Math.Abs(Rc); // magnitude do Rc; sinal define esquerda/direita
|
|
||||||
|
|
||||||
if (modo == TipoMovimentoDirecional.RodasDianteiras)
|
return Rad2Deg(Math.Atan((tanE + tanD) / 2.0));
|
||||||
{
|
|
||||||
// Traseiro: raio = Rc ± W/2
|
|
||||||
rRL = Math.Max(1e-6, ay - g.W / 2.0);
|
|
||||||
rRR = Math.Max(1e-6, ay + g.W / 2.0);
|
|
||||||
// Dianteiro: distância diagonal até ICC (pitágoras)
|
|
||||||
rFL = Math.Sqrt(Math.Pow(ay - g.W / 2.0, 2) + Math.Pow(g.L, 2));
|
|
||||||
rFR = Math.Sqrt(Math.Pow(ay + g.W / 2.0, 2) + Math.Pow(g.L, 2));
|
|
||||||
}
|
|
||||||
else if (modo == TipoMovimentoDirecional.RodasTraseiras)
|
|
||||||
{
|
|
||||||
// Dianteiro: raio = Rc ± W/2
|
|
||||||
rFL = Math.Max(1e-6, ay - g.W / 2.0);
|
|
||||||
rFR = Math.Max(1e-6, ay + g.W / 2.0);
|
|
||||||
// Traseiro: distância diagonal
|
|
||||||
rRL = Math.Sqrt(Math.Pow(ay - g.W / 2.0, 2) + Math.Pow(g.L, 2));
|
|
||||||
rRR = Math.Sqrt(Math.Pow(ay + g.W / 2.0, 2) + Math.Pow(g.L, 2));
|
|
||||||
}
|
|
||||||
else // Arco (simétrico)
|
|
||||||
{
|
|
||||||
// Ambos eixos a L/2 do centro
|
|
||||||
double a = g.L / 2.0;
|
|
||||||
rFL = Math.Sqrt(Math.Pow(ay - g.W / 2.0, 2) + a * a);
|
|
||||||
rFR = Math.Sqrt(Math.Pow(ay + g.W / 2.0, 2) + a * a);
|
|
||||||
rRL = Math.Sqrt(Math.Pow(ay - g.W / 2.0, 2) + a * a);
|
|
||||||
rRR = Math.Sqrt(Math.Pow(ay + g.W / 2.0, 2) + a * a);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Observação: sinal de Rc indica sentido da curva:
|
|
||||||
// Rc > 0 -> curva à esquerda (rodas esquerdas são internas)
|
|
||||||
// Rc < 0 -> curva à direita (troque "interna"↔"externa")
|
|
||||||
// Aqui usamos |Rc|; para decidir quem é interna/externa, use o sinal fora daqui.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retorna fatores por roda. Modo "SomenteAumentarExterna" mantém a interna em 1.0
|
|
||||||
public enum PoliticaFator { ReferenciaNoCentro, SomenteAumentarExterna }
|
|
||||||
|
|
||||||
public static Dictionary<string, double> FatoresVelocidade(TipoMovimentoDirecional modo, double deltaFrontDeg, double deltaRearDeg, GeometriaRobo g, PoliticaFator politica, double fatorMaximo = 1.5)
|
private static bool TryCalcularICC(double deltaFrenteGraus, double deltaTrasGraus, GeometriaRobo g, out double iccX, out double iccY)
|
||||||
{
|
{
|
||||||
if (!TryCalcularRaioCentro(modo, deltaFrontDeg, deltaRearDeg, g, out double Rc))
|
iccX = 0;
|
||||||
return Variaveis.OperacaoEmAndamento.DispMvd.Dados.Modulos.ToDictionary(x => x.Modulo_ID, x => 1.0);
|
iccY = 0;
|
||||||
|
|
||||||
CalcularRaiosPorRoda(modo, Rc, g, out var rFL, out var rFR, out var rRL, out var rRR);
|
double df = Deg2Rad(deltaFrenteGraus);
|
||||||
|
double dr = Deg2Rad(deltaTrasGraus);
|
||||||
|
|
||||||
// Centro do robô percorre raio |Rc|
|
/*
|
||||||
double RcAbs = Math.Abs(Rc);
|
* Vetor normal à orientação de cada eixo.
|
||||||
|
*/
|
||||||
|
double nfx = -Math.Sin(df);
|
||||||
|
double nfy = Math.Cos(df);
|
||||||
|
double nrx = -Math.Sin(dr);
|
||||||
|
double nry = Math.Cos(dr);
|
||||||
|
|
||||||
// Descobrir lado interno pela curva (sinal de Rc)
|
/*
|
||||||
bool esquerda = Variaveis.OperacaoEmAndamento.Controle.TiposControle.FirstOrDefault(x => x.Tipo == T_Code.Dir).UltimaDirecao == Direcao.Esquerda; // Rc > 0;
|
* cross(nf, nr)
|
||||||
|
*/
|
||||||
|
double denominador = nfx * nry - nfy * nrx;
|
||||||
|
|
||||||
double rIntFront = esquerda ? rFL : rFR;
|
/*
|
||||||
double rExtFront = esquerda ? rFR : rFL;
|
* Normais quase paralelas:
|
||||||
double rIntRear = esquerda ? rRL : rRR;
|
* raio tende ao infinito.
|
||||||
double rExtRear = esquerda ? rRR : rRL;
|
*/
|
||||||
|
if (Math.Abs(denominador) < 1e-6)
|
||||||
double fFL, fFR, fRL, fRR;
|
|
||||||
|
|
||||||
if (politica == PoliticaFator.ReferenciaNoCentro)
|
|
||||||
{
|
{
|
||||||
fFL = rIntFront / RcAbs;
|
return false;
|
||||||
fFR = rExtFront / RcAbs;
|
|
||||||
fRL = rIntRear / RcAbs;
|
|
||||||
fRR = rExtRear / RcAbs;
|
|
||||||
}
|
|
||||||
else // SomenteAumentarExterna
|
|
||||||
{
|
|
||||||
fFL = 1.0;
|
|
||||||
fFR = rExtFront / rIntFront;
|
|
||||||
fRL = 1.0;
|
|
||||||
fRR = rExtRear / rIntRear;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saturação e leve suavização (ex: EMA) podem ser aplicadas aqui
|
/*
|
||||||
fFL = Math.Min(fatorMaximo, fFL);
|
* Centro do eixo dianteiro:
|
||||||
fFR = Math.Min(fatorMaximo, fFR);
|
* F = (+L/2, 0)
|
||||||
fRL = Math.Min(fatorMaximo, fRL);
|
*
|
||||||
fRR = Math.Min(fatorMaximo, fRR);
|
* Centro do eixo traseiro:
|
||||||
|
* R = (-L/2, 0)
|
||||||
|
*
|
||||||
|
* vetor R-F = (-L,0)
|
||||||
|
*/
|
||||||
|
double vx = -g.L;
|
||||||
|
|
||||||
return Variaveis.OperacaoEmAndamento.DispMvd.Dados.Modulos.ToDictionary(x => x.Modulo_ID, x => (double)(x.Modulo_ID == "ET" ? fRL : x.Modulo_ID == "DT" ? fRR : x.Modulo_ID == "EF" ? fFL : x.Modulo_ID == "DF" ? fFR : 1.0));
|
double vy = 0.0;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* t =
|
||||||
|
* cross(R-F, nr) /
|
||||||
|
* cross(nf, nr)
|
||||||
|
*/
|
||||||
|
double t = (vx * nry - vy * nrx) / denominador;
|
||||||
|
|
||||||
|
iccX = g.L / 2.0 + t * nfx;
|
||||||
|
|
||||||
|
iccY = t * nfy;
|
||||||
|
|
||||||
|
return
|
||||||
|
!double.IsNaN(iccX) &&
|
||||||
|
!double.IsNaN(iccY) &&
|
||||||
|
!double.IsInfinity(iccX) &&
|
||||||
|
!double.IsInfinity(iccY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static Dictionary<string, double> CalcularRaios(GeometriaRobo g, double iccX, double iccY)
|
||||||
|
{
|
||||||
|
double xF = g.L / 2.0;
|
||||||
|
double xT = -g.L / 2.0;
|
||||||
|
double yE = g.W / 2.0;
|
||||||
|
double yD = -g.W / 2.0;
|
||||||
|
|
||||||
|
return new Dictionary<string, double>
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"EF",
|
||||||
|
Distancia(xF, yE, iccX, iccY)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DF",
|
||||||
|
Distancia(xF, yD, iccX, iccY)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"ET",
|
||||||
|
Distancia(xT, yE, iccX, iccY)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"DT",
|
||||||
|
Distancia(xT, yD, iccX, iccY)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static double ObterFator(string modId)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
EstadoCompensacaoRoda e;
|
||||||
|
|
||||||
|
if (_estado.TryGetValue(modId, out e))
|
||||||
|
{
|
||||||
|
return Math.Max(1.0, e.Fator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static int CalcularRpmMotor(string modId, int rpmBase, int rpmMin, int rpmMax)
|
||||||
|
{
|
||||||
|
if (rpmBase <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
double fatorRoda = ObterFator(modId);
|
||||||
|
|
||||||
|
double maiorFator;
|
||||||
|
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
maiorFator =
|
||||||
|
_estado.Count == 0
|
||||||
|
? 1.0
|
||||||
|
: _estado.Values.Max(x => x.Fator);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Primeiro limita o RPM base válido.
|
||||||
|
*/
|
||||||
|
double baseSolicitada = Math.Max(rpmMin, Math.Min(rpmBase, rpmMax));
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Se a roda externa estouraria RPM máximo,
|
||||||
|
* reduzimos TODAS proporcionalmente.
|
||||||
|
*
|
||||||
|
* Assim preservamos a geometria.
|
||||||
|
*/
|
||||||
|
double baseEfetiva = baseSolicitada;
|
||||||
|
|
||||||
|
if (maiorFator > 1.0)
|
||||||
|
{
|
||||||
|
double baseMaximaPermitida = rpmMax / maiorFator;
|
||||||
|
|
||||||
|
baseEfetiva = Math.Min(baseEfetiva, baseMaximaPermitida);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Normalmente respeitamos rpmMin.
|
||||||
|
*
|
||||||
|
* Porém, se a própria geometria tornar impossível manter
|
||||||
|
* simultaneamente rpmMin e rpmMax, priorizamos a proporção
|
||||||
|
* cinemática e o limite máximo.
|
||||||
|
*/
|
||||||
|
bool geometriaIncompativelComRpmMin = maiorFator > 1.0 && (rpmMax / maiorFator) < rpmMin;
|
||||||
|
|
||||||
|
if (!geometriaIncompativelComRpmMin)
|
||||||
|
{
|
||||||
|
baseEfetiva = Math.Max(baseEfetiva, rpmMin);
|
||||||
|
}
|
||||||
|
|
||||||
|
double rpm = baseEfetiva * fatorRoda;
|
||||||
|
|
||||||
|
return Convert.ToInt32(Math.Max(0, Math.Min(rpm, rpmMax)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static EstadoCompensacaoRoda ObterEstado(string modId)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
EstadoCompensacaoRoda e;
|
||||||
|
|
||||||
|
if (!_estado.TryGetValue(modId, out e))
|
||||||
|
{
|
||||||
|
return new EstadoCompensacaoRoda
|
||||||
|
{
|
||||||
|
ModuloId = modId,
|
||||||
|
Fator = 1.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void ResetarTudo(List<ModuloMvdModel> modulos = null)
|
||||||
|
{
|
||||||
|
ResetarCompensacao(modulos);
|
||||||
|
|
||||||
|
_angFrenteFiltrado = 0.0;
|
||||||
|
_angTrasFiltrado = 0.0;
|
||||||
|
_ultimoCalculo = DateTime.MinValue;
|
||||||
|
_curvaAtiva = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void ResetarCompensacao(List<ModuloMvdModel> modulos)
|
||||||
|
{
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_estado = new Dictionary<string, EstadoCompensacaoRoda>();
|
||||||
|
|
||||||
|
if (modulos != null)
|
||||||
|
{
|
||||||
|
foreach (var m in modulos)
|
||||||
|
{
|
||||||
|
if (m == null || string.IsNullOrWhiteSpace(m.Modulo_ID))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
_estado[m.Modulo_ID] = new EstadoCompensacaoRoda
|
||||||
|
{
|
||||||
|
ModuloId = m.Modulo_ID,
|
||||||
|
Fator = 1.0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static double Distancia(double x1, double y1, double x2, double y2)
|
||||||
|
{
|
||||||
|
double dx = x1 - x2;
|
||||||
|
double dy = y1 - y2;
|
||||||
|
|
||||||
|
return Math.Sqrt(dx * dx + dy * dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static double Deg2Rad(double graus)
|
||||||
|
{
|
||||||
|
return graus * Math.PI / 180.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static double Rad2Deg(double rad)
|
||||||
|
{
|
||||||
|
return rad * 180.0 / Math.PI;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Data;
|
using System.Data;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using static AgroBase.Models.Enums;
|
using static AgroBase.Models.Enums;
|
||||||
|
|
||||||
|
|
@ -46,6 +47,10 @@ namespace AgroBase.Models.Modules
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public AsyncTaskTimerModel tmrRegistradorDIR;
|
public AsyncTaskTimerModel tmrRegistradorDIR;
|
||||||
|
|
||||||
|
public bool MovimentoDirecionalCombinadoEmExecucao { get; private set; }
|
||||||
|
private readonly SemaphoreSlim _gateMovimentoCombinado = new SemaphoreSlim(1, 1);
|
||||||
|
private CancellationTokenSource _ctsMovimentoCombinado;
|
||||||
|
|
||||||
|
|
||||||
public List<ModuloMvdModel> Modulos { get; set; } = new List<ModuloMvdModel>();
|
public List<ModuloMvdModel> Modulos { get; set; } = new List<ModuloMvdModel>();
|
||||||
|
|
||||||
|
|
@ -150,6 +155,10 @@ namespace AgroBase.Models.Modules
|
||||||
x.MovMotor.Temperatura = 0;
|
x.MovMotor.Temperatura = 0;
|
||||||
x.MovMotor.RPM_SP = 0;
|
x.MovMotor.RPM_SP = 0;
|
||||||
x.MovMotor.RPM_SP_Controle = 0;
|
x.MovMotor.RPM_SP_Controle = 0;
|
||||||
|
x.MovMotor.UltimoRPMComandado = 0;
|
||||||
|
x.MovMotor.UltimoComandoEnviado = DateTime.MinValue;
|
||||||
|
x.MovMotor.UltimoSetPointEnviado = 0;
|
||||||
|
x.MovMotor.UltimaDirecao = Direcao.Parado;
|
||||||
|
|
||||||
x.DirMotor.Angulo_SP = 0;
|
x.DirMotor.Angulo_SP = 0;
|
||||||
x.DirMotor.Sentido_SP = Sentido.Parado;
|
x.DirMotor.Sentido_SP = Sentido.Parado;
|
||||||
|
|
@ -163,6 +172,8 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
public void RegistrarLogMov(ModuloMvdModel Modulo)
|
public void RegistrarLogMov(ModuloMvdModel Modulo)
|
||||||
{
|
{
|
||||||
|
var comp = MovimentacaoMovimentoCompensadoModel.ObterEstado(Modulo.Modulo_ID);
|
||||||
|
|
||||||
Modulo.MovMotor.LogGrafico.Add(new MvdMotorMOVSensorimanetoGrafico()
|
Modulo.MovMotor.LogGrafico.Add(new MvdMotorMOVSensorimanetoGrafico()
|
||||||
{
|
{
|
||||||
Momento = DateTime.Now,
|
Momento = DateTime.Now,
|
||||||
|
|
@ -177,7 +188,7 @@ namespace AgroBase.Models.Modules
|
||||||
Tensao = Modulo.MovMotor.Tensao,
|
Tensao = Modulo.MovMotor.Tensao,
|
||||||
Velocidade = Modulo.MovMotor.VelocidadeInstantanea,
|
Velocidade = Modulo.MovMotor.VelocidadeInstantanea,
|
||||||
Potencia = Modulo.MovMotor.Potencia,
|
Potencia = Modulo.MovMotor.Potencia,
|
||||||
CicloTrabalho = Modulo.MovMotor.CicloTrabalho
|
CicloTrabalho = Modulo.MovMotor.CicloTrabalho,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (Modulo.MovMotor.LogGrafico.Count > 60)
|
if (Modulo.MovMotor.LogGrafico.Count > 60)
|
||||||
|
|
@ -332,8 +343,11 @@ namespace AgroBase.Models.Modules
|
||||||
//}
|
//}
|
||||||
private DateTime _ultimoEnvioControleMov = DateTime.MinValue;
|
private DateTime _ultimoEnvioControleMov = DateTime.MinValue;
|
||||||
private DateTime _ultimoRegistroLogMov = DateTime.MinValue;
|
private DateTime _ultimoRegistroLogMov = DateTime.MinValue;
|
||||||
private static readonly TimeSpan IntervaloControleMov = TimeSpan.FromSeconds(1);
|
private static readonly TimeSpan IntervaloControleMov = TimeSpan.FromMilliseconds(100);
|
||||||
private static readonly TimeSpan IntervaloLogMov = TimeSpan.FromSeconds(1);
|
private static readonly TimeSpan IntervaloLogMov = TimeSpan.FromSeconds(1);
|
||||||
|
private DateTime _ultimaAtualizacaoCompensacaoMov = DateTime.MinValue;
|
||||||
|
private static readonly TimeSpan IntervaloCompensacaoMov = TimeSpan.FromMilliseconds(50);
|
||||||
|
|
||||||
private async Task tmrRegistradorMOV_Tick()
|
private async Task tmrRegistradorMOV_Tick()
|
||||||
{
|
{
|
||||||
var op = Variaveis.OperacaoEmAndamento;
|
var op = Variaveis.OperacaoEmAndamento;
|
||||||
|
|
@ -343,40 +357,27 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
DateTime agora = DateTime.Now;
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
if (!OIDCanService.Iniciado && SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Mov))
|
|
||||||
{
|
|
||||||
foreach (var leitura in OIDCanService.DadosLeitura.Where(x => x.UltimoComandoRecebido.AddSeconds(10) < agora))
|
|
||||||
{
|
|
||||||
OIDCanService.RequisitarDado(
|
|
||||||
leitura.EnderecoTx,
|
|
||||||
leitura.EnderecoRx,
|
|
||||||
OIDCanService.OidHandler.OidParametros.CodErro
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
tmrRegistradorMOV.SetInterval(3000);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool deveEnviarControle =
|
|
||||||
_ultimoEnvioControleMov == DateTime.MinValue ||
|
|
||||||
agora - _ultimoEnvioControleMov >= IntervaloControleMov;
|
|
||||||
|
|
||||||
bool deveRegistrarLog =
|
|
||||||
_ultimoRegistroLogMov == DateTime.MinValue ||
|
|
||||||
agora - _ultimoRegistroLogMov >= IntervaloLogMov;
|
|
||||||
|
|
||||||
bool enviouControle = false;
|
|
||||||
bool registrouLog = false;
|
|
||||||
|
|
||||||
var controleMov = deveEnviarControle ? op.Controle.TiposControle?.FirstOrDefault(x => x.Tipo == T_Code.Mov) : null;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Uma única tentativa global por tick.
|
* Uma única tentativa global por tick.
|
||||||
* O OIDCanService escolhe o driver e o parâmetro mais adequado.
|
* O OIDCanService escolhe o driver e o parâmetro mais adequado.
|
||||||
*/
|
*/
|
||||||
OIDCanService.AtualizarDados();
|
OIDCanService.AtualizarDados();
|
||||||
|
|
||||||
|
bool deveEnviarControle = _ultimoEnvioControleMov == DateTime.MinValue || agora - _ultimoEnvioControleMov >= IntervaloControleMov;
|
||||||
|
bool deveRegistrarLog = _ultimoRegistroLogMov == DateTime.MinValue || agora - _ultimoRegistroLogMov >= IntervaloLogMov;
|
||||||
|
bool deveAtualizarCompensacao = _ultimaAtualizacaoCompensacaoMov == DateTime.MinValue || agora - _ultimaAtualizacaoCompensacaoMov >= IntervaloCompensacaoMov;
|
||||||
|
|
||||||
|
if (deveAtualizarCompensacao)
|
||||||
|
{
|
||||||
|
MovimentacaoMovimentoCompensadoModel.AtualizarDados();
|
||||||
|
_ultimaAtualizacaoCompensacaoMov = agora;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool enviouControle = false;
|
||||||
|
bool registrouLog = false;
|
||||||
|
|
||||||
|
var controleMov = deveEnviarControle ? op.Controle.TiposControle?.FirstOrDefault(x => x.Tipo == T_Code.Mov) : null;
|
||||||
|
|
||||||
foreach (var modulo in Modulos)
|
foreach (var modulo in Modulos)
|
||||||
{
|
{
|
||||||
if (!modulo.MovMotor.Inicializado)
|
if (!modulo.MovMotor.Inicializado)
|
||||||
|
|
@ -387,7 +388,7 @@ namespace AgroBase.Models.Modules
|
||||||
* O polling agora é centralizado em AtualizarDados().
|
* O polling agora é centralizado em AtualizarDados().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if (deveEnviarControle && controleMov != null)
|
if (!MovimentoDirecionalCombinadoEmExecucao && deveEnviarControle && controleMov != null)
|
||||||
{
|
{
|
||||||
modulo.MovMotor.EnviarComandoControle(controleMov.UltimaDirecao);
|
modulo.MovMotor.EnviarComandoControle(controleMov.UltimaDirecao);
|
||||||
|
|
||||||
|
|
@ -477,7 +478,7 @@ namespace AgroBase.Models.Modules
|
||||||
* Durante referenciamento não interferimos no comando,
|
* Durante referenciamento não interferimos no comando,
|
||||||
* pois o próprio fluxo de referência assume o controle do MKS.
|
* pois o próprio fluxo de referência assume o controle do MKS.
|
||||||
*/
|
*/
|
||||||
if (!MKS057DCanService.Referenciando && deveEnviarControle && controleDir != null && !driver.AtingiuAnguloSP)
|
if (!MovimentoDirecionalCombinadoEmExecucao && !MKS057DCanService.Referenciando && deveEnviarControle && controleDir != null && !driver.AtingiuAnguloSP)
|
||||||
{
|
{
|
||||||
driver.EnviarComandoControle(controleDir.UltimaDirecao);
|
driver.EnviarComandoControle(controleDir.UltimaDirecao);
|
||||||
enviouControle = true;
|
enviouControle = true;
|
||||||
|
|
@ -532,51 +533,100 @@ namespace AgroBase.Models.Modules
|
||||||
|
|
||||||
public async Task ExecutarMovimentoDirecionalCombinado(Direcao DirecaoAtual)
|
public async Task ExecutarMovimentoDirecionalCombinado(Direcao DirecaoAtual)
|
||||||
{
|
{
|
||||||
var op = Variaveis.OperacaoEmAndamento;
|
_ctsMovimentoCombinado?.Cancel();
|
||||||
|
|
||||||
if (DirecaoAtual == Direcao.Parado)
|
var cts = new CancellationTokenSource();
|
||||||
|
_ctsMovimentoCombinado = cts;
|
||||||
|
|
||||||
|
await _gateMovimentoCombinado.WaitAsync();
|
||||||
|
|
||||||
|
try
|
||||||
{
|
{
|
||||||
foreach (var Modulo in Modulos)
|
if (cts.IsCancellationRequested)
|
||||||
|
return;
|
||||||
|
|
||||||
|
MovimentoDirecionalCombinadoEmExecucao = true;
|
||||||
|
|
||||||
|
var op = Variaveis.OperacaoEmAndamento;
|
||||||
|
|
||||||
|
var ctrlMov = op.Controle.TiposControle.First(x => x.Tipo == T_Code.Mov);
|
||||||
|
var ctrlDir = op.Controle.TiposControle.First(x => x.Tipo == T_Code.Dir);
|
||||||
|
|
||||||
|
ctrlDir.DirecaoAtual = DirecaoAtual;
|
||||||
|
ctrlDir.UltimaDirecao = DirecaoAtual;
|
||||||
|
|
||||||
|
// Sempre parar a tração antes de alterar a geometria das rodas
|
||||||
|
ctrlMov.DirecaoAtual = Direcao.Parado;
|
||||||
|
ctrlMov.UltimaDirecao = Direcao.Parado;
|
||||||
|
|
||||||
|
foreach (var modulo in Modulos)
|
||||||
{
|
{
|
||||||
Modulo.MovMotor.EnviarComandoControle(Direcao.Parado);
|
modulo.MovMotor.EnviarComandoControle(Direcao.Parado);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Task.Delay(1000);
|
if (DirecaoAtual == Direcao.Parado)
|
||||||
}
|
{
|
||||||
|
await Task.Delay(1000, cts.Token);
|
||||||
|
}
|
||||||
|
|
||||||
switch (op.Controle.TipoMovimento)
|
switch (op.Controle.TipoMovimento)
|
||||||
{
|
{
|
||||||
case TipoMovimentoDirecional.RotacionarNoEixo:
|
case TipoMovimentoDirecional.RotacionarNoEixo:
|
||||||
case TipoMovimentoDirecional.MovimentoLateral:
|
case TipoMovimentoDirecional.MovimentoLateral:
|
||||||
|
|
||||||
foreach (var Modulo in Modulos.Where(x => x.DirMotor.Inicializado && !x.DirMotor.PosicaoTipoMovimentoConcluido(DirecaoAtual)))
|
foreach (var Modulo in Modulos.Where(x => x.DirMotor.Inicializado && !x.DirMotor.PosicaoTipoMovimentoConcluido(DirecaoAtual)))
|
||||||
{
|
|
||||||
Modulo.DirMotor.EnviarComandoControle(DirecaoAtual);
|
|
||||||
}
|
|
||||||
|
|
||||||
DateTime Agora = DateTime.Now;
|
|
||||||
bool Sucesso = Modulos.All(x => x.DirMotor.PosicaoTipoMovimentoConcluido(DirecaoAtual));
|
|
||||||
while (Agora.AddMilliseconds(5000) > DateTime.Now && !Sucesso)
|
|
||||||
{
|
|
||||||
await Task.Delay(100);
|
|
||||||
Sucesso = Modulos.All(x => x.DirMotor.PosicaoTipoMovimentoConcluido(DirecaoAtual));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Sucesso && DirecaoAtual != Direcao.Parado)
|
|
||||||
{
|
|
||||||
op.Controle.PercentualVelocidadeSP = 20;
|
|
||||||
foreach (var Modulo in Modulos)
|
|
||||||
{
|
{
|
||||||
Modulo.MovMotor.EnviarComandoControle(
|
Modulo.DirMotor.EnviarComandoControle(DirecaoAtual);
|
||||||
DirecaoAtual == Direcao.Direita ? Direcao.Frente :
|
|
||||||
DirecaoAtual == Direcao.Esquerda ? Direcao.Tras :
|
|
||||||
Direcao.Parado
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
DateTime Agora = DateTime.Now;
|
||||||
|
bool Sucesso = Modulos.All(x => x.DirMotor.PosicaoTipoMovimentoConcluido(DirecaoAtual));
|
||||||
|
while (Agora.AddMilliseconds(5000) > DateTime.Now && !Sucesso)
|
||||||
|
{
|
||||||
|
await Task.Delay(100, cts.Token);
|
||||||
|
Sucesso = Modulos.All(x => x.DirMotor.PosicaoTipoMovimentoConcluido(DirecaoAtual));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Sucesso && DirecaoAtual != Direcao.Parado)
|
||||||
|
{
|
||||||
|
op.Controle.PercentualVelocidadeSP = 20;
|
||||||
|
|
||||||
|
Direcao direcaoMov = DirecaoAtual == Direcao.Direita ? Direcao.Frente : DirecaoAtual == Direcao.Esquerda ? Direcao.Tras : Direcao.Parado;
|
||||||
|
ctrlMov.DirecaoAtual = direcaoMov;
|
||||||
|
ctrlMov.UltimaDirecao = direcaoMov;
|
||||||
|
|
||||||
|
foreach (var Modulo in Modulos)
|
||||||
|
{
|
||||||
|
Modulo.MovMotor.EnviarComandoControle(
|
||||||
|
DirecaoAtual == Direcao.Direita ? Direcao.Frente :
|
||||||
|
DirecaoAtual == Direcao.Esquerda ? Direcao.Tras :
|
||||||
|
Direcao.Parado
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Sucesso)
|
||||||
|
{
|
||||||
|
ctrlMov.DirecaoAtual = Direcao.Parado;
|
||||||
|
ctrlMov.UltimaDirecao = Direcao.Parado;
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[MVD] Movimento combinado não atingiu posição | " +
|
||||||
|
$"Tipo={op.Controle.TipoMovimento} | " +
|
||||||
|
$"Direcao={DirecaoAtual}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
MovimentoDirecionalCombinadoEmExecucao = false;
|
||||||
|
_gateMovimentoCombinado.Release();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2115,6 +2115,8 @@ namespace AgroBase.Models.Modules
|
||||||
Mod_ID = Modulo_ID,
|
Mod_ID = Modulo_ID,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (op.Sensoriamento?.Operacao?.ModsCalibragem == null)
|
||||||
|
op.Sensoriamento.Operacao.ModsCalibragem = new Dictionary<T_Code, Dictionary<string, bool>>();
|
||||||
op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo] = new Dictionary<string, bool>();
|
op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo] = new Dictionary<string, bool>();
|
||||||
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Conectado, 0, "Dispositivo conectado");
|
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Conectado, 0, "Dispositivo conectado");
|
||||||
}
|
}
|
||||||
|
|
@ -3094,7 +3096,7 @@ namespace AgroBase.Models.Modules
|
||||||
double temperatura = ConverterTemperaturaNtc(data, 4);
|
double temperatura = ConverterTemperaturaNtc(data, 4);
|
||||||
double tensao = ConverterTensaoNtc(data, 6);
|
double tensao = ConverterTensaoNtc(data, 6);
|
||||||
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { raw, temperatura, tensao });
|
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { raw, temperatura, tensao });
|
||||||
Variaveis.MostrarLog($"[SEN] sNTC | ID_Num: {ID_Num} | Posicao: {posicao} | Leitura: {raw} | Temperatura: {temperatura} | Tensao: {tensao}");
|
//Variaveis.MostrarLog($"[SEN] sNTC | ID_Num: {ID_Num} | Posicao: {posicao} | Leitura: {raw} | Temperatura: {temperatura} | Tensao: {tensao}");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2045,11 +2045,10 @@ namespace AgroBase.Models
|
||||||
{
|
{
|
||||||
case T_Code.Mov:
|
case T_Code.Mov:
|
||||||
{
|
{
|
||||||
var modsAtualizar = op.DispMvd.Dados.Modulos.Where(Mod => Mod.MovMotor.NovoRPMNecessario).Select(x => x.Modulo_ID).ToList();
|
|
||||||
bool envioNecessario = ForcarEnvio || _Controle.RPM_SP != _ControleAnterior.RPM_SP || _Controle.EmFreio != _ControleAnterior.EmFreio;
|
bool envioNecessario = ForcarEnvio || _Controle.RPM_SP != _ControleAnterior.RPM_SP || _Controle.EmFreio != _ControleAnterior.EmFreio;
|
||||||
if (envioNecessario || modsAtualizar.Any())
|
if (envioNecessario)
|
||||||
{
|
{
|
||||||
foreach (var Mod in op.DispMvd.Dados.Modulos.Where(x => envioNecessario ? true : modsAtualizar.Contains(x.Modulo_ID)))
|
foreach (var Mod in op.DispMvd.Dados.Modulos)
|
||||||
{
|
{
|
||||||
Mod.MovMotor.EnviarComandoControle(DirecaoAtual);
|
Mod.MovMotor.EnviarComandoControle(DirecaoAtual);
|
||||||
}
|
}
|
||||||
|
|
@ -4740,6 +4739,8 @@ namespace AgroBase.Models
|
||||||
|
|
||||||
string moduloId = modulo?.Modulo_ID ?? mov?.Mod_ID ?? "";
|
string moduloId = modulo?.Modulo_ID ?? mov?.Mod_ID ?? "";
|
||||||
|
|
||||||
|
var comp = MovimentacaoMovimentoCompensadoModel.ObterEstado(moduloId);
|
||||||
|
|
||||||
return new MovimentacaoModuloLogModel
|
return new MovimentacaoModuloLogModel
|
||||||
{
|
{
|
||||||
Modulo_ID = moduloId,
|
Modulo_ID = moduloId,
|
||||||
|
|
@ -4790,7 +4791,12 @@ namespace AgroBase.Models
|
||||||
UltimoSetPointEnviado = mov?.UltimoSetPointEnviado ?? 0,
|
UltimoSetPointEnviado = mov?.UltimoSetPointEnviado ?? 0,
|
||||||
UltimaDirecao = mov?.UltimaDirecao ?? Direcao.Parado,
|
UltimaDirecao = mov?.UltimaDirecao ?? Direcao.Parado,
|
||||||
|
|
||||||
FatorCompensacaoCurva = BuscarFatorCompensacao(moduloId)
|
RPM_SP_Base = mov.RPM_SP_Controle,
|
||||||
|
FatorCompensacao = comp.Fator,
|
||||||
|
CompensacaoSaturada = comp.Saturado,
|
||||||
|
IccX = comp.IccX,
|
||||||
|
IccY = comp.IccY,
|
||||||
|
RaioTrajetoriaM = comp.RaioM,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -4827,19 +4833,6 @@ namespace AgroBase.Models
|
||||||
return valores.Count == 0 ? 0 : valores.Average();
|
return valores.Count == 0 ? 0 : valores.Average();
|
||||||
}
|
}
|
||||||
|
|
||||||
double BuscarFatorCompensacao(string moduloId)
|
|
||||||
{
|
|
||||||
if (string.IsNullOrWhiteSpace(moduloId))
|
|
||||||
return 1.0;
|
|
||||||
|
|
||||||
if (MovimentacaoMovimentoCompensadoModel.compensacoes != null && MovimentacaoMovimentoCompensadoModel.compensacoes.TryGetValue(moduloId, out var fator))
|
|
||||||
{
|
|
||||||
return fator;
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
var dados = op.DispMvd?.Dados;
|
var dados = op.DispMvd?.Dados;
|
||||||
|
|
||||||
var modulos = dados?.Modulos?
|
var modulos = dados?.Modulos?
|
||||||
|
|
@ -5245,7 +5238,6 @@ namespace AgroBase.Models
|
||||||
CameraWorkerService.AtualizarDadosCameras();
|
CameraWorkerService.AtualizarDadosCameras();
|
||||||
GPSService.AtualizaDadosRedis();
|
GPSService.AtualizaDadosRedis();
|
||||||
LivoxManagerProcess.AtualizarLeitura();
|
LivoxManagerProcess.AtualizarLeitura();
|
||||||
MovimentacaoMovimentoCompensadoModel.AtualizarDados();
|
|
||||||
DalyBMSService.AtualizarDados();
|
DalyBMSService.AtualizarDados();
|
||||||
SerialService.DispositivosMapeados.ForEach(d => d.AtualizarDados());
|
SerialService.DispositivosMapeados.ForEach(d => d.AtualizarDados());
|
||||||
|
|
||||||
|
|
@ -5974,7 +5966,12 @@ namespace AgroBase.Models
|
||||||
public Direcao UltimaDirecao { get; set; }
|
public Direcao UltimaDirecao { get; set; }
|
||||||
|
|
||||||
// Compensação em curva
|
// Compensação em curva
|
||||||
public double FatorCompensacaoCurva { get; set; } = 1.0;
|
public int RPM_SP_Base { get; set; }
|
||||||
|
public double FatorCompensacao { get; set; }
|
||||||
|
public double RaioTrajetoriaM { get; set; }
|
||||||
|
public double IccX { get; set; }
|
||||||
|
public double IccY { get; set; }
|
||||||
|
public bool CompensacaoSaturada { get; set; }
|
||||||
|
|
||||||
public MovimentacaoModuloLogModel Clone()
|
public MovimentacaoModuloLogModel Clone()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -2437,6 +2437,9 @@ namespace AgroBase.Models
|
||||||
|
|
||||||
public void ProjetarTrajetoriaFixa(GPSModel posicaoRobo = null)
|
public void ProjetarTrajetoriaFixa(GPSModel posicaoRobo = null)
|
||||||
{
|
{
|
||||||
|
if (Variaveis.OperacaoEmAndamento.Sensoriamento?.Operacao?.Modo != ModoOperacao.MapaGPS)
|
||||||
|
return;
|
||||||
|
|
||||||
List<PontoTrajetoriaModel> _trajetoriaFixa = new List<PontoTrajetoriaModel>();
|
List<PontoTrajetoriaModel> _trajetoriaFixa = new List<PontoTrajetoriaModel>();
|
||||||
List<List<GPSModel>> corredores;
|
List<List<GPSModel>> corredores;
|
||||||
List<double> CorredoresLarguras;
|
List<double> CorredoresLarguras;
|
||||||
|
|
|
||||||
|
|
@ -826,6 +826,7 @@ namespace AgroBase.Models
|
||||||
public static double ComprimentoFrente { get; } = 90.0; // 7
|
public static double ComprimentoFrente { get; } = 90.0; // 7
|
||||||
public static double ComprimentoTras { get; } = 40.0; // 107
|
public static double ComprimentoTras { get; } = 40.0; // 107
|
||||||
public static double DistanciaEntreEixosCm { get; set; } = 92.0;
|
public static double DistanciaEntreEixosCm { get; set; } = 92.0;
|
||||||
|
public static double BitolaRodasCm { get; set; } = 75.0;
|
||||||
public static double LeverArmFrontalCm { get; set; } = 37.0; // cm
|
public static double LeverArmFrontalCm { get; set; } = 37.0; // cm
|
||||||
public static double LeverArmLateralCm { get; set; } = 0.0; // cm
|
public static double LeverArmLateralCm { get; set; } = 0.0; // cm
|
||||||
public static double LarguraEquipamentoMm
|
public static double LarguraEquipamentoMm
|
||||||
|
|
|
||||||
|
|
@ -322,7 +322,7 @@ namespace AgroBase.Services
|
||||||
bool PortaIsCan(SerialPort Porta = null);
|
bool PortaIsCan(SerialPort Porta = null);
|
||||||
void RegistrarHandler(uint endereco_rx, ICanMessageHandler handler);
|
void RegistrarHandler(uint endereco_rx, ICanMessageHandler handler);
|
||||||
bool Inicializar(int BitRate = 250);
|
bool Inicializar(int BitRate = 250);
|
||||||
void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] idTx, byte[] idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false);
|
bool AdicionarMensagemNaFila(T_Code Dispositivo, byte[] idTx, byte[] idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false);
|
||||||
void Fechar();
|
void Fechar();
|
||||||
bool PossuiMensagemPendente(T_Code dispositivo);
|
bool PossuiMensagemPendente(T_Code dispositivo);
|
||||||
|
|
||||||
|
|
@ -373,6 +373,10 @@ namespace AgroBase.Services
|
||||||
int PendentesNaoEnviadas { get; }
|
int PendentesNaoEnviadas { get; }
|
||||||
int PendentesAguardandoResposta { get; }
|
int PendentesAguardandoResposta { get; }
|
||||||
int FramesRxNaFila { get; }
|
int FramesRxNaFila { get; }
|
||||||
|
|
||||||
|
double IdadeMaxFilaNaoEnviadaMs { get; }
|
||||||
|
int PendentesNaoEnviadasAtrasadas { get; }
|
||||||
|
double IdadeMediaFilaNaoEnviadaMs { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -158,6 +158,54 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
public int FramesRxNaFila => FramesRecebidos?.Count ?? 0;
|
public int FramesRxNaFila => FramesRecebidos?.Count ?? 0;
|
||||||
|
|
||||||
|
public double IdadeMaxFilaNaoEnviadaMs
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
var pendentes = MensagensPendentes.Where(x => !x.Enviado).ToList();
|
||||||
|
|
||||||
|
if (!pendentes.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
|
return pendentes.Max(x => (agora - x.Momento).TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int PendentesNaoEnviadasAtrasadas
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
DateTime limite = DateTime.Now.AddMilliseconds(-500);
|
||||||
|
|
||||||
|
return MensagensPendentes.Count(x => !x.Enviado && x.Momento < limite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public double IdadeMediaFilaNaoEnviadaMs
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
var pendentes = MensagensPendentes.Where(x => !x.Enviado).ToList();
|
||||||
|
|
||||||
|
if (!pendentes.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
|
return pendentes.Average(x => (agora - x.Momento).TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private bool DebugMode = false;
|
private bool DebugMode = false;
|
||||||
private void MostrarLog(string message, uint id = 0)
|
private void MostrarLog(string message, uint id = 0)
|
||||||
{
|
{
|
||||||
|
|
@ -607,9 +655,9 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
public bool AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
||||||
{
|
{
|
||||||
if (_PortaCAN?.IsOpen != true) return;
|
if (_PortaCAN?.IsOpen != true) return false;
|
||||||
|
|
||||||
uint idTx = CanManager.ConverterIdCan(_idTx);
|
uint idTx = CanManager.ConverterIdCan(_idTx);
|
||||||
uint idRx = CanManager.ConverterIdCan(_idRx);
|
uint idRx = CanManager.ConverterIdCan(_idRx);
|
||||||
|
|
@ -653,12 +701,16 @@ namespace AgroBase.Services
|
||||||
//{
|
//{
|
||||||
// Console.WriteLine($"{DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss.fff")} - MENSAGEM LORA ADICIONADO NA FILA CAN, FUNC_CODE: {((CanMessagePosicaoDados)dados[0]).ToString()} ({((DadosLoRaParse)dados[0]).ToString()}) " + string.Join(" ", dados));
|
// Console.WriteLine($"{DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss.fff")} - MENSAGEM LORA ADICIONADO NA FILA CAN, FUNC_CODE: {((CanMessagePosicaoDados)dados[0]).ToString()} ({((DadosLoRaParse)dados[0]).ToString()}) " + string.Join(" ", dados));
|
||||||
//}
|
//}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MostrarLog($"Mensagem já pendente e idêntica. ID: {novaMensagem.IdTx}, Data: {FuncoesGlobais.ConverterComandoBytesParaTexto(novaMensagem.DataTx)}", idTx);
|
MostrarLog($"Mensagem já pendente e idêntica. ID: {novaMensagem.IdTx}, Data: {FuncoesGlobais.ConverterComandoBytesParaTexto(novaMensagem.DataTx)}", idTx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CanMessage ObterProximaMensagem()
|
private CanMessage ObterProximaMensagem()
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,54 @@ namespace AgroBase.Services
|
||||||
private long _rxTotalUltimaJanela = 0;
|
private long _rxTotalUltimaJanela = 0;
|
||||||
private DateTime _ultimaJanelaFreq = DateTime.UtcNow;
|
private DateTime _ultimaJanelaFreq = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public double IdadeMaxFilaNaoEnviadaMs
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
var pendentes = MensagensPendentes.Where(x => !x.Enviado).ToList();
|
||||||
|
|
||||||
|
if (!pendentes.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
|
return pendentes.Max(x => (agora - x.Momento).TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int PendentesNaoEnviadasAtrasadas
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
DateTime limite = DateTime.Now.AddMilliseconds(-500);
|
||||||
|
|
||||||
|
return MensagensPendentes.Count(x => !x.Enviado && x.Momento < limite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public double IdadeMediaFilaNaoEnviadaMs
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
var pendentes = MensagensPendentes.Where(x => !x.Enviado).ToList();
|
||||||
|
|
||||||
|
if (!pendentes.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
|
return pendentes.Average(x => (agora - x.Momento).TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private static long NowUnixMs()
|
private static long NowUnixMs()
|
||||||
{
|
{
|
||||||
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||||
|
|
@ -418,9 +466,9 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
public bool AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
||||||
{
|
{
|
||||||
if (!Connector.IsOpen) return;
|
if (!Connector.IsOpen) return false;
|
||||||
|
|
||||||
uint idTx = CanManager.ConverterIdCan(_idTx);
|
uint idTx = CanManager.ConverterIdCan(_idTx);
|
||||||
uint idRx = CanManager.ConverterIdCan(_idRx);
|
uint idRx = CanManager.ConverterIdCan(_idRx);
|
||||||
|
|
@ -467,12 +515,14 @@ namespace AgroBase.Services
|
||||||
//{
|
//{
|
||||||
// Console.WriteLine($"{DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss.fff")} - MENSAGEM LORA ADICIONADO NA FILA CAN, FUNC_CODE: {((CanMessagePosicaoDados)payload[1]).ToString()} ({((DadosLoRaParse)payload[1]).ToString()})" + string.Join(" ", payload));
|
// Console.WriteLine($"{DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss.fff")} - MENSAGEM LORA ADICIONADO NA FILA CAN, FUNC_CODE: {((CanMessagePosicaoDados)payload[1]).ToString()} ({((DadosLoRaParse)payload[1]).ToString()})" + string.Join(" ", payload));
|
||||||
//}
|
//}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MostrarLog($"Mensagem já pendente e idêntica. ID: {novaMensagem.IdTx}, Data: {FuncoesGlobais.ConverterComandoBytesParaTexto(novaMensagem.DataTx)}", idTx);
|
MostrarLog($"Mensagem já pendente e idêntica. ID: {novaMensagem.IdTx}, Data: {FuncoesGlobais.ConverterComandoBytesParaTexto(novaMensagem.DataTx)}", idTx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CanMessage ObterProximaMensagem()
|
private CanMessage ObterProximaMensagem()
|
||||||
|
|
|
||||||
|
|
@ -65,9 +65,7 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
lock (_LockMensagens)
|
lock (_LockMensagens)
|
||||||
{
|
{
|
||||||
return MensagensPendentes == null ||
|
return !MensagensPendentes.Any(x => !x.Enviado);
|
||||||
MensagensPendentes.Count == 0 ||
|
|
||||||
!MensagensPendentes.Any(x => !x.Enviado && x.Momento >= DateTime.Now.AddSeconds(-10));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -196,6 +194,53 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
private const int USR_FRAME_SIZE = 13;
|
private const int USR_FRAME_SIZE = 13;
|
||||||
|
|
||||||
|
public double IdadeMaxFilaNaoEnviadaMs
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
var pendentes = MensagensPendentes.Where(x => !x.Enviado).ToList();
|
||||||
|
|
||||||
|
if (!pendentes.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
|
return pendentes.Max(x => (agora - x.Momento).TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public int PendentesNaoEnviadasAtrasadas
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
DateTime limite = DateTime.Now.AddMilliseconds(-500);
|
||||||
|
|
||||||
|
return MensagensPendentes.Count(x => !x.Enviado && x.Momento < limite);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public double IdadeMediaFilaNaoEnviadaMs
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_LockMensagens)
|
||||||
|
{
|
||||||
|
var pendentes = MensagensPendentes.Where(x => !x.Enviado).ToList();
|
||||||
|
|
||||||
|
if (!pendentes.Any())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
|
return pendentes.Average(x => (agora - x.Momento).TotalMilliseconds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void MostrarLog(string message, uint id = 0)
|
private void MostrarLog(string message, uint id = 0)
|
||||||
{
|
{
|
||||||
if (DebugMode)
|
if (DebugMode)
|
||||||
|
|
@ -337,9 +382,10 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
public bool AdicionarMensagemNaFila(T_Code Dispositivo, byte[] _idTx, byte[] _idRx, byte funcCodeTx, byte funcCodeRx, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true, bool mensagemDescoberta = false)
|
||||||
{
|
{
|
||||||
if (!IsConnected) return;
|
if (!IsConnected)
|
||||||
|
return false;
|
||||||
|
|
||||||
uint idTx = CanManager.ConverterIdCan(_idTx);
|
uint idTx = CanManager.ConverterIdCan(_idTx);
|
||||||
uint idRx = CanManager.ConverterIdCan(_idRx);
|
uint idRx = CanManager.ConverterIdCan(_idRx);
|
||||||
|
|
@ -355,21 +401,19 @@ namespace AgroBase.Services
|
||||||
if (Dispositivo == T_Code.Mks)
|
if (Dispositivo == T_Code.Mks)
|
||||||
{
|
{
|
||||||
byte crc = CanManager.CalcularChecksum(idTx, dados.ToArray());
|
byte crc = CanManager.CalcularChecksum(idTx, dados.ToArray());
|
||||||
|
|
||||||
dados.Add(crc);
|
dados.Add(crc);
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
||||||
string chaveCorrelacao =
|
$"Payload CAN maior que 8 bytes. " +
|
||||||
CanManager.GerarChaveCorrelacao(
|
$"ID=0x{idTx:X}, Len={dados.Count}"
|
||||||
Dispositivo,
|
|
||||||
idTx,
|
|
||||||
idRx,
|
|
||||||
funcCodeTx,
|
|
||||||
funcCodeRx,
|
|
||||||
dados.ToArray()
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
string chaveCorrelacao = CanManager.GerarChaveCorrelacao(Dispositivo, idTx, idRx, funcCodeTx, funcCodeRx, dados.ToArray());
|
||||||
|
|
||||||
var novaMensagem = new CanMessage
|
var novaMensagem = new CanMessage
|
||||||
{
|
{
|
||||||
|
|
@ -390,13 +434,6 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
DateTime agora = DateTime.Now;
|
DateTime agora = DateTime.Now;
|
||||||
|
|
||||||
/*
|
|
||||||
* O OID não deve possuir mais de uma consulta aguardando
|
|
||||||
* resposta para o mesmo driver.
|
|
||||||
*
|
|
||||||
* Diferente do dedupe por ChaveInterna, esta proteção vale
|
|
||||||
* mesmo quando o parâmetro consultado é diferente.
|
|
||||||
*/
|
|
||||||
if (get && Dispositivo == T_Code.Oid)
|
if (get && Dispositivo == T_Code.Oid)
|
||||||
{
|
{
|
||||||
bool consultaOidAberta =
|
bool consultaOidAberta =
|
||||||
|
|
@ -407,14 +444,9 @@ namespace AgroBase.Services
|
||||||
x.IdTx == idTx);
|
x.IdTx == idTx);
|
||||||
|
|
||||||
if (consultaOidAberta)
|
if (consultaOidAberta)
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* 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 =
|
bool mesmaPendenciaAberta =
|
||||||
MensagensPendentes.Any(x =>
|
MensagensPendentes.Any(x =>
|
||||||
x.ComResposta &&
|
x.ComResposta &&
|
||||||
|
|
@ -422,42 +454,37 @@ namespace AgroBase.Services
|
||||||
x.ChaveInterna == chaveCorrelacao);
|
x.ChaveInterna == chaveCorrelacao);
|
||||||
|
|
||||||
if (get && mesmaPendenciaAberta)
|
if (get && mesmaPendenciaAberta)
|
||||||
return;
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Para comando sem resposta, evita empilhar comandos idênticos
|
|
||||||
* ainda não enviados. Depois de enviado, ele será limpo normalmente.
|
|
||||||
*/
|
|
||||||
bool comandoIdenticoNaoEnviado =
|
bool comandoIdenticoNaoEnviado =
|
||||||
MensagensPendentes.Any(x =>
|
MensagensPendentes.Any(x =>
|
||||||
!x.ComResposta &&
|
!x.ComResposta &&
|
||||||
!x.Enviado &&
|
!x.Enviado &&
|
||||||
x.IdTx == idTx &&
|
x.IdTx == idTx &&
|
||||||
x.DataTx != null &&
|
x.DataTx != null &&
|
||||||
x.DataTx.SequenceEqual(dados.ToArray()));
|
x.DataTx.SequenceEqual(
|
||||||
|
dados.ToArray()
|
||||||
|
));
|
||||||
|
|
||||||
if (!get && comandoIdenticoNaoEnviado)
|
if (!get && comandoIdenticoNaoEnviado)
|
||||||
return;
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Dedupe temporal extra, útil para comandos de controle que chegam
|
|
||||||
* repetidos por loop rápido.
|
|
||||||
*/
|
|
||||||
TimeSpan janela = get ? DEDUPE_JANELA_GET : DEDUPE_JANELA_SET;
|
TimeSpan janela = get ? DEDUPE_JANELA_GET : DEDUPE_JANELA_SET;
|
||||||
|
|
||||||
if (_ultimaFilaPorChave.TryGetValue(chaveCorrelacao, out DateTime ultima) && agora - ultima < janela)
|
if (_ultimaFilaPorChave.TryGetValue(chaveCorrelacao, out DateTime ultima) && agora - ultima < janela)
|
||||||
{
|
{
|
||||||
return;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
_ultimaFilaPorChave[chaveCorrelacao] = agora;
|
_ultimaFilaPorChave[chaveCorrelacao] = agora;
|
||||||
|
|
||||||
MensagensPendentes.Add(novaMensagem);
|
MensagensPendentes.Add(novaMensagem);
|
||||||
|
|
||||||
MostrarLog(
|
return true;
|
||||||
$"Mensagem adicionada. ID=0x{idTx:X}, Data={BitConverter.ToString(novaMensagem.DataTx)}",
|
|
||||||
idTx
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -696,6 +696,9 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
if (!CanManager.CanService.IsConnected)
|
||||||
|
return;
|
||||||
|
|
||||||
var modulos = Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos;
|
var modulos = Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos;
|
||||||
if (modulos == null)
|
if (modulos == null)
|
||||||
return;
|
return;
|
||||||
|
|
@ -862,7 +865,8 @@ namespace AgroBase.Services
|
||||||
double angulo,
|
double angulo,
|
||||||
int velocidade,
|
int velocidade,
|
||||||
Sentido sentido = Sentido.Horario,
|
Sentido sentido = Sentido.Horario,
|
||||||
ModosControle modo = ModosControle.AnguloAbsoluto)
|
ModosControle modo = ModosControle.AnguloAbsoluto,
|
||||||
|
int aceleracao = 0)
|
||||||
{
|
{
|
||||||
byte funcCode;
|
byte funcCode;
|
||||||
double reducao = Variaveis.OperacaoEmAndamento?.DispMvd?.Dados?.Modulos?.FirstOrDefault(x => x.DirMotor._EnderecoCAN_Tx == idTx)?.DirMotor?.Reducao ?? VariaveisEquipamento.ReducaoDirecional;
|
double reducao = Variaveis.OperacaoEmAndamento?.DispMvd?.Dados?.Modulos?.FirstOrDefault(x => x.DirMotor._EnderecoCAN_Tx == idTx)?.DirMotor?.Reducao ?? VariaveisEquipamento.ReducaoDirecional;
|
||||||
|
|
@ -873,6 +877,7 @@ namespace AgroBase.Services
|
||||||
sentido,
|
sentido,
|
||||||
modo,
|
modo,
|
||||||
reducao,
|
reducao,
|
||||||
|
aceleracao,
|
||||||
out funcCode
|
out funcCode
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -956,6 +961,7 @@ namespace AgroBase.Services
|
||||||
Sentido sentido,
|
Sentido sentido,
|
||||||
ModosControle modo,
|
ModosControle modo,
|
||||||
double reducao,
|
double reducao,
|
||||||
|
int aceleracao,
|
||||||
out byte funcCode)
|
out byte funcCode)
|
||||||
{
|
{
|
||||||
List<byte> payload = new List<byte>();
|
List<byte> payload = new List<byte>();
|
||||||
|
|
@ -963,7 +969,7 @@ namespace AgroBase.Services
|
||||||
ushort vel = (ushort)Math.Max(0, Math.Min(velocidade, ushort.MaxValue));
|
ushort vel = (ushort)Math.Max(0, Math.Min(velocidade, ushort.MaxValue));
|
||||||
byte[] velocidadeBytes = BitConverter.GetBytes(vel);
|
byte[] velocidadeBytes = BitConverter.GetBytes(vel);
|
||||||
|
|
||||||
byte acc = 0;
|
byte acc = (byte)aceleracao;
|
||||||
|
|
||||||
if (modo == ModosControle.Pulsos)
|
if (modo == ModosControle.Pulsos)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -22,8 +22,7 @@ namespace AgroBase.Services
|
||||||
private static readonly object _sync = new object();
|
private static readonly object _sync = new object();
|
||||||
private static readonly HashSet<byte> _handlersRegistrados = new HashSet<byte>();
|
private static readonly HashSet<byte> _handlersRegistrados = new HashSet<byte>();
|
||||||
private static readonly Dictionary<byte, DateTime> _ultimoEnvioConsultaPorDriver = new Dictionary<byte, DateTime>();
|
private static readonly Dictionary<byte, DateTime> _ultimoEnvioConsultaPorDriver = new Dictionary<byte, DateTime>();
|
||||||
private static readonly Dictionary<string, DateTime> _ultimoEnvioParametro = new Dictionary<string, DateTime>();
|
private static readonly Dictionary<string, DateTime> _ultimaConsultaParametro = new Dictionary<string, DateTime>();
|
||||||
private static readonly Dictionary<byte, OidParametros> _ultimaConsultaPendente = new Dictionary<byte, OidParametros>();
|
|
||||||
private static int _cursorDriverPolling = 0;
|
private static int _cursorDriverPolling = 0;
|
||||||
private static readonly Dictionary<string, string> _ultimoComandoControleAssinatura = new Dictionary<string, string>();
|
private static readonly Dictionary<string, string> _ultimoComandoControleAssinatura = new Dictionary<string, string>();
|
||||||
private static readonly Dictionary<string, DateTime> _ultimoComandoControleQuando = new Dictionary<string, DateTime>();
|
private static readonly Dictionary<string, DateTime> _ultimoComandoControleQuando = new Dictionary<string, DateTime>();
|
||||||
|
|
@ -35,11 +34,12 @@ namespace AgroBase.Services
|
||||||
*/
|
*/
|
||||||
private static readonly TimeSpan IntervaloMinimoConsultaPorDriver = TimeSpan.FromMilliseconds(40);
|
private static readonly TimeSpan IntervaloMinimoConsultaPorDriver = TimeSpan.FromMilliseconds(40);
|
||||||
private static readonly TimeSpan IntervaloMinimoMesmoParametro = TimeSpan.FromMilliseconds(300);
|
private static readonly TimeSpan IntervaloMinimoMesmoParametro = TimeSpan.FromMilliseconds(300);
|
||||||
private static readonly TimeSpan JanelaConsultaPendente = TimeSpan.FromMilliseconds(1200);
|
|
||||||
private static readonly TimeSpan JanelaDedupeComandoControle = TimeSpan.FromMilliseconds(80);
|
private static readonly TimeSpan JanelaDedupeComandoControle = TimeSpan.FromMilliseconds(80);
|
||||||
private static readonly TimeSpan TempoRespostaRecente = TimeSpan.FromSeconds(3);
|
private static readonly TimeSpan TempoRespostaRecente = TimeSpan.FromSeconds(3);
|
||||||
private static readonly TimeSpan TempoRemoverDriver = TimeSpan.FromSeconds(15);
|
private static readonly TimeSpan TempoRemoverDriver = TimeSpan.FromSeconds(5);
|
||||||
private static readonly TimeSpan PeriodoHeartbeat = TimeSpan.FromMilliseconds(500);
|
private static readonly TimeSpan PeriodoHeartbeat = TimeSpan.FromMilliseconds(500);
|
||||||
|
private static readonly TimeSpan IntervaloTentativaReconexao = TimeSpan.FromSeconds(1);
|
||||||
|
private static readonly Dictionary<byte, DateTime> _ultimaTentativaReconexaoPorDriver = new Dictionary<byte, DateTime>();
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Polling recomendado. Se outro ponto do sistema ainda chamar
|
* Polling recomendado. Se outro ponto do sistema ainda chamar
|
||||||
|
|
@ -314,8 +314,8 @@ namespace AgroBase.Services
|
||||||
));
|
));
|
||||||
|
|
||||||
campos.Add((
|
campos.Add((
|
||||||
$"{enderecoMov}.polling.timeout_pendente_ms",
|
$"{enderecoMov}.polling.timeout_resposta_gerenciado_por",
|
||||||
JanelaConsultaPendente.TotalMilliseconds
|
"CanService"
|
||||||
));
|
));
|
||||||
|
|
||||||
campos.Add((
|
campos.Add((
|
||||||
|
|
@ -499,6 +499,65 @@ namespace AgroBase.Services
|
||||||
tmrHeartbeat.Start();
|
tmrHeartbeat.Start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void GarantirMapeamentoDriver(MovimentacaoModel motor)
|
||||||
|
{
|
||||||
|
if (motor == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
string modId = motor.Mod_ID;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(modId))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (SerialService.DispositivosMapeados.Any(
|
||||||
|
x =>
|
||||||
|
x.Dispositivo == T_Code.Mov &&
|
||||||
|
x.Mod_ID == modId))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SerialService.DispositivosMapeados.Add(
|
||||||
|
new DispositivoDetalhesModel
|
||||||
|
{
|
||||||
|
Dispositivo = T_Code.Mov,
|
||||||
|
Endereco = CanManager.CanService._portName,
|
||||||
|
Versao = "1.0",
|
||||||
|
Mod_ID = modId,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
var op = Variaveis.OperacaoEmAndamento;
|
||||||
|
|
||||||
|
if (op.Sensoriamento?.Operacao?.ModsCalibragem == null)
|
||||||
|
op.Sensoriamento.Operacao.ModsCalibragem = new Dictionary<T_Code, Dictionary<string, bool>>();
|
||||||
|
|
||||||
|
if (!op.Sensoriamento.Operacao.ModsCalibragem.ContainsKey(T_Code.Mov))
|
||||||
|
{
|
||||||
|
op.Sensoriamento.Operacao.ModsCalibragem.Add(
|
||||||
|
T_Code.Mov,
|
||||||
|
new Dictionary<string, bool>()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
op.Sensoriamento.Operacao
|
||||||
|
.ModsCalibragem[T_Code.Mov][modId] = false;
|
||||||
|
|
||||||
|
op.Sensoriamento.InserirLog(
|
||||||
|
T_Code.Mov,
|
||||||
|
StatusModulo.Conectado,
|
||||||
|
0,
|
||||||
|
"Dispositivo conectado",
|
||||||
|
modId
|
||||||
|
);
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[OIDCanService] [CONEXAO] " +
|
||||||
|
$"MOV {modId} mapeado | " +
|
||||||
|
$"CAN=0x{motor._EnderecoCAN_Tx:X2}"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private static void RegistrarHandler(byte idRx)
|
private static void RegistrarHandler(byte idRx)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
|
|
@ -542,61 +601,44 @@ namespace AgroBase.Services
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void EnviarComando(
|
private static bool EnviarComando(T_Code dispositivo, byte idTx, byte idRx, byte funcCode, byte[] payload = null, bool get = true, bool contabilizarTimeoutHealth = true)
|
||||||
T_Code dispositivo,
|
|
||||||
byte idTx,
|
|
||||||
byte idRx,
|
|
||||||
byte funcCode,
|
|
||||||
byte[] payload = null,
|
|
||||||
bool get = true,
|
|
||||||
bool contabilizarTimeoutHealth = true)
|
|
||||||
{
|
{
|
||||||
RegistrarHandlersMotor(idTx, idRx);
|
RegistrarHandlersMotor(idTx, idRx);
|
||||||
|
|
||||||
CanManager.CanService.AdicionarMensagemNaFila(
|
return CanManager.CanService.AdicionarMensagemNaFila(
|
||||||
dispositivo,
|
dispositivo,
|
||||||
new byte[] { idTx },
|
new byte[] { idTx },
|
||||||
new byte[] { idRx },
|
new byte[] { idRx },
|
||||||
funcCode,
|
funcCode,
|
||||||
funcCode,
|
funcCode,
|
||||||
payload ?? new byte[0],
|
payload ?? new byte[0],
|
||||||
get,
|
get,
|
||||||
contabilizarTimeoutHealth
|
contabilizarTimeoutHealth
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void RequisitarDado(
|
public static void RequisitarDado(byte idTx, byte idRx, OidParametros dado)
|
||||||
byte idTx,
|
|
||||||
byte idRx,
|
|
||||||
OidParametros dado)
|
|
||||||
{
|
{
|
||||||
RequisitarDadoInterno(
|
RequisitarDadoInterno(
|
||||||
idTx,
|
idTx,
|
||||||
idRx,
|
idRx,
|
||||||
dado,
|
dado,
|
||||||
forcar: false,
|
forcar: false,
|
||||||
contabilizarTimeoutHealth:
|
contabilizarTimeoutHealth: DeveContabilizarTimeoutHealth(dado)
|
||||||
DeveContabilizarTimeoutHealth(dado)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool RequisitarDadoInterno(
|
private static bool RequisitarDadoInterno(byte idTx, byte idRx, OidParametros dado, bool forcar, bool contabilizarTimeoutHealth)
|
||||||
byte idTx,
|
|
||||||
byte idRx,
|
|
||||||
OidParametros dado,
|
|
||||||
bool forcar,
|
|
||||||
bool contabilizarTimeoutHealth)
|
|
||||||
{
|
{
|
||||||
DateTime agora =
|
DateTime agora = DateTime.Now;
|
||||||
DateTime.Now;
|
|
||||||
|
string chaveParametro =
|
||||||
|
idTx.ToString("X2") +
|
||||||
|
":" +
|
||||||
|
((byte)dado).ToString("X2");
|
||||||
|
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
string chaveParametro =
|
|
||||||
idTx.ToString("X2") +
|
|
||||||
":" +
|
|
||||||
((byte)dado).ToString("X2");
|
|
||||||
|
|
||||||
if (!forcar)
|
if (!forcar)
|
||||||
{
|
{
|
||||||
if (_ultimoEnvioConsultaPorDriver.TryGetValue(idTx, out DateTime ultimoDriver) && agora - ultimoDriver < IntervaloMinimoConsultaPorDriver)
|
if (_ultimoEnvioConsultaPorDriver.TryGetValue(idTx, out DateTime ultimoDriver) && agora - ultimoDriver < IntervaloMinimoConsultaPorDriver)
|
||||||
|
|
@ -604,80 +646,59 @@ namespace AgroBase.Services
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_ultimaConsultaPendente.TryGetValue(idTx, out OidParametros pendente))
|
|
||||||
{
|
|
||||||
bool possuiMomentoEnvio = _ultimoEnvioConsultaPorDriver.TryGetValue(idTx, out DateTime ultimoPendente);
|
|
||||||
|
|
||||||
bool aindaDentroDaJanela =
|
|
||||||
possuiMomentoEnvio &&
|
|
||||||
agora - ultimoPendente < JanelaConsultaPendente;
|
|
||||||
|
|
||||||
if (aindaDentroDaJanela)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* A consulta anterior venceu. Libera o driver para uma nova
|
|
||||||
* tentativa sem manter pendência órfã.
|
|
||||||
*/
|
|
||||||
_ultimaConsultaPendente.Remove(idTx);
|
|
||||||
|
|
||||||
Variaveis.MostrarLog(
|
|
||||||
"[OID] Consulta pendente expirada: " +
|
|
||||||
"driver=" + idTx.ToString("X2") +
|
|
||||||
", parametro=" + pendente
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
TimeSpan intervalo =
|
TimeSpan intervalo =
|
||||||
IntervaloParametro.TryGetValue(
|
IntervaloParametro.TryGetValue(
|
||||||
dado,
|
dado,
|
||||||
out TimeSpan valor
|
out TimeSpan valor)
|
||||||
)
|
|
||||||
? valor
|
? valor
|
||||||
: IntervaloMinimoMesmoParametro;
|
: IntervaloMinimoMesmoParametro;
|
||||||
|
|
||||||
if (_ultimoEnvioParametro
|
if (_ultimaConsultaParametro.TryGetValue(chaveParametro, out DateTime ultimoParam) && agora - ultimoParam < intervalo)
|
||||||
.TryGetValue(chaveParametro, out DateTime ultimoParam) &&
|
|
||||||
agora - ultimoParam < intervalo)
|
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ultimoEnvioConsultaPorDriver[idTx] = agora;
|
|
||||||
_ultimoEnvioParametro[chaveParametro] = agora;
|
|
||||||
_ultimaConsultaPendente[idTx] = dado;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Console.WriteLine(
|
/*
|
||||||
// "[OID/TX] idTx=" +
|
* A partir daqui quem decide se existe espaço
|
||||||
// idTx.ToString("X2") +
|
* para esta transação é o CanService.
|
||||||
// " idRx=" +
|
*/
|
||||||
// idRx.ToString("X2") +
|
bool aceitoNaFila = EnviarComando(
|
||||||
// " param=" +
|
|
||||||
// dado +
|
|
||||||
// " data=0F-" +
|
|
||||||
// ((byte)dado).ToString("X2")
|
|
||||||
//);
|
|
||||||
|
|
||||||
EnviarComando(
|
|
||||||
Dispositivo,
|
Dispositivo,
|
||||||
idTx,
|
idTx,
|
||||||
idRx,
|
idRx,
|
||||||
(byte)OidFuncCode.Consulta,
|
(byte)OidFuncCode.Consulta,
|
||||||
new byte[] { (byte)dado },
|
new byte[]
|
||||||
|
{
|
||||||
|
(byte)dado
|
||||||
|
},
|
||||||
get: true,
|
get: true,
|
||||||
contabilizarTimeoutHealth:
|
contabilizarTimeoutHealth: contabilizarTimeoutHealth
|
||||||
contabilizarTimeoutHealth
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (!aceitoNaFila)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IMPORTANTE:
|
||||||
|
* isso significa "aceito pela fila",
|
||||||
|
* não "fisicamente transmitido".
|
||||||
|
*
|
||||||
|
* Timeout de resposta não é contado aqui.
|
||||||
|
*/
|
||||||
|
DateTime momentoAceite = DateTime.Now;
|
||||||
|
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_ultimoEnvioConsultaPorDriver[idTx] = momentoAceite;
|
||||||
|
_ultimaConsultaParametro[chaveParametro] = momentoAceite;
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool DeveContabilizarTimeoutHealth(
|
private static bool DeveContabilizarTimeoutHealth(OidParametros dado)
|
||||||
OidParametros dado)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Saúde CAN deve refletir comandos/leituras críticas.
|
* Saúde CAN deve refletir comandos/leituras críticas.
|
||||||
|
|
@ -709,63 +730,132 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var modulos =
|
if (!CanManager.CanService.IsConnected)
|
||||||
Variaveis
|
return;
|
||||||
.OperacaoEmAndamento
|
|
||||||
.DispMvd?
|
|
||||||
.Dados?
|
|
||||||
.Modulos?
|
|
||||||
.Where(x =>
|
|
||||||
x?.MovMotor != null &&
|
|
||||||
x.MovMotor.Inicializado
|
|
||||||
)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
|
var modulos = Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos?.Where(x => x?.MovMotor != null).ToList();
|
||||||
if (modulos == null || modulos.Count == 0)
|
if (modulos == null || modulos.Count == 0)
|
||||||
{
|
{
|
||||||
RemoverDriversMuitoAntigos();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Existem OIDs configurados nesta operação e o CAN está ativo.
|
||||||
|
* O heartbeat deve existir independentemente de termos encontrado
|
||||||
|
* algum driver no discovery inicial.
|
||||||
|
*/
|
||||||
|
GarantirTimerHeartbeat();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 1. Primeiro atualiza lifecycle.
|
||||||
|
*/
|
||||||
|
MarcarDriversInativosPorTimeout();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 2. Offline recebe tentativa de reconexão.
|
||||||
|
*/
|
||||||
|
foreach (var modulo in modulos)
|
||||||
|
{
|
||||||
|
var motor =
|
||||||
|
modulo.MovMotor;
|
||||||
|
|
||||||
|
if (motor.Inicializado)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
TentarReconectarDriver(
|
||||||
|
motor._EnderecoCAN_Tx,
|
||||||
|
motor._EnderecoCAN_Rx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 3. Drivers online que acabaram de voltar
|
||||||
|
* precisam recuperar configuração.
|
||||||
|
*/
|
||||||
|
foreach (var modulo in modulos)
|
||||||
|
{
|
||||||
|
var motor =
|
||||||
|
modulo.MovMotor;
|
||||||
|
|
||||||
|
if (!motor.Inicializado)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
GarantirMapeamentoDriver(motor);
|
||||||
|
|
||||||
|
var leitura =
|
||||||
|
DadosLeitura.FirstOrDefault(
|
||||||
|
x => x.EnderecoTx ==
|
||||||
|
motor._EnderecoCAN_Tx
|
||||||
|
);
|
||||||
|
|
||||||
|
if (leitura != null &&
|
||||||
|
!leitura.Configurado)
|
||||||
|
{
|
||||||
|
ConfigurarDriver(motor);
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[OIDCanService] [CONFIG] " +
|
||||||
|
$"Driver CAN 0x{motor._EnderecoCAN_Tx:X2} " +
|
||||||
|
$"reconfigurado."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 4. Polling global dos online.
|
||||||
|
*/
|
||||||
|
var online =
|
||||||
|
modulos
|
||||||
|
.Where(x =>
|
||||||
|
x.MovMotor.Inicializado)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (online.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
int inicio;
|
int inicio;
|
||||||
|
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
if (_cursorDriverPolling < 0 ||
|
if (_cursorDriverPolling < 0 ||
|
||||||
_cursorDriverPolling >= modulos.Count)
|
_cursorDriverPolling >= online.Count)
|
||||||
{
|
{
|
||||||
_cursorDriverPolling = 0;
|
_cursorDriverPolling = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
inicio = _cursorDriverPolling;
|
inicio =
|
||||||
|
_cursorDriverPolling;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int deslocamento = 0;
|
for (int deslocamento = 0;
|
||||||
deslocamento < modulos.Count;
|
deslocamento < online.Count;
|
||||||
deslocamento++)
|
deslocamento++)
|
||||||
{
|
{
|
||||||
int index =
|
int index =
|
||||||
(inicio + deslocamento) % modulos.Count;
|
(inicio + deslocamento) %
|
||||||
|
online.Count;
|
||||||
|
|
||||||
var motor = modulos[index].MovMotor;
|
var motor =
|
||||||
|
online[index].MovMotor;
|
||||||
byte idTx = motor._EnderecoCAN_Tx;
|
|
||||||
byte idRx = motor._EnderecoCAN_Rx;
|
|
||||||
|
|
||||||
OidParametros? proximo =
|
OidParametros? proximo =
|
||||||
EscolherProximoParametro(idTx);
|
EscolherProximoParametro(
|
||||||
|
motor._EnderecoCAN_Tx
|
||||||
|
);
|
||||||
|
|
||||||
if (!proximo.HasValue)
|
if (!proximo.HasValue)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
bool enviado = RequisitarDadoInterno(
|
bool enviado =
|
||||||
idTx,
|
RequisitarDadoInterno(
|
||||||
idRx,
|
motor._EnderecoCAN_Tx,
|
||||||
proximo.Value,
|
motor._EnderecoCAN_Rx,
|
||||||
forcar: false,
|
proximo.Value,
|
||||||
contabilizarTimeoutHealth:
|
forcar: false,
|
||||||
DeveContabilizarTimeoutHealth(proximo.Value)
|
contabilizarTimeoutHealth:
|
||||||
);
|
DeveContabilizarTimeoutHealth(
|
||||||
|
proximo.Value)
|
||||||
|
);
|
||||||
|
|
||||||
if (!enviado)
|
if (!enviado)
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -773,22 +863,20 @@ namespace AgroBase.Services
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
_cursorDriverPolling =
|
_cursorDriverPolling =
|
||||||
(index + 1) % modulos.Count;
|
(index + 1) %
|
||||||
|
online.Count;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uma consulta nova por tick.
|
/*
|
||||||
|
* Uma consulta nova global por tick.
|
||||||
|
*/
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Mesmo quando ninguém estava apto a enviar, fazemos a limpeza.
|
|
||||||
*/
|
|
||||||
RemoverDriversMuitoAntigos();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine(
|
Variaveis.MostrarLog(
|
||||||
"[OID] Erro em AtualizarDados: " +
|
"[OIDCanService] Erro em AtualizarDados: " +
|
||||||
ex.Message
|
ex.Message
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -811,22 +899,17 @@ namespace AgroBase.Services
|
||||||
((byte)parametro).ToString("X2");
|
((byte)parametro).ToString("X2");
|
||||||
|
|
||||||
TimeSpan intervalo =
|
TimeSpan intervalo =
|
||||||
IntervaloParametro.TryGetValue(
|
IntervaloParametro.TryGetValue(parametro, out TimeSpan configurado)
|
||||||
parametro,
|
|
||||||
out TimeSpan configurado
|
|
||||||
)
|
|
||||||
? configurado
|
? configurado
|
||||||
: IntervaloMinimoMesmoParametro;
|
: IntervaloMinimoMesmoParametro;
|
||||||
|
|
||||||
if (!_ultimoEnvioParametro.TryGetValue(
|
if (!_ultimaConsultaParametro.TryGetValue(chave, out DateTime ultimaConsulta))
|
||||||
chave,
|
|
||||||
out DateTime ultimoEnvio))
|
|
||||||
{
|
{
|
||||||
// Parâmetro nunca consultado tem prioridade máxima.
|
// Parâmetro nunca consultado tem prioridade máxima.
|
||||||
return parametro;
|
return parametro;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeSpan decorrido = agora - ultimoEnvio;
|
TimeSpan decorrido = agora - ultimaConsulta;
|
||||||
|
|
||||||
if (decorrido < intervalo)
|
if (decorrido < intervalo)
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -853,10 +936,9 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RemoverDriversMuitoAntigos()
|
private static void MarcarDriversInativosPorTimeout()
|
||||||
{
|
{
|
||||||
DateTime agora =
|
DateTime agora = DateTime.Now;
|
||||||
DateTime.Now;
|
|
||||||
|
|
||||||
List<OIDModel> antigos;
|
List<OIDModel> antigos;
|
||||||
|
|
||||||
|
|
@ -868,19 +950,69 @@ namespace AgroBase.Services
|
||||||
x.Iniciado &&
|
x.Iniciado &&
|
||||||
x.UltimoComandoRecebido != DateTime.MinValue &&
|
x.UltimoComandoRecebido != DateTime.MinValue &&
|
||||||
agora - x.UltimoComandoRecebido >
|
agora - x.UltimoComandoRecebido >
|
||||||
TempoRemoverDriver)
|
TempoRemoverDriver)
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var antigo in antigos)
|
foreach (var antigo in antigos)
|
||||||
|
{
|
||||||
antigo.Iniciado = false;
|
antigo.Iniciado = false;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Importantíssimo:
|
||||||
|
* não sabemos se houve apenas perda CAN
|
||||||
|
* ou reboot/power-cycle do OID.
|
||||||
|
*/
|
||||||
|
antigo.Configurado = false;
|
||||||
|
|
||||||
|
lock (_sync)
|
||||||
|
{
|
||||||
|
_ultimoEnvioConsultaPorDriver.Remove(
|
||||||
|
antigo.EnderecoTx
|
||||||
|
);
|
||||||
|
|
||||||
|
string prefixo =
|
||||||
|
antigo.EnderecoTx.ToString("X2") +
|
||||||
|
":";
|
||||||
|
|
||||||
|
var chaves =
|
||||||
|
_ultimaConsultaParametro.Keys
|
||||||
|
.Where(x =>
|
||||||
|
x.StartsWith(prefixo))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
foreach (var chave in chaves)
|
||||||
|
_ultimaConsultaParametro.Remove(chave);
|
||||||
|
}
|
||||||
|
|
||||||
|
Variaveis.MostrarLog(
|
||||||
|
$"[OIDCanService] [CONEXAO] " +
|
||||||
|
$"Driver CAN 0x{antigo.EnderecoTx:X2} OFFLINE | " +
|
||||||
|
$"sem resposta há " +
|
||||||
|
$"{(agora - antigo.UltimoComandoRecebido).TotalSeconds:F1}s"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ComandoControle(
|
private static void TentarReconectarDriver(byte idTx, byte idRx)
|
||||||
byte idTx,
|
{
|
||||||
byte idRx,
|
DateTime agora = DateTime.Now;
|
||||||
OidFuncCode funcCode,
|
|
||||||
double setPoint)
|
lock (_sync)
|
||||||
|
{
|
||||||
|
if (_ultimaTentativaReconexaoPorDriver.TryGetValue(idTx, out DateTime ultima) && agora - ultima < IntervaloTentativaReconexao)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ultimaTentativaReconexaoPorDriver[idTx] = agora;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegistrarHandlersMotor(idTx, idRx);
|
||||||
|
EnviarComandoDescoberta(idTx, idRx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ComandoControle(byte idTx, byte idRx, OidFuncCode funcCode, double setPoint)
|
||||||
{
|
{
|
||||||
byte[] setPointBytes =
|
byte[] setPointBytes =
|
||||||
MontarPayloadControle(
|
MontarPayloadControle(
|
||||||
|
|
@ -914,10 +1046,7 @@ namespace AgroBase.Services
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool DeveDroparComandoDuplicado(
|
private static bool DeveDroparComandoDuplicado(byte idTx, OidFuncCode funcCode, byte[] payload)
|
||||||
byte idTx,
|
|
||||||
OidFuncCode funcCode,
|
|
||||||
byte[] payload)
|
|
||||||
{
|
{
|
||||||
if (funcCode == OidFuncCode.Heartbeat)
|
if (funcCode == OidFuncCode.Heartbeat)
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -955,10 +1084,7 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static byte[] MontarPayloadControle(
|
private static byte[] MontarPayloadControle(byte idTx, OidFuncCode funcCode, double setPoint)
|
||||||
byte idTx,
|
|
||||||
OidFuncCode funcCode,
|
|
||||||
double setPoint)
|
|
||||||
{
|
{
|
||||||
OIDModel item =
|
OIDModel item =
|
||||||
DadosLeitura.FirstOrDefault(
|
DadosLeitura.FirstOrDefault(
|
||||||
|
|
@ -1057,8 +1183,8 @@ namespace AgroBase.Services
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
Console.WriteLine(
|
Variaveis.MostrarLog(
|
||||||
"[OID] Comando não mapeado: " +
|
"[OIDCanService] Comando não mapeado: " +
|
||||||
funcCode
|
funcCode
|
||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -1400,18 +1526,14 @@ namespace AgroBase.Services
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (mensagem == null ||
|
if (mensagem == null || mensagem.DataRx == null || mensagem.DataRx.Length < 2)
|
||||||
mensagem.DataRx == null ||
|
|
||||||
mensagem.DataRx.Length < 2)
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
OidFuncCode func =
|
OidFuncCode func = (OidFuncCode)mensagem.DataRx[0];
|
||||||
(OidFuncCode)mensagem.DataRx[0];
|
|
||||||
|
|
||||||
if (func != OidFuncCode.Consulta &&
|
if (func != OidFuncCode.Consulta && func != OidFuncCode.ConsultaStatusRetornoZero)
|
||||||
func != OidFuncCode.ConsultaStatusRetornoZero)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Controle não deveria responder. Se responder algo
|
* Controle não deveria responder. Se responder algo
|
||||||
|
|
@ -1434,7 +1556,7 @@ namespace AgroBase.Services
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//Console.WriteLine(
|
//Variaveis.MostrarLog(
|
||||||
// "[OID/RX] idTx=" +
|
// "[OID/RX] idTx=" +
|
||||||
// mensagem.IdTx.ToString("X") +
|
// mensagem.IdTx.ToString("X") +
|
||||||
// " idRx=" +
|
// " idRx=" +
|
||||||
|
|
@ -1443,120 +1565,135 @@ namespace AgroBase.Services
|
||||||
// BitConverter.ToString(mensagem.DataRx ?? new byte[0])
|
// BitConverter.ToString(mensagem.DataRx ?? new byte[0])
|
||||||
//);
|
//);
|
||||||
|
|
||||||
OidParametros parametro =
|
OidParametros parametro = (OidParametros)mensagem.DataRx[1];
|
||||||
(OidParametros)mensagem.DataRx[1];
|
|
||||||
|
|
||||||
ProcessarParametro(
|
bool processado = ProcessarParametro(item, parametro, mensagem.DataRx);
|
||||||
item,
|
|
||||||
parametro,
|
if (!processado)
|
||||||
mensagem.DataRx
|
{
|
||||||
);
|
Variaveis.MostrarLog(
|
||||||
|
$"[OIDCanService] [RX] " +
|
||||||
|
$"Resposta inválida ignorada | " +
|
||||||
|
$"driver=0x{item.EnderecoTx:X2} | " +
|
||||||
|
$"parametro={parametro}"
|
||||||
|
);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool jaFoiVisto = item.UltimoComandoRecebido != DateTime.MinValue;
|
||||||
|
bool estavaOffline = jaFoiVisto && !item.Iniciado;
|
||||||
|
|
||||||
item.UltimoComandoRecebido = DateTime.Now;
|
item.UltimoComandoRecebido = DateTime.Now;
|
||||||
item.Iniciado = true;
|
item.Iniciado = true;
|
||||||
|
|
||||||
|
DateTime agoraRx = DateTime.Now;
|
||||||
|
string chaveParametro = item.EnderecoTx.ToString("X2") + ":" + ((byte)parametro).ToString("X2");
|
||||||
lock (_sync)
|
lock (_sync)
|
||||||
{
|
{
|
||||||
RemoverPendenteSeCorresponder(
|
_ultimaConsultaParametro[chaveParametro] = agoraRx;
|
||||||
item.EnderecoTx,
|
}
|
||||||
parametro
|
|
||||||
);
|
|
||||||
|
|
||||||
if (item.EnderecoRx != item.EnderecoTx)
|
if (estavaOffline)
|
||||||
{
|
{
|
||||||
RemoverPendenteSeCorresponder(
|
Variaveis.MostrarLog(
|
||||||
item.EnderecoRx,
|
$"[OIDCanService] [CONEXAO] " +
|
||||||
parametro
|
$"Driver CAN 0x{item.EnderecoTx:X2} ONLINE novamente"
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
Console.WriteLine(
|
Variaveis.MostrarLog(
|
||||||
"[OID] Erro ao interpretar resposta CAN: " +
|
"[OIDCanService] Erro ao interpretar resposta CAN: " +
|
||||||
ex.Message
|
ex.Message
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void RemoverPendenteSeCorresponder(byte endereco, OidParametros parametroRecebido)
|
private bool ProcessarParametro(OIDModel item, OidParametros parametro, byte[] data)
|
||||||
{
|
|
||||||
if (_ultimaConsultaPendente.TryGetValue(endereco, out OidParametros pendente) && pendente == parametroRecebido)
|
|
||||||
{
|
|
||||||
_ultimaConsultaPendente.Remove(endereco);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ProcessarParametro(
|
|
||||||
OIDModel item,
|
|
||||||
OidParametros parametro,
|
|
||||||
byte[] data)
|
|
||||||
{
|
{
|
||||||
switch (parametro)
|
switch (parametro)
|
||||||
{
|
{
|
||||||
case OidParametros.CodErro:
|
case OidParametros.CodErro:
|
||||||
if (data.Length >= 4)
|
if (data.Length >= 4)
|
||||||
{
|
{
|
||||||
short codigo =
|
short codigo = ReadInt16BE(data, 2);
|
||||||
ReadInt16BE(data, 2);
|
item.CodigoAlarme.valor = (OidCodigoErro)codigo;
|
||||||
|
return true;
|
||||||
item.CodigoAlarme.valor =
|
|
||||||
(OidCodigoErro)codigo;
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.VelocidadeAtual:
|
case OidParametros.VelocidadeAtual:
|
||||||
if (data.Length >= 6)
|
if (data.Length >= 6)
|
||||||
|
{
|
||||||
item.ERPM.valor = ReadInt32BE(data, 2);
|
item.ERPM.valor = ReadInt32BE(data, 2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.CicloTrabalho:
|
case OidParametros.CicloTrabalho:
|
||||||
if (data.Length >= 4)
|
if (data.Length >= 4)
|
||||||
item.CicloTrabalho.valor =
|
{
|
||||||
ReadInt16BE(data, 2) / 10.0;
|
item.CicloTrabalho.valor = ReadInt16BE(data, 2) / 10.0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.Potencia:
|
case OidParametros.Potencia:
|
||||||
if (data.Length >= 4)
|
if (data.Length >= 4)
|
||||||
item.Potencia.valor =
|
{
|
||||||
ReadInt16BE(data, 2);
|
item.Potencia.valor = ReadInt16BE(data, 2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.Tensao:
|
case OidParametros.Tensao:
|
||||||
if (data.Length >= 4)
|
if (data.Length >= 4)
|
||||||
item.Tensao.valor =
|
{
|
||||||
ReadInt16BE(data, 2);
|
item.Tensao.valor = ReadInt16BE(data, 2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.CorrenteMotor:
|
case OidParametros.CorrenteMotor:
|
||||||
if (data.Length >= 4)
|
if (data.Length >= 4)
|
||||||
item.CorrenteMotor.valor =
|
{
|
||||||
ReadInt16BE(data, 2) / 100.0;
|
item.CorrenteMotor.valor = ReadInt16BE(data, 2) / 100.0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.CorrenteBarramento:
|
case OidParametros.CorrenteBarramento:
|
||||||
if (data.Length >= 4)
|
if (data.Length >= 4)
|
||||||
item.CorrenteBarramento.valor =
|
{
|
||||||
ReadInt16BE(data, 2) / 100.0;
|
item.CorrenteBarramento.valor = ReadInt16BE(data, 2) / 100.0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.Temperatura:
|
case OidParametros.Temperatura:
|
||||||
if (data.Length >= 4)
|
if (data.Length >= 4)
|
||||||
item.Temperatura.valor =
|
{
|
||||||
ReadInt16BE(data, 2);
|
item.Temperatura.valor = ReadInt16BE(data, 2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.PosicaoAbsoluta:
|
case OidParametros.PosicaoAbsoluta:
|
||||||
if (data.Length >= 6)
|
if (data.Length >= 6)
|
||||||
item.PosicaoAbsoluta =
|
{
|
||||||
ReadInt32BE(data, 2) / 100.0;
|
item.PosicaoAbsoluta = ReadInt32BE(data, 2) / 100.0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.PosicaoRelativa:
|
case OidParametros.PosicaoRelativa:
|
||||||
if (data.Length >= 6)
|
if (data.Length >= 6)
|
||||||
item.PosicaoRelativa =
|
{
|
||||||
ReadInt32BE(data, 2) / 100.0;
|
item.PosicaoRelativa = ReadInt32BE(data, 2) / 100.0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.ModoCodificador:
|
case OidParametros.ModoCodificador:
|
||||||
|
|
@ -1564,7 +1701,10 @@ namespace AgroBase.Services
|
||||||
* Manual: resposta DLC 3, DATA[2]=0/1.
|
* Manual: resposta DLC 3, DATA[2]=0/1.
|
||||||
*/
|
*/
|
||||||
if (data.Length >= 3)
|
if (data.Length >= 3)
|
||||||
|
{
|
||||||
item.ModoCodificador = data[2];
|
item.ModoCodificador = data[2];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.ErroLerCache:
|
case OidParametros.ErroLerCache:
|
||||||
|
|
@ -1573,20 +1713,25 @@ namespace AgroBase.Services
|
||||||
* Mantém o primeiro para compatibilidade.
|
* Mantém o primeiro para compatibilidade.
|
||||||
*/
|
*/
|
||||||
if (data.Length >= 3)
|
if (data.Length >= 3)
|
||||||
|
{
|
||||||
item.ErroHistorico = data[2];
|
item.ErroHistorico = data[2];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case OidParametros.IO_Status:
|
case OidParametros.IO_Status:
|
||||||
if (data.Length >= 4)
|
if (data.Length >= 4)
|
||||||
item.IO_Status =
|
{
|
||||||
ReadUInt16BE(data, 2);
|
item.IO_Status = ReadUInt16BE(data, 2);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessarStatusRetornoZero(
|
private void ProcessarStatusRetornoZero(OIDModel item, byte[] data)
|
||||||
OIDModel item,
|
|
||||||
byte[] data)
|
|
||||||
{
|
{
|
||||||
/*
|
/*
|
||||||
* Manual:
|
* Manual:
|
||||||
|
|
@ -1603,9 +1748,7 @@ namespace AgroBase.Services
|
||||||
|
|
||||||
internal static class OidModelExtensions
|
internal static class OidModelExtensions
|
||||||
{
|
{
|
||||||
public static void SetModoControleSeguro(
|
public static void SetModoControleSeguro(this OIDModel model, OIDModoControle modo)
|
||||||
this OIDModel model,
|
|
||||||
OIDModoControle modo)
|
|
||||||
{
|
{
|
||||||
if (model == null)
|
if (model == null)
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
|
|
@ -1430,6 +1430,9 @@ namespace AgroBase.Services.Operadores
|
||||||
("pendentes_nao_enviadas", DadosCan.PendentesNaoEnviadas),
|
("pendentes_nao_enviadas", DadosCan.PendentesNaoEnviadas),
|
||||||
("pendentes_aguardando_resposta", DadosCan.PendentesAguardandoResposta),
|
("pendentes_aguardando_resposta", DadosCan.PendentesAguardandoResposta),
|
||||||
("frames_rx_fila", DadosCan.FramesRxNaFila),
|
("frames_rx_fila", DadosCan.FramesRxNaFila),
|
||||||
|
("idade_max_fila_nao_enviada_ms", DadosCan.IdadeMaxFilaNaoEnviadaMs),
|
||||||
|
("pendentes_nao_enviadas_atrasadas", DadosCan.PendentesNaoEnviadasAtrasadas),
|
||||||
|
("idade_media_fila_nao_enviada_ms", DadosCan.IdadeMediaFilaNaoEnviadaMs),
|
||||||
|
|
||||||
// Latência
|
// Latência
|
||||||
("latencia_media_ms", DadosCan.LatenciaMediaMs),
|
("latencia_media_ms", DadosCan.LatenciaMediaMs),
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,9 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
pend_nao_env = int(m.get("pendentes_nao_enviadas", 0) or 0)
|
pend_nao_env = int(m.get("pendentes_nao_enviadas", 0) or 0)
|
||||||
pend_resp = int(m.get("pendentes_aguardando_resposta", 0) or 0)
|
pend_resp = int(m.get("pendentes_aguardando_resposta", 0) or 0)
|
||||||
frames_rx_fila = int(m.get("frames_rx_fila", 0) or 0)
|
frames_rx_fila = int(m.get("frames_rx_fila", 0) or 0)
|
||||||
|
idade_max_fila_nao_enviada_ms = float(m.get("idade_max_fila_nao_enviada_ms", 0.0) or 0.0)
|
||||||
|
pendentes_nao_enviadas_atrasadas = int(m.get("pendentes_nao_enviadas_atrasadas", 0) or 0)
|
||||||
|
idade_media_fila_nao_enviada_ms = float(m.get("idade_media_fila_nao_enviada_ms", 0.0) or 0.0)
|
||||||
|
|
||||||
timeouts_total = int(m.get("timeouts_resposta_total", 0) or 0)
|
timeouts_total = int(m.get("timeouts_resposta_total", 0) or 0)
|
||||||
timeouts_por_id_total = m.get("timeouts_por_id_total", {}) or {}
|
timeouts_por_id_total = m.get("timeouts_por_id_total", {}) or {}
|
||||||
|
|
@ -276,6 +279,9 @@ class ModuloCAN(ModuloDiagnosticoBase):
|
||||||
"pendentes_total": pend_total,
|
"pendentes_total": pend_total,
|
||||||
"pendentes_nao_enviadas": pend_nao_env,
|
"pendentes_nao_enviadas": pend_nao_env,
|
||||||
"pendentes_aguardando_resposta": pend_resp,
|
"pendentes_aguardando_resposta": pend_resp,
|
||||||
|
"idade_max_fila_nao_enviada_ms": idade_max_fila_nao_enviada_ms,
|
||||||
|
"idade_media_fila_nao_enviada_ms": idade_media_fila_nao_enviada_ms,
|
||||||
|
"pendentes_nao_enviadas_atrasadas": pendentes_nao_enviadas_atrasadas,
|
||||||
"frames_rx_fila": frames_rx_fila,
|
"frames_rx_fila": frames_rx_fila,
|
||||||
"timeouts_resposta_total": timeouts_total,
|
"timeouts_resposta_total": timeouts_total,
|
||||||
"timeouts_por_id_total": timeouts_por_id_total,
|
"timeouts_por_id_total": timeouts_por_id_total,
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue