ajustes para nao deixar a saude do atuador falhar por incoerencia

This commit is contained in:
Diego Freitas 2026-07-02 14:01:53 -03:00
parent 912773fea2
commit b953ff524e
10 changed files with 452 additions and 192 deletions

View File

@ -150,21 +150,36 @@ namespace AgroBase.Forms.Atuador
private void nudPressaoSP_ValueChanged(object sender, EventArgs e)
{
var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora;
Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuPressaoLinha = Convert.ToInt32(((NumericUpDown)sender).Value);
GeneralJoystick.EnviarComandoAtuador(S_Code.sBMB, Bomba.ID, Status: Bomba.ComandoAtuar, Comando: Services.CanMessagePosicaoDados.Command1);
var op = Variaveis.OperacaoEmAndamento;
var Bomba = op.DispAtu.Dados.BombaPressurizadora;
op.Parametros.Controle.AtuPressaoLinha = Convert.ToInt32(((NumericUpDown)sender).Value);
op.DispAtu?.Dados?.EnviarComandoFisicoBomba(
Bomba,
Services.CanMessagePosicaoDados.Command1,
Bomba.ComandoAtuar
);
}
private void btnLigarBomba_Click(object sender, EventArgs e)
{
var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora;
GeneralJoystick.EnviarComandoAtuador(S_Code.sBMB, Bomba.ID, Status: true, Comando: Services.CanMessagePosicaoDados.Command1, Forcar: true);
var op = Variaveis.OperacaoEmAndamento;
var Bomba = op.DispAtu.Dados.BombaPressurizadora;
op.DispAtu?.Dados?.EnviarComandoFisicoBomba(
Bomba,
Services.CanMessagePosicaoDados.Command1,
true
);
}
private void btnDesligarBomba_Click(object sender, EventArgs e)
{
var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora;
GeneralJoystick.EnviarComandoAtuador(S_Code.sBMB, Bomba.ID, Status: false, Comando: Services.CanMessagePosicaoDados.Command1, Forcar: true);
var op = Variaveis.OperacaoEmAndamento;
var Bomba = op.DispAtu.Dados.BombaPressurizadora;
op.DispAtu?.Dados?.EnviarComandoFisicoBomba(
Bomba,
Services.CanMessagePosicaoDados.Command1,
false
);
}
private void pnlBico_Paint(object sender, PaintEventArgs e)
@ -188,7 +203,13 @@ namespace AgroBase.Forms.Atuador
{
Button btn = (Button)sender;
string ID = btn.Name.Replace("btn0_", "");
GeneralJoystick.EnviarComandoAtuador(S_Code.sBIC, ID, Status: false, Forcar: true);
var op = Variaveis.OperacaoEmAndamento;
var Bico = op?.DispAtu?.Dados?.BicosPulverizadores?.FirstOrDefault(x => x.ID == ID);
Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.EnviarComandoFisicoBico(
Bico,
Services.CanMessagePosicaoDados.Command1,
false
);
pnlBicos.Controls.OfType<Panel>().First(x => x.Name == "pnlB_" + ID).Invalidate();
pnlBicos.Controls.OfType<Panel>().First(x => x.Name == "pnlB_" + ID).Controls.OfType<Label>().First(x => x.Name == "lblCmd_" + ID).Text = "Comando: Desligado";
}
@ -197,7 +218,13 @@ namespace AgroBase.Forms.Atuador
{
Button btn = (Button)sender;
string ID = btn.Name.Replace("btn1_", "");
GeneralJoystick.EnviarComandoAtuador(S_Code.sBIC, ID, Status: true, Forcar: true);
var op = Variaveis.OperacaoEmAndamento;
var Bico = op?.DispAtu?.Dados?.BicosPulverizadores?.FirstOrDefault(x => x.ID == ID);
Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.EnviarComandoFisicoBico(
Bico,
Services.CanMessagePosicaoDados.Command1,
true
);
pnlBicos.Controls.OfType<Panel>().First(x => x.Name == "pnlB_" + ID).Invalidate();
pnlBicos.Controls.OfType<Panel>().First(x => x.Name == "pnlB_" + ID).Controls.OfType<Label>().First(x => x.Name == "lblCmd_" + ID).Text = "Comando: Ligado";
}

View File

@ -525,9 +525,15 @@ namespace AgroBase.Forms.IHM
private async Task EnviarComandoBomba(bool Ligar, double Pressao, int Timeout, AtuadorModel disp = null)
{
var Bomba = Variaveis.OperacaoEmAndamento.DispAtu.Dados.BombaPressurizadora;
Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuPressaoLinha = Pressao + 10;
GeneralJoystick.EnviarComandoAtuador(S_Code.sBMB, Bomba.ID, Status: Ligar, Comando: CanMessagePosicaoDados.Command1);
var op = Variaveis.OperacaoEmAndamento;
var Bomba = op.DispAtu.Dados.BombaPressurizadora;
op.DispAtu?.Dados?.EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command1,
Ligar,
potencia: 100,
pressao: Pressao + 10
);
DateTime inicio = DateTime.Now;
while (DateTime.Now < inicio.AddMilliseconds(Timeout))
@ -544,7 +550,11 @@ namespace AgroBase.Forms.IHM
private async Task EnviarComandoBico(AtuadorBicoModel Bico, bool Ligar, int Timeout, double ValorComparar = 0, AtuadorModel disp = null)
{
GeneralJoystick.EnviarComandoAtuador(S_Code.sBIC, Bico.ID, Status: Ligar);
Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.EnviarComandoFisicoBico(
Bico,
CanMessagePosicaoDados.Command1,
Ligar
);
DateTime inicio = DateTime.Now;
while (DateTime.Now < inicio.AddMilliseconds(Timeout))
{

View File

@ -672,7 +672,11 @@ namespace AgroBase.Forms.Operacoes
if (Bico != null)
{
bool comando = !Bico.ComandoAtuar;
GeneralJoystick.EnviarComandoAtuador(Bico.Componente, Bico.ID, Status: comando, Forcar: true);
op.DispAtu?.Dados?.EnviarComandoFisicoBico(
Bico,
CanMessagePosicaoDados.Command1,
comando
);
pnlAtuadores.Controls.OfType<Label>().First(x => x.Name == "lblCmd_" + ID).Text = "Comando: " + (comando ? "Ligado" : "Desligado");
btn.Text = comando ? "Desligar" : "Ligar";
@ -692,8 +696,25 @@ namespace AgroBase.Forms.Operacoes
double Kp = Convert.ToDouble(txtKp.Text);
double Ki = Convert.ToDouble(txtKi.Text);
double Kd = Convert.ToDouble(txtKd.Text);
GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: true, Valores: new System.Collections.Generic.List<double?>() { cmbModoBomba.SelectedIndex, potMin, Kp, Ki, Kd }, Comando: CanMessagePosicaoDados.Command2, Forcar: true);
GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: true, Valores: new System.Collections.Generic.List<double?>() { potMax, pressao }, Comando: CanMessagePosicaoDados.Command1, Forcar: true);
op.DispAtu?.Dados?.EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command2,
true,
modo: (ModoControleBomba)cmbModoBomba.SelectedIndex,
potenciaMinPid: potMin,
Kp: Kp,
Ki: Ki,
Kd: Kd
);
op.DispAtu?.Dados?.EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command1,
true,
potencia: potMax,
pressao: pressao
);
}
}
@ -701,7 +722,11 @@ namespace AgroBase.Forms.Operacoes
{
var Bomba = op.DispAtu.Dados.BombaPressurizadora;
if (Bomba != null)
GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: false, Comando: CanMessagePosicaoDados.Command1, Forcar: true);
op.DispAtu?.Dados?.EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command1,
false
);
}
private void btnLigarAgitador_Click(object sender, EventArgs e)
@ -710,9 +735,25 @@ namespace AgroBase.Forms.Operacoes
if (Agitador != null)
{
op.Parametros.Controle.AtuAgitadorModo = (ModoAgitadorCalda)cmbModoAgitador.SelectedIndex;
double potencia = Convert.ToInt32(nudPotenciaAgitador.Value);
GeneralJoystick.EnviarComandoAtuador(Agitador.Componente, Agitador.ID, Status: true, Valores: new System.Collections.Generic.List<double?>() { potencia }, Comando: CanMessagePosicaoDados.Command1, Forcar: true);
op.DispAtu?.Dados?.EnviarComandoFisicoBomba(
Agitador,
CanMessagePosicaoDados.Command2,
true,
modo: Agitador.Controle.Modo,
potenciaMinPid: Agitador.Controle.PotenciaMinPID,
Kp: Agitador.Controle.Kp,
Ki: Agitador.Controle.Ki,
Kd: Agitador.Controle.Kd
);
op.DispAtu?.Dados?.EnviarComandoFisicoBomba(
Agitador,
CanMessagePosicaoDados.Command1,
true,
potencia: potencia
);
}
}
@ -720,7 +761,11 @@ namespace AgroBase.Forms.Operacoes
{
var Agitador = op.DispAtu.Dados.AgitadorDeCalda;
if (Agitador != null)
GeneralJoystick.EnviarComandoAtuador(Agitador.Componente, Agitador.ID, Status: false, Comando: CanMessagePosicaoDados.Command1, Forcar: true);
op.DispAtu?.Dados?.EnviarComandoFisicoBomba(
Agitador,
CanMessagePosicaoDados.Command1,
false
);
}
private void nudPressaoSP_ValueChanged(object sender, EventArgs e)

View File

@ -279,7 +279,7 @@ namespace AgroBase.Forms.Sensoriamento
if (MessageBox.Show($"Tem certeza que deseja reiniciar o módulo {Dispositivo}?", "Confirmação", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
{
if (Dispositivo == T_Code.Atu)
GeneralJoystick.EnviarComandoAtuador(S_Code.sMOD, "", true);
(Disp as AtuadorModel).EnviarComandoFisicoReset();
if (Dispositivo == T_Code.Sen)
GeneralJoystick.EnviarComandoSensoriamento(S_Code.sMOD, "", true);
}

View File

@ -625,75 +625,24 @@ namespace AgroBase
var bomba = op.DispAtu?.Dados?.BombasPressurizadoras?.FirstOrDefault(x => x.ID == Comando.IDcontrolar);
S_Code comp = bico?.Componente ?? bomba?.Componente ?? S_Code.sVZO;
for (int i = 0; i < 1; i++)
switch (comp)
{
EnviarComandoAtuador(comp, Comando.IDcontrolar, Status: Comando.EstadoAplicar, Comando: Comando.Comando);
}
}
public static void EnviarComandoAtuador(S_Code Componente, string ID, bool? Status = null, List<double?> Valores = null, CanMessagePosicaoDados Comando = CanMessagePosicaoDados.Command1, bool Forcar = false)
{
var op = Variaveis.OperacaoEmAndamento;
var DispAtu = op.DispAtu;
if (!DispAtu.Dados.Conectado)
return;
if (!Forcar)
{
if (!op.Sensoriamento.Operacao.OperacaoIniciada) return;
bool componente_em_uso = op.Parametros.ModulosMandatorios.Any(x => x.Dispositivo == T_Code.Atu && (x.ComponentesEmUso?.ContainsKey(ID) ?? false) && x.ComponentesEmUso[ID].Item2);
if (!componente_em_uso && (Status ?? false)) return; // Impede execucao de comandos que liguem componentes quando estiver marcado para nao utilizar
}
int id_num = -1;
CanMessagePosicaoDados comando = Comando;
byte[] data = null;
switch (Componente)
{
case S_Code.sMOD:
if (DispAtu?.Dados != null && Status == true)
{
(id_num, comando, data) = DispAtu.Dados.ProtocoloComandoReset();
}
break;
case S_Code.sBMB:
var Bomba = DispAtu?.Dados?.BombasPressurizadoras?.FirstOrDefault(x => x.ID == ID);
if (Bomba != null)
{
switch (comando)
{
case CanMessagePosicaoDados.Command1:
if (Status != null) Bomba.ComandoAtuar = (bool)Status;
if (Valores?.Count() > 0 && Valores[0] is double) Bomba.PotenciaSP = (double)Valores[0];
if (Valores?.Count() > 1 && Valores[1] is double) Bomba.PressaoSP = (double)Valores[1];
(id_num, comando, data) = Bomba.ProtocoloComando(comando);
break;
case CanMessagePosicaoDados.Command2:
if (Valores?.Count() > 0 && Valores[0] is double) Bomba.Controle.Modo = (ModoControleBomba)((int)Valores[0]);
if (Valores?.Count() > 1 && Valores[1] is double) Bomba.Controle.PotenciaMinPID = (double)Valores[1];
if (Valores?.Count() > 2 && Valores[2] is double) Bomba.Controle.Kp = (double)Valores[2];
if (Valores?.Count() > 3 && Valores[3] is double) Bomba.Controle.Ki = (double)Valores[3];
if (Valores?.Count() > 4 && Valores[4] is double) Bomba.Controle.Kd = (double)Valores[4];
(id_num, comando, data) = Bomba.ProtocoloComando(comando);
break;
}
}
break;
case S_Code.sBIC:
var Bico = DispAtu.Dados.BicosPulverizadores.FirstOrDefault(x => x.ID == ID);
if (Bico != null)
{
if (Status != null) Bico.ComandoAtuar = (bool)Status;
if (Valores?.Count() > 0) Bico.AnguloControle = (int)Valores[0];
(id_num, comando, data) = Bico.ProtocoloComando();
}
break;
op?.DispAtu?.Dados?.EnviarComandoFisicoBico(
bico,
Comando.Comando,
Comando.EstadoAplicar
);
return;
case S_Code.sBMB:
op?.DispAtu?.Dados?.EnviarComandoFisicoBomba(
bomba,
Comando.Comando,
Comando.EstadoAplicar
);
return;
}
DispAtu.AdicionarMensagemFila(id_num, comando, comando, data, false);
}
public static void EnviarComandoSensoriamento(S_Code Componente, string ID, dynamic Status) //, bool Forcar = false

View File

@ -1331,6 +1331,17 @@ namespace AgroBase.Models.Modules
bomba.PotenciaSP = 100;
bomba.PressaoSP = controle.AtuPressaoLinha;
bomba.Controle.Modo = controle.AtuModoControle;
EnviarComandoFisicoBomba(
bomba,
CanMessagePosicaoDados.Command2,
true,
modo: bomba.Controle.Modo,
potenciaMinPid: bomba.Controle.PotenciaMinPID,
Kp: bomba.Controle.Kp,
Ki: bomba.Controle.Ki,
Kd: bomba.Controle.Kd
);
}
var agitador = dadosAtuador.AgitadorDeCalda;
@ -1346,6 +1357,17 @@ namespace AgroBase.Models.Modules
*/
agitador.PotenciaSP = 0;
agitador.PressaoSP = -1;
EnviarComandoFisicoBomba(
agitador,
CanMessagePosicaoDados.Command2,
true,
modo: agitador.Controle.Modo,
potenciaMinPid: agitador.Controle.PotenciaMinPID,
Kp: agitador.Controle.Kp,
Ki: agitador.Controle.Ki,
Kd: agitador.Controle.Kd
);
}
}
@ -1408,9 +1430,10 @@ namespace AgroBase.Models.Modules
{
EnviarComandoFisicoBomba(
bomba,
CanMessagePosicaoDados.Command1,
statusDesejado,
potenciaEfetiva,
pressaoEfetiva
potencia: potenciaEfetiva,
pressao: pressaoEfetiva
);
}
}
@ -1714,8 +1737,9 @@ namespace AgroBase.Models.Modules
{
EnviarComandoFisicoBico(
bico,
CanMessagePosicaoDados.Command1,
statusDesejado,
anguloDesejado
angulo: anguloDesejado
);
}
}
@ -1806,65 +1830,84 @@ namespace AgroBase.Models.Modules
{
EnviarComandoFisicoBomba(
bomba,
CanMessagePosicaoDados.Command1,
statusDesejado,
potenciaEfetiva,
pressaoEfetiva
potencia: potenciaEfetiva,
pressao: pressaoEfetiva
);
}
}
}
private void EnviarComandoFisicoBico(AtuadorBicoModel bico, bool status, double angulo)
public void EnviarComandoFisicoReset()
{
var dispAtu = Variaveis.OperacaoEmAndamento?.DispAtu;
if (dispAtu?.Dados?.Conectado != true)
return;
(int id_num, CanMessagePosicaoDados dado, byte[] data) = dispAtu.Dados.ProtocoloComandoReset();
dispAtu.AdicionarMensagemFila(id_num, dado, dado, data, false);
}
public void EnviarComandoFisicoBico(
AtuadorBicoModel bico,
CanMessagePosicaoDados dado,
bool status,
double? angulo = null
)
{
var dispAtu = Variaveis.OperacaoEmAndamento?.DispAtu;
if (dispAtu?.Dados?.Conectado != true || bico == null)
return;
byte anguloByte = (byte)Math.Round(FuncoesMatematicas.Clamp(angulo, 0, 255));
bico.ComandoAtuar = status;
if (angulo.HasValue) bico.AnguloControle = angulo.Value;
byte[] data =
{
(byte)(status ? Estado.Ligado : Estado.Desligado),
anguloByte
};
(_, _, byte[] data) = bico.ProtocoloComando();
dispAtu.AdicionarMensagemFila(bico.ID_Num, CanMessagePosicaoDados.Command1, CanMessagePosicaoDados.Command1, data, false);
dispAtu.AdicionarMensagemFila(bico.ID_Num, dado, dado, data, false);
bico.UltimoComandoEnviado = DateTime.Now;
bico.UltimoStatusDesejado = status;
bico.UltimoAnguloDesejado = angulo;
if (angulo.HasValue) bico.UltimoAnguloDesejado = angulo.Value;
}
private void EnviarComandoFisicoBomba(AtuadorBombaModel bomba, bool status, double potencia, double pressao)
public void EnviarComandoFisicoBomba(
AtuadorBombaModel bomba,
CanMessagePosicaoDados dado,
bool status,
double? potencia = null,
double? pressao = null,
ModoControleBomba? modo = null,
double? potenciaMinPid = null,
double? Kp = null,
double? Ki = null,
double? Kd = null
)
{
var dispAtu = Variaveis.OperacaoEmAndamento?.DispAtu;
if (dispAtu?.Dados?.Conectado != true || bomba == null)
return;
byte potenciaByte = (byte)Math.Round(FuncoesMatematicas.Clamp(potencia, 0, 100));
bomba.ComandoAtuar = status;
if (potencia.HasValue) bomba.PotenciaSP = potencia.Value;
if (pressao.HasValue) bomba.PressaoSP = pressao.Value;
if (modo.HasValue) bomba.Controle.Modo = modo.Value;
if (potenciaMinPid.HasValue) bomba.Controle.PotenciaMinPID = potenciaMinPid.Value;
if (Kp.HasValue) bomba.Controle.Kp = Kp.Value;
if (Ki.HasValue) bomba.Controle.Ki = Ki.Value;
if (Kd.HasValue) bomba.Controle.Kd = Kd.Value;
/*
* -1 é usado para indicar ausência de sensor
* de pressão. No protocolo atual, representa 255.
*/
byte pressaoByte = pressao < 0 ? byte.MaxValue : (byte)Math.Round(FuncoesMatematicas.Clamp(pressao, 0, 255));
(_, _, byte[] data) = bomba.ProtocoloComando(dado);
byte[] data =
{
(byte)(status ? Estado.Ligado : Estado.Desligado),
potenciaByte,
pressaoByte
};
dispAtu.AdicionarMensagemFila(bomba.ID_Num, CanMessagePosicaoDados.Command1, CanMessagePosicaoDados.Command1, data, false);
Variaveis.MostrarLog($"Enviando comando para a bomba {bomba.ID}: potencia={potencia}, pressao={pressao}");
dispAtu.AdicionarMensagemFila(bomba.ID_Num, dado, dado, data, false);
bomba.UltimoComandoEnviado = DateTime.Now;
bomba.UltimoStatusDesejado = status;
bomba.UltimaPotenciaDesejada = potencia;
bomba.UltimaPressaoDesejada = pressao;
if (potencia.HasValue) bomba.UltimaPotenciaDesejada = potencia.Value;
if (pressao.HasValue) bomba.UltimaPressaoDesejada = pressao.Value;
}
@ -1917,19 +1960,37 @@ namespace AgroBase.Models.Modules
continue;
}
Bomba.PotenciaSP = 100;
if (Bomba.ID == BombaPressurizadora.ID)
{
Bomba.Controle.Modo = op.Parametros.Controle.AtuModoControle;
Bomba.PressaoSP = op.Parametros.Controle.AtuPressaoLinha;
GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: true, Valores: new List<double?>() { (int)Bomba.Controle.Modo, Bomba.Controle.PotenciaMinPID, Bomba.Controle.Kp, Bomba.Controle.Ki, Bomba.Controle.Kd }, Comando: CanMessagePosicaoDados.Command2, Forcar: true);
GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: true, Valores: new List<double?>() { Bomba.PotenciaSP, Bomba.PressaoSP }, Comando: CanMessagePosicaoDados.Command1, Forcar: true);
EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command2,
true,
modo: op.Parametros.Controle.AtuModoControle,
potenciaMinPid: Bomba.Controle.PotenciaMinPID,
Kp: Bomba.Controle.Kp,
Ki: Bomba.Controle.Ki,
Kd: Bomba.Controle.Kd
);
EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command1,
true,
potencia: 100,
pressao: op.Parametros.Controle.AtuPressaoLinha
);
}
else
{
Bomba.PressaoSP = -1;
bool ligar = op.Parametros.Controle.AtuAgitadorModo != ModoAgitadorCalda.SemAgitacao;
GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: ligar, Valores: new List<double?>() { Bomba.PotenciaSP, Bomba.PressaoSP }, Comando: CanMessagePosicaoDados.Command1, Forcar: true);
EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command1,
ligar,
potencia: 100,
pressao: -1
);
}
if (Bomba.Sensor_ID_Num == -1)
@ -1951,14 +2012,22 @@ namespace AgroBase.Models.Modules
{
foreach (var Bico in BicosPulverizadores.Where(x => x.Comandar && x.Inicializado))
{
GeneralJoystick.EnviarComandoAtuador(Bico.Componente, Bico.ID, Status: true, Forcar: true);
EnviarComandoFisicoBico(
Bico,
CanMessagePosicaoDados.Command1,
true
);
}
await Task.Delay(3000);
foreach (var Bico in BicosPulverizadores.Where(x => x.Comandar && x.Inicializado))
{
GeneralJoystick.EnviarComandoAtuador(Bico.Componente, Bico.ID, Status: false, Forcar: true);
EnviarComandoFisicoBico(
Bico,
CanMessagePosicaoDados.Command1,
false
);
}
await FuncoesGlobais.AguardarCondicaoAsync(Bomba.AtingiuPressao, 4000, 200);
@ -1978,11 +2047,19 @@ namespace AgroBase.Models.Modules
if (Bico.Inicializado)
{
GeneralJoystick.EnviarComandoAtuador(Bico.Componente, Bico.ID, Status: true, Forcar: true);
EnviarComandoFisicoBico(
Bico,
CanMessagePosicaoDados.Command1,
true
);
await Task.Delay(200);
GeneralJoystick.EnviarComandoAtuador(Bico.Componente, Bico.ID, Status: false, Forcar: true);
EnviarComandoFisicoBico(
Bico,
CanMessagePosicaoDados.Command1,
false
);
await Task.Delay(200);

View File

@ -1152,6 +1152,12 @@ namespace AgroBase.Models
if (op.Sensoriamento.Operacao.Finalizando)
return;
if (op.Sensoriamento?.Operacao?.OperacaoIniciada != true)
{
Variaveis.MostrarLog("[OperacaoModel.FinalizarOperacao] Ignorado: operação não iniciada.");
return;
}
op.Sensoriamento.Operacao.Finalizando = true;
op.Trajetoria?.LoopAtualizaDados();
@ -1785,7 +1791,11 @@ namespace AgroBase.Models
Estado comandoEstado = bicos[i].ComandoAtuar ? Estado.Ligado : Estado.Desligado;
if (ForcarEnvio || comandoEstado != bicos[i]._EstadoLeitura)
{
GeneralJoystick.EnviarComandoAtuador(bicos[i].Componente, bicos[i].ID, Status: bicos[i].ComandoAtuar);
op.DispAtu?.Dados?.EnviarComandoFisicoBico(
bicos[i],
CanMessagePosicaoDados.Command1,
bicos[i].ComandoAtuar
);
}
}
@ -2360,8 +2370,6 @@ namespace AgroBase.Models
return;
}
List<double?> valores = null;
switch (comp)
{
case S_Code.sMOD:
@ -2369,39 +2377,37 @@ namespace AgroBase.Models
{
op.Controle.AlturaBarra = (double)controleBase.Controle.Altura;
}
break;
dadosAtu?.EnviarComandoFisicoReset();
return;
case S_Code.sBIC:
var bico = dadosAtu.BicosPulverizadores?.FirstOrDefault(x => x.ID == controleBase._comp_id);
if (controleBase._comp_value != null)
{
int angulo;
if (TryDeserializeValue(controleBase._comp_value, out angulo))
{
var bico = dadosAtu.BicosPulverizadores?.FirstOrDefault(x => x.ID == controleBase._comp_id);
if (bico != null)
bico.AnguloAbertura = angulo;
}
valores = new List<double?>
{
controleBase.Controle.AnguloSP
};
}
break;
dadosAtu?.EnviarComandoFisicoBico(
bico,
CanMessagePosicaoDados.Command1,
controleBase.Controle.Estado == true,
angulo: controleBase.Controle.AnguloSP
);
return;
case S_Code.sBMB:
var bomba = dadosAtu.BombasPressurizadoras?.FirstOrDefault(x => x.ID == controleBase._comp_id);
switch (controleBase.Controle.Comando)
{
case CanMessagePosicaoDados.Command1:
valores = new List<double?>
{
controleBase.Controle.Potencia,
controleBase.Controle.Pressao
};
break;
case CanMessagePosicaoDados.Command2:
ModoControleBomba modo;
@ -2411,26 +2417,20 @@ namespace AgroBase.Models
{
op.Parametros.Controle.AtuModoControle = modo;
}
valores = new List<double?>
{
(int)modo,
controleBase.Controle.Potencia
};
}
break;
}
break;
}
GeneralJoystick.EnviarComandoAtuador(
comp,
controleBase._comp_id,
Status: controleBase.Controle.Estado,
Valores: valores,
Comando: controleBase.Controle.Comando,
Forcar: true
);
dadosAtu?.EnviarComandoFisicoBomba(
bomba,
CanMessagePosicaoDados.Command1,
controleBase.Controle.Estado == true,
potencia: controleBase.Controle.Potencia,
pressao: controleBase.Controle.Pressao,
modo: op.Parametros.Controle.AtuModoControle
);
return;
}
}
private void ExecutarComandoSensoriamento(OperacaoComandoBaseModel controleBase, OperacaoModel op)

View File

@ -298,7 +298,7 @@ namespace AgroBase.Comum
if (Dispositivo == T_Code.Sen)
GeneralJoystick.EnviarComandoSensoriamento(S_Code.sMOD, "", Status: true);
else if (Dispositivo == T_Code.Atu)
GeneralJoystick.EnviarComandoAtuador(S_Code.sMOD, "", Status: true);
(Dados as AtuadorModel)?.EnviarComandoFisicoReset();
}
private void btnPinout_Click(object sender, EventArgs e)

View File

@ -974,9 +974,6 @@ namespace AgroBase.Services.Operadores
angulo_controle = bico.AnguloControle,
angulo_abertura = bico.AnguloAbertura,
ultimo_comando_ms = idadeUltimoComandoBicoMs,
ultima_resposta_ms = idadeUltimaRespostaBicoMs,
atuacoes = bico.Atuacoes,
tempo_atuado = bico.TempoAtuado,
tempo_atuado_em_movimento = bico.TempoAtuadoEmMovimento,
@ -984,6 +981,16 @@ namespace AgroBase.Services.Operadores
vazao_instantanea_mls = bico.VazaoInstantaneaMLs,
vazao_media_mls = bico.VazaoMediaMLs,
volume_vazado_ml = bico.VolumeVazadoML,
comando_solicitado = bico.ComandoAtuar,
status_desejado_fisico = bico.UltimoStatusDesejado,
angulo_desejado_fisico = bico.UltimoAnguloDesejado,
ultimo_comando_ms = idadeUltimoComandoBicoMs,
ultima_resposta_ms = idadeUltimaRespostaBicoMs,
telemetria_pos_comando =
bico.UltimoComandoEnviado != DateTime.MinValue &&
bico.UltimaLeitura != DateTime.MinValue &&
bico.UltimaLeitura >= bico.UltimoComandoEnviado
};
atuBicosAtualizados.Add(($"{basePath}", bicoObj));
@ -1056,13 +1063,21 @@ namespace AgroBase.Services.Operadores
modo_controle = bomba.Controle?.Modo,
sensor_id_num = bomba.Sensor_ID_Num,
ultimo_comando_ms = idadeUltimoComandoBombaMs,
ultima_resposta_ms = idadeUltimaRespostaBombaMs,
atuacoes = bomba.Atuacoes,
tempo_atuado = bomba.TempoAtuado,
tempo_com_potencia = bomba.TempoComPotencia,
potencia_media_ativa = bomba.PotenciaMediaAtiva,
comando_solicitado = bomba.ComandoAtuar,
status_desejado_fisico = bomba.UltimoStatusDesejado,
potencia_desejada_fisica = bomba.UltimaPotenciaDesejada,
pressao_desejada_fisica = bomba.UltimaPressaoDesejada,
ultimo_comando_ms = idadeUltimoComandoBombaMs,
ultima_resposta_ms = idadeUltimaRespostaBombaMs,
telemetria_pos_comando =
bomba.UltimoComandoEnviado != DateTime.MinValue &&
bomba.UltimaLeitura != DateTime.MinValue &&
bomba.UltimaLeitura >= bomba.UltimoComandoEnviado
};
atuBombasAtivos.Add($"{tipo}.{id}");

View File

@ -562,7 +562,7 @@ class ModuloAtuador(ModuloDiagnosticoBase):
"ultimo_status_desejado" in dados
)
comando = self._estado_desejado_ligado(dados)
comando = self._estado_fisico_desejado_ligado(dados)
estado = self._get(dados, "estado_leitura", self._campo_atual(dados, "EstadoLeitura", 0))
estado_on = self._estado_ligado(estado)
@ -583,7 +583,13 @@ class ModuloAtuador(ModuloDiagnosticoBase):
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),
dados.get(
"angulo_desejado_fisico",
dados.get(
"angulo_desejado",
angulo_controle if comando else angulo_neutro
)
),
angulo_controle if comando else angulo_neutro,
)
@ -610,6 +616,21 @@ class ModuloAtuador(ModuloDiagnosticoBase):
"angulo_desejado": angulo_desejado,
"ultimo_comando_ms": ultimo_comando_ms,
}
if self._aguardando_telemetria_pos_comando(dados):
return 94, {
"comando_disponivel": True,
"aguardando_telemetria_pos_comando": True,
"comando_fisico": comando,
"comando_logico": self._comando_logico_ligado(dados),
"estado_on": estado_on,
"usa_servo": usa_servo,
"angulo": angulo,
"angulo_desejado": angulo_desejado,
"ultimo_comando_ms": ultimo_comando_ms,
"ultima_resposta_ms": self._float(dados.get("ultima_resposta_ms", 999999), 999999),
"telemetria_pos_comando": self._telemetria_depois_do_comando(dados),
}
if comando and not estado_on:
severidade = 85 if tem_idade_comando and ultimo_comando_ms >= self.COMANDO_FALHA_MS else 70
@ -631,14 +652,20 @@ class ModuloAtuador(ModuloDiagnosticoBase):
score = min(score, 55 if severidade >= 85 else 75)
if not comando and estado_on:
severidade = 95 if tem_idade_comando and ultimo_comando_ms >= self.COMANDO_FALHA_MS else 82
falha_confirmada = (
tem_idade_comando and
ultimo_comando_ms >= self.COMANDO_FALHA_MS and
self._telemetria_depois_do_comando(dados)
)
severidade = 95 if falha_confirmada else 82
motivos.append(f"Bico ligado sem comando ({label})")
condicoes.append({
"label": "Bico",
"valor": 1,
"severidade": severidade,
"classe": "falha_hardware" if severidade > 90 else "processo",
"classe": "falha_hardware" if falha_confirmada else "processo",
"descricao": f"Bico {label} aparenta estar ligado sem comando",
"acoes": [
"Verificar travamento da solenoide.",
@ -646,7 +673,7 @@ class ModuloAtuador(ModuloDiagnosticoBase):
],
})
score = min(score, 25 if severidade > 90 else 65)
score = min(score, 25 if falha_confirmada else 65)
if usa_servo and angulo is not None and angulo_desejado is not None:
erro_ang = abs(angulo - angulo_desejado)
@ -734,7 +761,7 @@ class ModuloAtuador(ModuloDiagnosticoBase):
"status_desejado_fisico" in dados or
"ultimo_status_desejado" in dados
)
comando = self._estado_desejado_ligado(dados)
comando = self._estado_fisico_desejado_ligado(dados)
estado = self._get(dados, "estado_leitura", self._campo_atual(dados, "EstadoLeitura", 0))
estado_on = self._estado_ligado(estado)
@ -768,6 +795,23 @@ class ModuloAtuador(ModuloDiagnosticoBase):
"pressao_sp": pressao_sp,
"ultimo_comando_ms": ultimo_comando_ms,
}
if self._aguardando_telemetria_pos_comando(dados):
return 94, {
"comando_disponivel": True,
"aguardando_telemetria_pos_comando": True,
"comando_fisico": comando,
"comando_logico": self._comando_logico_ligado(dados),
"estado_on": estado_on,
"bomba_ligada": bomba_ligada,
"potencia": potencia,
"potencia_sp": potencia_sp,
"pressao": pressao,
"pressao_sp": pressao_sp,
"ultimo_comando_ms": ultimo_comando_ms,
"ultima_resposta_ms": self._float(dados.get("ultima_resposta_ms", 999999), 999999),
"telemetria_pos_comando": self._telemetria_depois_do_comando(dados),
}
if comando and not bomba_ligada:
severidade = 85 if tem_idade_comando and ultimo_comando_ms >= self.COMANDO_FALHA_MS else 70
@ -789,14 +833,25 @@ class ModuloAtuador(ModuloDiagnosticoBase):
score = min(score, 55 if severidade >= 85 else 75)
if not comando and bomba_ligada:
severidade = 95 if tem_idade_comando and ultimo_comando_ms >= self.COMANDO_FALHA_MS else 82
falha_confirmada = (
tem_idade_comando and
ultimo_comando_ms >= self.COMANDO_FALHA_MS and
self._telemetria_depois_do_comando(dados)
)
# O agitador não deve parar a operação por esse caso sozinho.
# Ele pode alertar, mas não virar falha crítica do ATU.
if eh_agitador:
falha_confirmada = False
severidade = 95 if falha_confirmada else 82
motivos.append(f"Bomba {label} ligada sem comando")
condicoes.append({
"label": "Bomba",
"valor": potencia,
"severidade": severidade,
"classe": "falha_hardware" if severidade > 90 else "processo",
"classe": "falha_hardware" if falha_confirmada else "processo",
"descricao": f"Bomba {label} aparenta ligada sem comando",
"acoes": [
"Verificar travamento de relé/saída.",
@ -804,7 +859,7 @@ class ModuloAtuador(ModuloDiagnosticoBase):
],
})
score = min(score, 25 if severidade > 90 else 65)
score = min(score, 25 if falha_confirmada else 65)
if eh_principal and comando and bomba_ligada:
pressao_ref = pressao if pressao > 0 else ctx["pressao_linha"]
@ -1328,8 +1383,27 @@ class ModuloAtuador(ModuloDiagnosticoBase):
def _comando_em_transicao(self, dados, janela_ms=None):
janela_ms = janela_ms if janela_ms is not None else self.PROCESSO_GRACE_MS
ultimo_comando_ms = self._float(dados.get("ultimo_comando_ms", 999999), 999999)
return ultimo_comando_ms < 900000 and ultimo_comando_ms < janela_ms
ultimo_comando_ms = self._float(
dados.get("ultimo_comando_ms", 999999),
999999
)
if ultimo_comando_ms >= 900000:
return False
if ultimo_comando_ms < janela_ms:
return True
# Mesmo fora da janela curta, se ainda não chegou telemetria
# posterior ao comando, não dá para julgar a atuação como falha real.
if (
ultimo_comando_ms < self.COMANDO_FALHA_MS and
not self._telemetria_depois_do_comando(dados)
):
return True
return False
def _pulverizacao_em_transicao(self, ctx):
bomba = ctx.get("bomba_principal")
@ -1413,22 +1487,85 @@ class ModuloAtuador(ModuloDiagnosticoBase):
return texto in ["1", "ligado", "true", "on", "sim", "yes"]
def _estado_desejado_ligado(self, dados):
"""
Compatibilidade com código antigo.
A partir de agora, 'estado desejado' no health significa
último estado físico enviado ao ATU, não intenção lógica instantânea.
"""
return self._estado_fisico_desejado_ligado(dados)
def _estado_fisico_desejado_ligado(self, dados):
if not isinstance(dados, dict):
return False
if "comando_atuar" in dados:
return self._bool(dados.get("comando_atuar", False))
for key in [
"estado_desejado",
"status_desejado",
"status_desejado_fisico",
"estado_desejado_fisico",
"estado_desejado",
"ultimo_status_desejado",
"status_desejado",
]:
if key in dados:
if key in dados and dados.get(key) is not None:
return self._estado_ligado(dados.get(key))
return False
# Fallback para versões antigas do Redis.
return self._bool(
dados.get(
"comando_solicitado",
dados.get("comando_atuar", False)
)
)
def _comando_logico_ligado(self, dados):
if not isinstance(dados, dict):
return False
return self._bool(
dados.get(
"comando_solicitado",
dados.get("comando_atuar", False)
)
)
def _tem_comando_fisico_registrado(self, dados):
ultimo_comando_ms = self._float(
dados.get("ultimo_comando_ms", 999999),
999999
)
return ultimo_comando_ms < 900000
def _telemetria_depois_do_comando(self, dados, tolerancia_ms=150):
if not isinstance(dados, dict):
return True
if not self._tem_comando_fisico_registrado(dados):
return True
if "telemetria_pos_comando" in dados:
return self._bool(dados.get("telemetria_pos_comando", False))
ultimo_comando_ms = self._float(dados.get("ultimo_comando_ms", 999999), 999999)
ultima_resposta_ms = self._float(dados.get("ultima_resposta_ms", 999999), 999999)
if ultimo_comando_ms >= 900000 or ultima_resposta_ms >= 900000:
return False
# idade menor = evento mais recente
return ultima_resposta_ms <= ultimo_comando_ms + tolerancia_ms
def _aguardando_telemetria_pos_comando(self, dados):
ultimo_comando_ms = self._float(
dados.get("ultimo_comando_ms", 999999),
999999
)
return (
ultimo_comando_ms < 900000 and
ultimo_comando_ms < self.COMANDO_FALHA_MS and
not self._telemetria_depois_do_comando(dados)
)
@staticmethod