melhoria na saude do atuador, bicos com ou sem movimento

This commit is contained in:
Diego Freitas 2026-07-01 13:26:08 -03:00
parent 64fce863d5
commit c9f3bd69d7
9 changed files with 230 additions and 128 deletions

View File

@ -135,7 +135,7 @@ namespace AgroBase.Forms
* Hoje pode ficar comentado se UDP não for usado no rover,
* mas o método fica aqui no lugar certo do lifecycle.
*/
Variaveis.IniciarUDP();
//Variaveis.IniciarUDP();
APIService.IniciarRotinas();
VersionamentoService.IniciarRotinas();

View File

@ -764,7 +764,7 @@ namespace AgroBase.Models.Modules
posicao = CanMessagePosicaoDados.Dados1,
indice = 2,
analise_health = true,
maximo = 100,
maximo = 120,
minimo = 0,
nominal = 0,
unidade_medida = "mL/s"
@ -1181,6 +1181,7 @@ namespace AgroBase.Models.Modules
Componente = S_Code.sBIC,
Posicao = posicao,
Inicializado = false,
ComMovimento = false,
AnguloNeutro = 90,
AnguloControle = 90,
AnguloAbertura = 30,
@ -2074,6 +2075,7 @@ namespace AgroBase.Models.Modules
public int Posicao { get; set; }
public S_Code Componente { get; set; } = S_Code.sBIC;
public bool AtuacaoNivelAlto { get; set; }
public bool ComMovimento { get; set; }
public double AnguloNeutro { get; set; }
public double AnguloControle { get; set; }
public double AnguloAbertura { get; set; }
@ -2250,8 +2252,12 @@ namespace AgroBase.Models.Modules
AtuacaoNivelAlto = AtuacaoNivelAlto,
Atuacoes = Atuacoes,
ComandoAtuar = ComandoAtuar,
ComMovimento = ComMovimento,
AnguloAbertura = AnguloAbertura,
AnguloControle = AnguloControle,
UltimoAnguloDesejado = UltimoAnguloDesejado,
UltimoComandoEnviado = UltimoComandoEnviado,
UltimoStatusDesejado = UltimoStatusDesejado,
Inicializado = Inicializado,
Mandatorio = Mandatorio,
Componente = Componente,

View File

@ -602,9 +602,9 @@ namespace AgroBase.Models.Modules
posicao = CanMessagePosicaoDados.Dados1,
indice = 1,
analise_health = true,
maximo = 2200,
minimo = 1000,
nominal = 1700,
maximo = 2500,
minimo = 400,
nominal = 700,
unidade_medida = "mA"
},
new SensorValoresLeituraModel()

View File

@ -14,7 +14,6 @@ using System.Drawing;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.ComTypes;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@ -30,15 +29,16 @@ namespace AgroBase.Models
{
get
{
return tmrLogs.Interval;
return _tmrLogsOperacao.Interval;
}
set
{
tmrLogs.SetInterval((int)value);
_tmrLogsOperacao.SetInterval((int)value);
}
}
private static AsyncTaskTimerModel tmrLogs;
private AsyncTaskTimerModel _tmrLogsOperacao;
private int idxLog = 0;
private readonly SemaphoreSlim _salvarLogsGate = new SemaphoreSlim(1, 1);
public double tmrLeiturasInterval
{
get
@ -55,8 +55,13 @@ namespace AgroBase.Models
public OperacaoModel()
{
tmrLogs?.Dispose();
tmrLogs = new AsyncTaskTimerModel("tmrLogs", tmrLogs_Tick, 1000);
Variaveis.MostrarLog("[OperacaoModel.ctor] StackTrace: " + Environment.StackTrace);
Variaveis.MostrarLog(
$"[OperacaoModel.ctor] Nova instância. Hash={GetHashCode()}, " +
$"OperacaoAtual={Variaveis.OperacaoEmAndamento?.ID}, " +
$"HashAtual={Variaveis.OperacaoEmAndamento?.GetHashCode()}"
);
tmrLeituras?.Dispose();
tmrLeituras = new AsyncTaskTimerModel("tmrLeitura", tmrLeituras_Tick, 500);
@ -200,8 +205,6 @@ namespace AgroBase.Models
if (CarregarModulos)
{
tmrLogs?.Stop();
var SenConectado = (DispositivosService<SensoriamentoModel>)Variaveis.DispositivosConectados.FirstOrDefault(x => x.Dispositivo == T_Code.Sen);
var AtuConectado = (DispositivosService<AtuadorModel>)Variaveis.DispositivosConectados.FirstOrDefault(x => x.Dispositivo == T_Code.Atu);
var MvdConectado = (DispositivosService<MovimentacaoUnificadaModel>)Variaveis.DispositivosConectados.FirstOrDefault(x => x.Dispositivo == T_Code.Mvd);
@ -1059,7 +1062,7 @@ namespace AgroBase.Models
op.Sensoriamento.UltimoRegistroLog = agora;
op.idxLog = 0;
tmrLogs?.Start();
op.IniciarTimerLogsOperacao();
await op.RealizarCalibragemInicialAsync(true);
@ -1087,9 +1090,9 @@ namespace AgroBase.Models
op.Sensoriamento.Operacao.DataFim = DateTime.Now;
tmrLogs?.Stop();
op.PararTimerLogsOperacao();
op.SalvarLogs(false);
op.SalvarLogs(true);
op.Sensoriamento.Operacao.Finalizando = false;
op.Trajetoria?.AutonomiaCorredor?.Parar();
@ -1194,6 +1197,33 @@ namespace AgroBase.Models
}
private void IniciarTimerLogsOperacao()
{
_tmrLogsOperacao?.Stop();
_tmrLogsOperacao?.Dispose();
_tmrLogsOperacao = new AsyncTaskTimerModel($"tmrLogs_{ID}", tmrLogs_Tick, 1000);
_tmrLogsOperacao.Start();
Variaveis.MostrarLog($"[OperacaoModel.IniciarTimerLogsOperacao] Timer de logs iniciado. Operacao={ID}, Hash={GetHashCode()}");
}
private void PararTimerLogsOperacao()
{
try
{
_tmrLogsOperacao?.Stop();
Variaveis.MostrarLog($"[OperacaoModel.PararTimerLogsOperacao] Timer de logs parado. Operacao={ID}, Hash={GetHashCode()}, idxLog={idxLog}");
}
catch (Exception ex)
{
Variaveis.MostrarLog("[OperacaoModel.PararTimerLogsOperacao] Erro ao parar timer de logs: " + ex.Message);
}
}
public static string DeserializarDadosOperacao(string caminho, string arquivo)
{
string pastaBase = Path.GetDirectoryName(caminho) ?? "";
@ -2628,7 +2658,24 @@ namespace AgroBase.Models
{
var op = this;
if (op.Sensoriamento.Operacao.OperacaoIniciada)
if (!ReferenceEquals(Variaveis.OperacaoEmAndamento, op))
{
Variaveis.MostrarLog($"[OperacaoModel.tmrLogs_Tick] Timer órfão parado. TimerHash={GetHashCode()}, OperacaoTimer={ID}, OperacaoAtual={Variaveis.OperacaoEmAndamento?.ID}");
PararTimerLogsOperacao();
return;
}
if (op.Sensoriamento?.Operacao?.OperacaoIniciada != true)
return;
if (!await _salvarLogsGate.WaitAsync(0))
{
Variaveis.MostrarLog($"[OperacaoModel.tmrLogs_Tick] Tick ignorado porque SalvarLogs ainda está em execução. Operacao={ID}, idxLog={idxLog}");
return;
}
try
{
op.SalvarLogs(false);
@ -2639,9 +2686,13 @@ namespace AgroBase.Models
if (op.idxLog % 5 == 0 && !op.Sensoriamento.Operacao.Calibrando)
{
Task.Run(async () => await op.RealizarCalibragemInicialAsync(false));
_ = Task.Run(async () => await op.RealizarCalibragemInicialAsync(false));
}
}
finally
{
_salvarLogsGate.Release();
}
}
public void SalvarLogs(bool Fim)
@ -3594,6 +3645,7 @@ namespace AgroBase.Models
Atuacoes = bico.Atuacoes,
TempoAtuado = bico.TempoAtuado,
AnguloAbertura = bico.AnguloAbertura,
ComMovimento = bico.ComMovimento,
AnguloControle = bico.AnguloControle
}).ToList()
};
@ -4649,7 +4701,7 @@ namespace AgroBase.Models
DadosPerformance = dadosPerformance,
LivoxLidar = dadosLivox,
CoolerControl = dadosCooler,
Logs = Logs != null ? Logs.Where(x => x?.Momento >= ultimoRegistroLog).ToList() : new List<OperacaoSensoriamentoLogErrosModel>(),
Logs = Logs != null ? Logs.Where(x => x != null && x.Momento > ultimoRegistroLog).ToList() : new List<OperacaoSensoriamentoLogErrosModel>(),
UltimoRegistroLog = ultimoRegistroLog,
};
@ -5108,6 +5160,7 @@ namespace AgroBase.Models
public int Atuacoes { get; set; }
public double TempoAtuado { get; set; }
public double AnguloAbertura { get; set; }
public bool ComMovimento { get; set; }
public double AnguloControle { get; set; }
public AtuadorBicoLogModel Clone()
@ -5124,6 +5177,7 @@ namespace AgroBase.Models
Atuacoes = Atuacoes,
TempoAtuado = TempoAtuado,
AnguloAbertura = AnguloAbertura,
ComMovimento = ComMovimento,
AnguloControle = AnguloControle
};
}

View File

@ -610,8 +610,7 @@ namespace AgroBase.Services
return;
}
var agitador =
op?.DispAtu?.Dados?.AgitadorDeCalda;
var agitador = op?.DispAtu?.Dados?.AgitadorDeCalda;
if (!(agitador?.Inicializado ?? false))
return;
@ -634,32 +633,20 @@ namespace AgroBase.Services
!operacaoConcluida &&
PulverizadorLiberado;
bool bicosAtuando =
op.DispAtu?.Dados?.BicosPulverizadores?
.Any(x =>
x.Comandar &&
x.ComandoAtuar)
?? false;
bool bicosAtuando = op.DispAtu?.Dados?.BicosPulverizadores?.Any(x => x.Comandar && x.ComandoAtuar) ?? false;
double potenciaFinal = 0;
DateTime agora = DateTime.Now;
if (
operacaoAtiva &&
!_agitadorOperacaoAtivaAnterior
)
if (operacaoAtiva && !_agitadorOperacaoAtivaAnterior)
{
_agitadorBoostAte =
agora.AddMilliseconds(
AtuAgitadorBoostRetomadaMs
);
_agitadorBoostAte = agora.AddMilliseconds(AtuAgitadorBoostRetomadaMs);
_agitadorUltimaTroca = agora;
_agitadorIntermitenteLigado = true;
}
bool emBoost =
agora < _agitadorBoostAte;
bool emBoost = agora < _agitadorBoostAte;
switch (modo)
{
@ -672,18 +659,15 @@ namespace AgroBase.Services
{
if (emBoost)
{
potenciaFinal =
AtuAgitadorPotenciaBoost;
potenciaFinal = AtuAgitadorPotenciaBoost;
}
else if (bicosAtuando)
{
potenciaFinal =
AtuAgitadorPotenciaContinua;
potenciaFinal = AtuAgitadorPotenciaContinua;
}
else
{
potenciaFinal =
AtuAgitadorPotenciaManutencao;
potenciaFinal = AtuAgitadorPotenciaManutencao;
}
}
break;
@ -697,44 +681,27 @@ namespace AgroBase.Services
}
else if (emBoost)
{
potenciaFinal =
AtuAgitadorPotenciaBoost;
potenciaFinal = AtuAgitadorPotenciaBoost;
}
else
{
int tempoAtualMs =
_agitadorIntermitenteLigado
? AtuAgitadorTempoLigadoMs
: AtuAgitadorTempoDesligadoMs;
int tempoAtualMs = _agitadorIntermitenteLigado ? AtuAgitadorTempoLigadoMs : AtuAgitadorTempoDesligadoMs;
if (
(agora - _agitadorUltimaTroca)
.TotalMilliseconds >=
tempoAtualMs
)
if ((agora - _agitadorUltimaTroca).TotalMilliseconds >= tempoAtualMs)
{
_agitadorIntermitenteLigado =
!_agitadorIntermitenteLigado;
_agitadorIntermitenteLigado = !_agitadorIntermitenteLigado;
_agitadorUltimaTroca = agora;
}
potenciaFinal =
_agitadorIntermitenteLigado
? AtuAgitadorPotenciaIntermitente
: 0;
potenciaFinal = _agitadorIntermitenteLigado ? AtuAgitadorPotenciaIntermitente : 0;
}
break;
}
potenciaFinal =
Math.Min(
potenciaFinal,
PotenciaLimitePorNivel(PercentualAtual)
);
potenciaFinal = Math.Min(potenciaFinal, PotenciaLimitePorNivel(PercentualAtual));
potenciaFinal =
Clamp(potenciaFinal, 0, 100);
potenciaFinal = Clamp(potenciaFinal, 0, 100);
/*
* Este método é a fonte de verdade do agitador.
@ -742,12 +709,9 @@ namespace AgroBase.Services
agitador.PotenciaSP = potenciaFinal;
agitador.PressaoSP = -1;
agitador.ComandoAtuar =
agitador.Comandar &&
potenciaFinal > 0;
agitador.ComandoAtuar = agitador.Comandar && potenciaFinal > 0;
_agitadorOperacaoAtivaAnterior =
operacaoAtiva;
_agitadorOperacaoAtivaAnterior = operacaoAtiva;
}
}
}

View File

@ -969,6 +969,7 @@ namespace AgroBase.Services.Operadores
estado_leitura = bico._EstadoLeitura,
angulo_leitura = bico._AnguloBicoLeitura,
com_movimento = bico.ComMovimento,
angulo_neutro = bico.AnguloNeutro,
angulo_controle = bico.AnguloControle,
angulo_abertura = bico.AnguloAbertura,

View File

@ -57,54 +57,121 @@ public sealed class UdpReliableChannel : IDisposable
}
}
public void Start(int localPort)
public bool Start(int localPort)
{
if (_udp != null) return;
lock (_sockLock)
{
if (_udp != null || _running || _stopping)
return true;
_seqTx = 0; // opcional
_lastSeqRx = 0; // importante
_running = true;
try
{
_seqTx = 0;
_lastSeqRx = 0;
_running = true;
_stopping = false;
_udp = new UdpClient(localPort);
_udp.Client.ReceiveBufferSize = 1 << 20;
_udp.Client.SendBufferSize = 1 << 20;
_udp = new UdpClient(localPort);
_udp.Client.ReceiveBufferSize = 1 << 20;
_udp.Client.SendBufferSize = 1 << 20;
_rxThread = new Thread(RxLoop) { IsBackground = true, Name = "UdpReliableChannel.Rx" };
_rxThread.Start();
_rxThread = new Thread(RxLoop)
{
IsBackground = true,
Name = "UdpReliableChannel.Rx"
};
if (EnableHeartbeat)
StartHeartbeat();
_rxThread.Start();
if (EnableHeartbeat)
StartHeartbeat();
return true;
}
catch (SocketException ex)
{
_running = false;
_stopping = false;
try { _udp?.Close(); } catch { }
try { _udp?.Dispose(); } catch { }
_udp = null;
_rxThread = null;
// 10048 = address already in use
if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse)
{
// Não relança. Quem chamou decide tentar depois.
return false;
}
return false;
}
catch
{
_running = false;
_stopping = false;
try { _udp?.Close(); } catch { }
try { _udp?.Dispose(); } catch { }
_udp = null;
_rxThread = null;
return false;
}
}
}
public void Stop()
{
UdpClient udpParaFechar = null;
Thread threadParaAguardar = null;
lock (_sockLock)
{
if (_stopping)
return;
_stopping = true;
_running = false;
udpParaFechar = _udp;
threadParaAguardar = _rxThread;
}
// pare heartbeat antes de matar socket
StopHeartbeat();
try { _udp?.Close(); } catch { }
try { _udp?.Dispose(); } catch { }
try { udpParaFechar?.Close(); } catch { }
try { udpParaFechar?.Dispose(); } catch { }
try
{
if (_rxThread != null && _rxThread.IsAlive)
_rxThread.Join(2000); // ou sem timeout se preferir travar até fechar
if (
threadParaAguardar != null &&
threadParaAguardar.IsAlive &&
Thread.CurrentThread.ManagedThreadId != threadParaAguardar.ManagedThreadId
)
{
threadParaAguardar.Join(1000);
}
}
catch { }
lock (_sockLock)
{
_udp = null;
_rxThread = null;
if (ReferenceEquals(_udp, udpParaFechar))
_udp = null;
if (ReferenceEquals(_rxThread, threadParaAguardar))
_rxThread = null;
_lastSeqRx = 0;
foreach (var kv in _pendingAck)
kv.Value.TrySetResult(false);
_pendingAck.Clear();
_stopping = false;

View File

@ -554,27 +554,38 @@ class ModuloAtuador(ModuloDiagnosticoBase):
def _avaliar_comando_bico(self, item, ctx, motivos, condicoes):
dados = item["dados"]
label = dados.get("label")
comando_presente = (
"comando_atuar" in dados or
"estado_desejado" in dados or
"status_desejado_fisico" in dados or
"ultimo_status_desejado" in dados
)
comando = self._estado_desejado_ligado(dados)
estado = self._get(dados, "estado_leitura", self._campo_atual(dados, "EstadoLeitura", 0))
estado_on = self._estado_ligado(estado)
angulo = self._float(
self._get(dados, "angulo_leitura", self._campo_atual(dados, "ServoAnguloLeitura", 0)),
0,
)
usa_servo = dados.get("com_movimento", False)
angulo_neutro = self._float(dados.get("angulo_neutro", 90), 90)
angulo_controle = self._float(dados.get("angulo_controle", dados.get("angulo_sp", 90)), 90)
angulo_desejado = self._float(
dados.get("angulo_desejado", angulo_controle if comando else angulo_neutro),
angulo_controle if comando else angulo_neutro,
)
angulo = None
angulo_desejado = None
angulo_neutro = None
angulo_controle = None
if usa_servo:
angulo = self._float(
self._get(dados, "angulo_leitura", self._campo_atual(dados, "ServoAnguloLeitura", 0)),
0,
)
angulo_neutro = self._float(dados.get("angulo_neutro", 90), 90)
angulo_controle = self._float(dados.get("angulo_controle", dados.get("angulo_sp", 90)), 90)
angulo_desejado = self._float(
dados.get("angulo_desejado", angulo_controle if comando else angulo_neutro),
angulo_controle if comando else angulo_neutro,
)
ultimo_comando_ms = self._float(dados.get("ultimo_comando_ms", 999999), 999999)
tem_idade_comando = ultimo_comando_ms < 900000
@ -585,13 +596,16 @@ class ModuloAtuador(ModuloDiagnosticoBase):
return 92, {
"comando_disponivel": False,
"estado_on": estado_on,
"usa_servo": usa_servo,
"angulo": angulo,
"angulo_desejado": angulo_desejado,
}
if tem_idade_comando and ultimo_comando_ms < self.COMANDO_GRACE_MS:
return 96, {
"comando_atuar": comando,
"estado_on": estado_on,
"usa_servo": usa_servo,
"angulo": angulo,
"angulo_desejado": angulo_desejado,
"ultimo_comando_ms": ultimo_comando_ms,
@ -634,32 +648,36 @@ class ModuloAtuador(ModuloDiagnosticoBase):
score = min(score, 25 if severidade > 90 else 65)
erro_ang = abs(angulo - angulo_desejado)
if usa_servo and angulo is not None and angulo_desejado is not None:
erro_ang = abs(angulo - angulo_desejado)
if erro_ang > self.ANGULO_TOL_BICO_GRAUS and (comando or estado_on):
motivos.append(f"Ângulo do bico distante do desejado ({angulo:.1f}°/{angulo_desejado:.1f}°)")
condicoes.append({
"label": "Ângulo bico",
"valor": angulo,
"severidade": min(75, max(30, int(erro_ang * 3))),
"classe": "processo",
"descricao": f"Ângulo do bico {label} distante do desejado",
"acoes": [
"Verificar servo/posição mecânica do bico.",
"Conferir neutro e ângulo de abertura configurados.",
],
})
score = min(score, 78)
if erro_ang > self.ANGULO_TOL_BICO_GRAUS and (comando or estado_on):
motivos.append(f"Ângulo do bico distante do desejado ({angulo:.1f}°/{angulo_desejado:.1f}°)")
condicoes.append({
"label": "Ângulo bico",
"valor": angulo,
"severidade": min(75, max(30, int(erro_ang * 3))),
"classe": "processo",
"descricao": f"Ângulo do bico {label} distante do desejado",
"acoes": [
"Verificar servo/posição mecânica do bico.",
"Conferir neutro e ângulo de abertura configurados.",
],
})
score = min(score, 78)
return int(max(0, min(100, round(score)))), {
"comando_disponivel": True,
"comando_atuar": comando,
"estado_on": estado_on,
"usa_servo": usa_servo,
"angulo": angulo,
"angulo_desejado": angulo_desejado,
"angulo_neutro": angulo_neutro,
"angulo_controle": angulo_controle,
"ultimo_comando_ms": ultimo_comando_ms,
}
# ============================================================
# Bombas
# ============================================================

View File

@ -350,23 +350,15 @@ namespace OperationControl.Models
HeartbeatPayload = new byte[] { 0 }
};
channel.Start(
VariaveisPortas.Ethernet_UDP_TX
);
channel.Start(VariaveisPortas.Ethernet_UDP_TX);
UdpChannel = channel;
ControlSenderDir = new ManualControlSender(
UdpChannel,
(byte)AgroBase.Models.Enums.T_Code.Dir
);
ControlSenderDir = new ManualControlSender(UdpChannel, (byte)AgroBase.Models.Enums.T_Code.Dir);
ControlSenderDir.Start();
ControlSenderMov = new ManualControlSender(
UdpChannel,
(byte)AgroBase.Models.Enums.T_Code.Mov
);
ControlSenderMov = new ManualControlSender(UdpChannel, (byte)AgroBase.Models.Enums.T_Code.Mov);
ControlSenderMov.Start();