diff --git a/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs b/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs index 2a948e5a7..bf684d46a 100644 --- a/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs +++ b/AgroBase/AgroBase/Models/Modules/MovimentacaoUnificadoModel.cs @@ -428,28 +428,22 @@ namespace AgroBase.Models.Modules DateTime agora = DateTime.Now; - if (!MKS057DCanService.Iniciado && SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Dir)) - { - foreach (var leitura in MKS057DCanService.DadosLeitura.Where(x => x.UltimoComandoRecebido.AddSeconds(10) < agora)) - { - /* - * Poke leve para tentar recuperar leitura. - * O MKS057DCanService agora já segura throttle interno. - */ - MKS057DCanService.RequisitarDado( - leitura.EnderecoTx, - leitura.EnderecoRx, - MKS057DCanService.MksHandler.MksFuncCode.PulsosRecebidos - ); - } - - tmrRegistradorDIR.SetInterval(1000); - await Task.CompletedTask; - return; - } + /* + * Toda a manutenção da comunicação com os MKS fica concentrada + * no MKS057DCanService: + * + * - polling dos drivers online; + * - throttle por driver/parâmetro; + * - prioridade durante referenciamento; + * - identificação de drivers sem resposta; + * - marcação individual como offline; + * - tentativa periódica de reconexão dos drivers offline. + * + * O timer não precisa mais fazer poke ou polling individual. + */ + MKS057DCanService.AtualizarDados(); bool deveEnviarControle = _ultimoEnvioControleDir == DateTime.MinValue || agora - _ultimoEnvioControleDir >= IntervaloControleDir; - bool deveRequisitarSensoresExternos = _ultimoSensoresExternosDir == DateTime.MinValue || agora - _ultimoSensoresExternosDir >= IntervaloSensoresExternosDir; bool enviouControle = false; @@ -459,43 +453,43 @@ namespace AgroBase.Models.Modules foreach (var modulo in Modulos) { - if (modulo?.DirMotor == null || !modulo.DirMotor.Inicializado) + var driver = modulo?.DirMotor; + + if (driver == null || !driver.Inicializado) continue; - byte addrTx = modulo.DirMotor._EnderecoCAN_Tx; - byte addrRx = modulo.DirMotor._EnderecoCAN_Rx; - /* - * Agora é igual ao MOV/OID: - * o service escolhe PulsosRecebidos, IO, RPM, ErroAcumulado etc. - * com throttle, cursor e prioridade de referenciamento. - */ - MKS057DCanService.RequisitarProximoDado(addrTx, addrRx); - - /* - * Se isso também gera CAN, melhor não chamar em todo tick. - * Se for leitura local/barata, pode voltar para todo tick. + * Sensores externos do direcional. + * + * Mantemos o throttle de 500 ms para não gerar tráfego + * desnecessário no sensoriamento. */ if (deveRequisitarSensoresExternos) { - modulo.DirMotor.RequisitarDadosSensoresExternos(); + driver.RequisitarDadosSensoresExternos(); requisitouSensoresExternos = true; } /* - * Reenvio de controle direcional. + * Reenvio periódico do comando enquanto o ângulo solicitado + * ainda não foi atingido. * - * Não precisa mais reenviar no ritmo da TaxaAmostragem. - * O MKS057DCanService já tem dedupe curto, mas ainda assim - * é melhor não transformar posição em metralhadora CAN. + * Durante referenciamento não interferimos no comando, + * pois o próprio fluxo de referência assume o controle do MKS. */ - if (!MKS057DCanService.Referenciando && deveEnviarControle && controleDir != null && !modulo.DirMotor.AtingiuAnguloSP) + if (!MKS057DCanService.Referenciando && deveEnviarControle && controleDir != null && !driver.AtingiuAnguloSP) { - modulo.DirMotor.EnviarComandoControle(controleDir.UltimaDirecao); + driver.EnviarComandoControle(controleDir.UltimaDirecao); enviouControle = true; } - modulo.DirMotor.AtualizarSaudeSeguimentoAngular(agora); + /* + * Atualização do acompanhamento angular. + * + * Saúde global será validada em outro tópico, mas mantemos + * aqui o comportamento já existente. + */ + driver.AtualizarSaudeSeguimentoAngular(agora); } if (enviouControle) @@ -504,6 +498,10 @@ namespace AgroBase.Models.Modules if (requisitouSensoresExternos) _ultimoSensoresExternosDir = agora; + /* + * Normalmente 80 ms. + * Durante referenciamento, 40 ms. + */ tmrRegistradorDIR.SetInterval(MKS057DCanService.TaxaAmostragem); await Task.CompletedTask; diff --git a/AgroBase/AgroBase/Services/MKS057DCanService.cs b/AgroBase/AgroBase/Services/MKS057DCanService.cs index e3e1e410c..b1d085d43 100644 --- a/AgroBase/AgroBase/Services/MKS057DCanService.cs +++ b/AgroBase/AgroBase/Services/MKS057DCanService.cs @@ -48,10 +48,13 @@ namespace AgroBase.Services */ 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 TempoRespostaDescoberta = TimeSpan.FromMilliseconds(700); private static readonly TimeSpan JanelaDedupeComandoControle = TimeSpan.FromMilliseconds(20); + private static readonly Dictionary _ultimaTentativaReconexaoPorDriver = new Dictionary(); + private static readonly TimeSpan IntervaloTentativaReconexao = TimeSpan.FromSeconds(1); + /* * Pooling de produção: * operação normal = ângulo rápido + IO lento. @@ -693,37 +696,73 @@ namespace AgroBase.Services { try { - var modulos = - Variaveis - .OperacaoEmAndamento - .DispMvd? - .Dados? - .Modulos; - + var modulos = Variaveis.OperacaoEmAndamento.DispMvd?.Dados?.Modulos; if (modulos == null) return; + MarcarDriversInativosPorTimeout(); + foreach (var mod in modulos) { var driver = mod?.DirMotor; - if (driver == null || !driver.Inicializado) + if (driver == null) continue; - RequisitarProximoDado( - driver._EnderecoCAN_Tx, - driver._EnderecoCAN_Rx - ); - } + if (driver.Inicializado) + { + RequisitarProximoDado(driver._EnderecoCAN_Tx, driver._EnderecoCAN_Rx); + continue; + } - RemoverDriversMuitoAntigos(); + TentarReconectarDriver(driver._EnderecoCAN_Tx, driver._EnderecoCAN_Rx); + } } catch (Exception ex) { - Console.WriteLine("[MKS] Erro em AtualizarDados: " + ex.Message); + Variaveis.MostrarLog("[MKS057DCanService] Erro em AtualizarDados: " + ex.Message); } } + private static void TentarReconectarDriver(byte idTx, byte idRx) + { + /* + * Só tentamos recuperar um driver que já existiu anteriormente. + * + * Descoberta inicial continua sendo responsabilidade de + * VerificaDispositivoConectado(). + */ + bool driverConhecido; + + lock (DadosLeitura) + { + driverConhecido = DadosLeitura.Any(x => x.EnderecoTx == idTx); + } + + if (!driverConhecido) + return; + + DateTime agora = DateTime.Now; + + lock (_sync) + { + if (_ultimaTentativaReconexaoPorDriver.TryGetValue(idTx, out DateTime ultima) && agora - ultima < IntervaloTentativaReconexao) + { + return; + } + + _ultimaTentativaReconexaoPorDriver[idTx] = agora; + } + + /* + * Versão é uma consulta pequena e já é utilizada + * pela descoberta. + * + * Não contamina timeout do health. + */ + EnviarComandoDescoberta(idTx, idRx); + } + private static MksFuncCode? EscolherProximoParametro(byte idTx) { DateTime agora = DateTime.Now; @@ -756,7 +795,7 @@ namespace AgroBase.Services return null; } - private static void RemoverDriversMuitoAntigos() + private static void MarcarDriversInativosPorTimeout() { DateTime agora = DateTime.Now; @@ -774,7 +813,17 @@ namespace AgroBase.Services } foreach (var antigo in antigos) + { antigo.Iniciado = false; + + double tempoSemResposta = (agora - antigo.UltimoComandoRecebido).TotalSeconds; + + Variaveis.MostrarLog( + $"[MKS057DCanService] [CONEXAO] " + + $"Driver CAN 0x{antigo.EnderecoTx:X2} OFFLINE | " + + $"sem resposta há {tempoSemResposta:F1}s" + ); + } } public static void SetZero(byte idTx, byte idRx) @@ -1130,9 +1179,7 @@ namespace AgroBase.Services { try { - if (mensagem == null || - mensagem.DataRx == null || - mensagem.DataRx.Length < 1) + if (mensagem == null || mensagem.DataRx == null || mensagem.DataRx.Length < 1) { return; } @@ -1142,6 +1189,8 @@ namespace AgroBase.Services var item = ObterOuCriar(mensagem); + bool estavaOffline = !item.Iniciado && item.UltimoComandoRecebido != DateTime.MinValue; + switch (func) { case MksFuncCode.EncoderCarregado: @@ -1188,10 +1237,15 @@ namespace AgroBase.Services item.UltimoComandoRecebido = DateTime.Now; item.Iniciado = true; + + if (estavaOffline) + { + Variaveis.MostrarLog($"[MKS057DCanService] [CONEXAO] Driver CAN 0x{item.EnderecoTx:X2} ONLINE novamente"); + } } catch (Exception ex) { - Console.WriteLine("[MKS] Erro ao interpretar resposta CAN: " + ex.Message); + Variaveis.MostrarLog("[MKS057DCanService] Erro ao interpretar resposta CAN: " + ex.Message); } }