agrobot_base/AgroBase/AgroBase/Models/Modules/AtuadorModel.cs

3217 lines
130 KiB
C#

using AgroBase.Services;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using static AgroBase.Models.Enums;
namespace AgroBase.Models.Modules
{
public class AtuadorModel : DispositivoBaseModel
{
public T_Code Dispositivo { get; set; } = T_Code.Atu;
public string Modulo_ID { get; set; } = T_Code.Atu.ToString();
public byte _EnderecoCAN_Tx { get; set; } = 0x13;
public byte _EnderecoCAN_Rx { get; set; } = 0x14;
public int _TaxaAmostragem { get; set; } = 1000;
public double FrequenciaBase
{
get
{
return 1.0 / (_TaxaAmostragem / 1000.0);
}
}
public bool RequisitarDados { get; set; } = true;
public static int TempoLimiteConexao { get; set; } = 8000;
public bool Conectado
{
get
{
DateTime Agora = DateTime.Now;
bool modConectado = DadosLeitura.UltimoComandoRespondido.AddMilliseconds(TempoLimiteConexao) > Agora;
return modConectado;
}
}
public bool ConexaoAtiva
{
get
{
return Variaveis.OperacaoEmAndamento.Sensoriamento?.OperadorSaude?.ModulosSaude?.Any(x => x.modulo == Dispositivo && x.status == StatusModulo.Operante) ?? false;
}
}
public bool AtuPronto { get; set; } = false;
public double AnguloBarraEsquerda { get; set; }
public double AnguloBarraDireita { get; set; }
public static PinoutDataModel Pinout { get; set; }
public static List<FuncoesPinout> Funcoes { get; set; } = new List<FuncoesPinout>()
{
FuncoesPinout.SDA,
FuncoesPinout.SCL,
};
//public int QuantidadeBicos { get; set; } = 4;
public double DensidadeHerbicida { get; set; } = 1.0;
public List<AtuadorBicoModel> BicosPulverizadores { get; set; }
public List<AtuadorBombaModel> BombasPressurizadoras { get; set; }
public List<SensorModel> Sensores { get; set; }
[JsonIgnore]
public AsyncTaskTimerModel tmrRegistrador;
private int _TempoReconexaoComponentes = 5;
private int _VezesLeituraReconexao = 0;
public AtuadorMensagemJsonModel DadosLeitura { get; set; } = new AtuadorMensagemJsonModel();
[JsonIgnore]
public AtuHandler _AtuHandler = new AtuHandler();
[JsonIgnore]
public AtuadorBombaModel BombaPressurizadora
{
get
{
var Bomba = BombasPressurizadoras?.FirstOrDefault(x => x.ID.Contains("BMBLN"));
return Bomba;
}
}
[JsonIgnore]
public AtuadorBombaModel AgitadorDeCalda
{
get
{
var Bomba = BombasPressurizadoras?.FirstOrDefault(x => x.ID.Contains("BMBAGT"));
return Bomba;
}
}
[JsonIgnore]
public SensorModel SensorMassaReservatorio
{
get
{
var sMAS = Sensores?.FirstOrDefault(x => x.Componente == S_Code.sMAS && x.ID.Contains("MASRS"));
return sMAS;
}
}
[JsonIgnore]
private SensorModel SensorAnguloBarraEsquerda
{
get
{
var sMAG = Sensores?.FirstOrDefault(x => x.Componente == S_Code.sMAG && x.ID.Contains("ESQ"));
return sMAG;
}
}
[JsonIgnore]
private SensorModel SensorAnguloBarraDireita
{
get
{
var sMAG = Sensores?.FirstOrDefault(x => x.Componente == S_Code.sMAG && x.ID.Contains("DIR"));
return sMAG;
}
}
public HerbicideCounterService ContadorCarga = new HerbicideCounterService(VariaveisEquipamento.CapacidadeReservatorio, VariaveisEquipamento.DensidadeHerbicida);
#region VAZAO
[JsonIgnore]
private SensorModel SensorFluxoLinha => Sensores.FirstOrDefault(x => x.Componente == S_Code.sFLX && x.ID == "FLXLN");
const double FatorCalibracaoFluxo = 0.55;
public double VazaoMaximaSensor_LMin { get; set; } = 20.0;
public double VazaoFluxoMLpSInstantanea => SensorFluxoLinha?.ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Fluxo)?.atual?.valor ?? 0;
public double VazaoFluxoMLpSInstantaneaCorrigida => VazaoFluxoMLpSInstantanea * FatorCalibracaoFluxo;
public double VazaoFluxoMLpSInstantaneaCalc
{
get
{
int pulsos = (int)(SensorFluxoLinha?.ValoresLeituras?
.FirstOrDefault(x => x.funcao == FuncoesPinout.QtdPulsos)?
.atual?.valor ?? 0.0);
int periodo = (int)(SensorFluxoLinha?.ValoresLeituras?
.FirstOrDefault(x => x.funcao == FuncoesPinout.Periodo)?
.atual?.valor ?? 0.0);
if (pulsos <= 0 || periodo <= 0)
return 0.0;
double frequencia = (double)pulsos / periodo * 1000.0;
double q_L_min = (frequencia + 3.0) / 8.1;
if (q_L_min < 0)
q_L_min = 0;
if (q_L_min > VazaoMaximaSensor_LMin)
q_L_min = VazaoMaximaSensor_LMin;
return q_L_min * 1000.0 / 60.0 * FatorCalibracaoFluxo;
}
}
public double VazaoFluxoMLpSReferencia
{
get
{
return ObterVazaoAtualConfiavelMLs(DateTime.Now);
}
}
[JsonIgnore]
public double VazaoMaximaSensor_mLs => VazaoMaximaSensor_LMin * 1000.0 / 60.0;
public double VazaoMediaMLs { get; set; }
public double VolumeVazadoML { get; set; }
public double TempoAtuado { get; set; }
#endregion
#region PRESSAO
[JsonIgnore]
private SensorModel SensorPressaoLinha
{
get
{
return Sensores.FirstOrDefault(x => x.Componente == S_Code.sPRS && x.ID == "PRSLN");
}
}
public double _PressaoVmin { get; set; } = 0.0;
public double _PressaoVmax { get; set; } = 4.5;
public double _PressaoMax { get; set; } = 174.045; // 1,2 MPa
public double PressaoLinha => SensorPressaoLinha?.ValoresLeituras.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Pressao)?.atual?.valor ?? 0;
public double PressaoLinhaCalc
{
get
{
double _LeituraPressao = SensorPressaoLinha?.ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.LeituraBruta)?.atual?.valor ?? 0;
double tensao = FuncoesMatematicas.Map(_LeituraPressao, 0, 4095, 0, 3300) / 1000.0;
double pressao = FuncoesMatematicas.Map(tensao, _PressaoVmin, _PressaoVmax, 0, _PressaoMax);
return pressao;
}
}
#endregion
public double PercentualErvasTerreno { get; set; } = 0;
#region METRICAS GERAIS DE TEMPO E VAZAO
private readonly object _metricasLock = new object();
private long _ultimoTickMetricas;
private HashSet<int> _bicosAtivosIntervaloAnterior = new HashSet<int>();
private HashSet<int> _bombasAtivasIntervaloAnterior = new HashSet<int>();
private Dictionary<int, double> _potenciaBombasIntervaloAnterior = new Dictionary<int, double>();
private double _vazaoIntervaloAnteriorMLs;
private const double IntervaloMaximoIntegracaoSeg = 2.0;
private const double IdadeMaximaTelemetriaSeg = 2.5;
private double _tempoMovimentoAnteriorSeg;
private bool LeituraSensorRecente(SensorValoresLeituraModel leitura, DateTime agora)
{
if (leitura?.atual == null || leitura.atual.lidoEm == DateTime.MinValue)
{
return false;
}
double idadeSeg = (agora - leitura.atual.lidoEm).TotalSeconds;
return idadeSeg >= -1.0 && idadeSeg <= IdadeMaximaTelemetriaSeg;
}
private double ObterVazaoAtualConfiavelMLs(DateTime agora)
{
var sensor = SensorFluxoLinha;
if (sensor == null || !sensor.Inicializado || sensor.ValoresLeituras == null)
return 0;
double maximaMLs =
VazaoMaximaSensor_LMin *
1000.0 /
60.0;
var leituraFluxo = sensor.ValoresLeituras.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Fluxo);
/*
* IMPORTANTE:
* A leitura direta FuncoesPinout.Fluxo já está em mL/s no modelo.
* Não converter novamente de L/min para mL/s.
*
* Zero recente é válido. Se a linha está sem fluxo, o sensor deve
* poder dizer zero sem cair no fallback por pulso antigo.
*/
if (LeituraSensorRecente(leituraFluxo, agora))
{
double vazaoMLs;
try
{
vazaoMLs = Convert.ToDouble(leituraFluxo.atual.valor);
}
catch
{
return 0;
}
if (double.IsNaN(vazaoMLs) ||
double.IsInfinity(vazaoMLs) ||
vazaoMLs < 0)
{
return 0;
}
double vazaoCorrigidaMLs = vazaoMLs * FatorCalibracaoFluxo;
return FuncoesMatematicas.Clamp(
vazaoCorrigidaMLs,
0.0,
maximaMLs
);
}
/*
* Fallback por pulso:
* aqui sim a fórmula calcula q em L/min e depois converte para mL/s.
*/
var leituraPulsos = sensor.ValoresLeituras.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.QtdPulsos);
var leituraPeriodo = sensor.ValoresLeituras.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Periodo);
if (!LeituraSensorRecente(leituraPulsos, agora) || !LeituraSensorRecente(leituraPeriodo, agora))
return 0;
double calculadaMLs =
VazaoFluxoMLpSInstantaneaCalc;
if (double.IsNaN(calculadaMLs) || double.IsInfinity(calculadaMLs) || calculadaMLs < 0)
return 0;
return FuncoesMatematicas.Clamp(
calculadaMLs,
0.0,
maximaMLs
);
}
private double ObterPotenciaAtualConfiavelPct(AtuadorBombaModel bomba, DateTime agora)
{
if (bomba == null || !bomba.Inicializado || bomba.ValoresLeituras == null)
return 0;
var leituraPotencia = bomba.ValoresLeituras.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Potencia);
if (!LeituraSensorRecente(leituraPotencia, agora))
return 0;
double potencia;
try
{
potencia = Convert.ToDouble(leituraPotencia.atual.valor);
}
catch
{
return 0;
}
if (double.IsNaN(potencia) || double.IsInfinity(potencia))
return 0;
return FuncoesMatematicas.Clamp(
potencia,
0.0,
100.0
);
}
public void AtualizarMetricasOperacao()
{
var op = Variaveis.OperacaoEmAndamento;
lock (_metricasLock)
{
long agoraTicks = Stopwatch.GetTimestamp();
DateTime agora = DateTime.Now;
bool operacaoConcluida = op.Sensoriamento?.Operacao?.StatusOperacaoAtual == StatusOperacao.Concluido;
bool operacaoAtiva = op.Sensoriamento?.Operacao?.OperacaoIniciada == true && !operacaoConcluida;
bool atuadorComTelemetriaRecente =
DadosLeitura != null &&
DadosLeitura.UltimoComandoRespondido != DateTime.MinValue &&
DadosLeitura.UltimoComandoRespondido.AddMilliseconds(TempoLimiteConexao) > agora;
bool atuadorTemEstrutura = (BicosPulverizadores?.Count ?? 0) > 0;
bool atuadorDisponivel =
operacaoAtiva &&
atuadorTemEstrutura &&
atuadorComTelemetriaRecente;
bool pulverizadorAutomatico = op.Parametros?.Controle?.PulverizadorAutomatico ?? false;
/*
* Bicos são contabilizados pelo comando de alta resolução.
* O retorno CAN permanece sendo telemetria e diagnóstico.
*/
var bicosAtivosAgora = atuadorDisponivel
? BicosPulverizadores
.Where(b =>
b != null &&
b.Inicializado &&
b.Comandar &&
b.ComandoAtuar)
.Select(b => b.ID_Num)
.ToHashSet()
: new HashSet<int>();
/*
* O mesmo estado usado nas métricas governa
* abertura e fechamento dos trechos.
*/
foreach (var bico in BicosPulverizadores ?? new List<AtuadorBicoModel>())
{
bool atuandoComandado = bicosAtivosAgora.Contains(bico.ID_Num);
bico.AtualizarEstadoTrecho(atuandoComandado);
}
/*
* Em pulverização automática, respeita a mesma liberação
* utilizada por VerificaControleLeitura().
*/
bool bombasLiberadas =
!pulverizadorAutomatico ||
ContadorCarga.PulverizadorLiberado;
var bombasAtivasAgora =
atuadorDisponivel &&
bombasLiberadas
? BombasPressurizadoras
.Where(b =>
b != null &&
b.Inicializado &&
b.Comandar &&
b.ComandoAtuar)
.Select(b => b.ID_Num)
.ToHashSet()
: new HashSet<int>();
double vazaoAtualMLs = operacaoAtiva ? ObterVazaoAtualConfiavelMLs(agora) : 0.0;
double tempoMovimentoAtualSeg = op.Sensoriamento?.Movimentacao?.TempoMovimentoSegs ?? 0.0;
if (_ultimoTickMetricas != 0)
{
double dt = (agoraTicks - _ultimoTickMetricas) / (double)Stopwatch.Frequency;
if (dt > 0 && dt <= IntervaloMaximoIntegracaoSeg)
{
double deltaMovimentoSeg = 0;
if (tempoMovimentoAtualSeg >= _tempoMovimentoAnteriorSeg)
{
deltaMovimentoSeg = tempoMovimentoAtualSeg - _tempoMovimentoAnteriorSeg;
}
/*
* O tempo em movimento não pode exceder
* o tempo físico do intervalo integrado.
*/
deltaMovimentoSeg = FuncoesMatematicas.Clamp(deltaMovimentoSeg, 0.0, dt);
IntegrarIntervaloAnterior(dt, deltaMovimentoSeg);
}
else
{
/*
* Houve pausa anormal no loop.
* Não inventa tempo nem volume.
*/
ZerarVazoesInstantaneas();
if (dt > IntervaloMaximoIntegracaoSeg)
{
Variaveis.MostrarLog("[ATU/METRICAS] Integração ignorada por dt alto: " + dt.ToString("0.000") + "s");
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Alerta, 50, "[ATU/METRICAS] Integração ignorada por dt alto: " + dt.ToString("0.000") + "s");
}
}
}
/*
* Contagem de atuações das bombas por borda
* do comando efetivo OFF -> ON.
*/
foreach (var bomba in BombasPressurizadoras ?? new List<AtuadorBombaModel>())
{
bool estavaLigada = _bombasAtivasIntervaloAnterior.Contains(bomba.ID_Num);
bool estaLigada = bombasAtivasAgora.Contains(bomba.ID_Num);
if (!estavaLigada && estaLigada)
{
bomba.Atuacoes++;
}
}
/*
* Atualiza o snapshot que representará
* o próximo intervalo.
*/
_ultimoTickMetricas = agoraTicks;
_tempoMovimentoAnteriorSeg = tempoMovimentoAtualSeg;
_bicosAtivosIntervaloAnterior = bicosAtivosAgora;
_bombasAtivasIntervaloAnterior = bombasAtivasAgora;
_potenciaBombasIntervaloAnterior = (BombasPressurizadoras ??
new List<AtuadorBombaModel>())
.ToDictionary(
b => b.ID_Num,
b =>
ObterPotenciaAtualConfiavelPct(
b,
agora
)
);
_vazaoIntervaloAnteriorMLs = vazaoAtualMLs;
AtualizarVazoesInstantaneas(bicosAtivosAgora, vazaoAtualMLs);
VazaoMediaMLs = TempoAtuado > 0 ? VolumeVazadoML / TempoAtuado : 0;
AtualizaPercentualErvas();
}
}
private void IntegrarIntervaloAnterior(double dt, double tempoMovimentoIntervaloSeg)
{
var bicosDoIntervalo = (BicosPulverizadores ??
new List<AtuadorBicoModel>())
.Where(b =>
_bicosAtivosIntervaloAnterior
.Contains(b.ID_Num))
.ToList();
/*
* Tempo geral de pulverização:
* tempo durante o qual pelo menos um bico
* permaneceu comandado.
*/
if (bicosDoIntervalo.Count > 0)
{
TempoAtuado += dt;
foreach (var bico in bicosDoIntervalo)
{
bico.TempoAtuado += dt;
bico.TempoAtuadoEmMovimento += tempoMovimentoIntervaloSeg;
}
double vazaoTotalMLs = Math.Max(0, _vazaoIntervaloAnteriorMLs);
if (vazaoTotalMLs > 0)
{
double volumeIntervaloML = vazaoTotalMLs * dt;
VolumeVazadoML += volumeIntervaloML;
double volumePorBicoML = volumeIntervaloML / bicosDoIntervalo.Count;
foreach (var bico in bicosDoIntervalo)
{
bico.TempoComFluxo += dt;
bico.VolumeVazadoML += volumePorBicoML;
}
}
/*
* A média precisa ser recalculada mesmo quando
* não existe fluxo. O tempo continua crescendo,
* logo a média operacional deve diminuir.
*/
foreach (var bico in bicosDoIntervalo)
{
bico.VazaoMediaMLs = bico.TempoAtuado > 0 ? bico.VolumeVazadoML / bico.TempoAtuado : 0;
}
}
/*
* Bombas incluem BMBLN e BMBAGT.
* TempoAtuado é baseado no comando efetivo.
* TempoComPotencia usa telemetria CAN recente.
*/
foreach (var bomba in BombasPressurizadoras ?? new List<AtuadorBombaModel>())
{
if (!_bombasAtivasIntervaloAnterior.Contains(bomba.ID_Num))
{
continue;
}
bomba.TempoAtuado += dt;
double potencia = _potenciaBombasIntervaloAnterior
.TryGetValue(
bomba.ID_Num,
out double valorPotencia)
? valorPotencia
: 0;
if (potencia > 0)
{
bomba.TempoComPotencia += dt;
bomba.PotenciaAcumuladaPctSeg += potencia * dt;
}
/*
* Como o nome é PotenciaMediaAtiva,
* o denominador deve ser o período com
* potência efetivamente observada.
*/
bomba.PotenciaMediaAtiva = bomba.TempoComPotencia > 0 ? bomba.PotenciaAcumuladaPctSeg / bomba.TempoComPotencia : 0;
}
}
private void AtualizarVazoesInstantaneas(HashSet<int> ativos, double vazaoTotalMLs)
{
foreach (var bico in BicosPulverizadores)
bico.VazaoInstantaneaMLs = 0;
if (ativos.Count == 0 || vazaoTotalMLs <= 0)
{
return;
}
double vazaoPorBico = vazaoTotalMLs / ativos.Count;
foreach (var bico in BicosPulverizadores)
{
if (ativos.Contains(bico.ID_Num))
{
bico.VazaoInstantaneaMLs = vazaoPorBico;
}
}
}
private void AtualizaPercentualErvas()
{
var op = Variaveis.OperacaoEmAndamento;
double tempoMovimentoSeg = op.Sensoriamento?.Movimentacao?.TempoMovimentoSegs ?? 0;
var bicosConsiderados = BicosPulverizadores?
.Where(b => b != null && b.Comandar)
.ToList()
?? new List<AtuadorBicoModel>();
if (tempoMovimentoSeg <= 0 || bicosConsiderados.Count == 0)
{
PercentualErvasTerreno = 0;
return;
}
/*
* Soma somente atuação ocorrida enquanto
* o rover acumulava tempo de movimento.
*/
double somaBicoSegEmMovimento = bicosConsiderados.Sum(b => b.TempoAtuadoEmMovimento);
/*
* Capacidade total:
* tempo de movimento x quantidade de regiões/bicos.
*/
double capacidadeTotalBicoSeg = tempoMovimentoSeg * bicosConsiderados.Count;
double percentual = capacidadeTotalBicoSeg > 0 ? somaBicoSegEmMovimento / capacidadeTotalBicoSeg * 100.0 : 0;
percentual = FuncoesMatematicas.Clamp(percentual, 0.0, 100.0);
PercentualErvasTerreno = Math.Round(percentual, 2);
}
private void ZerarVazoesInstantaneas()
{
foreach (var bico in BicosPulverizadores)
bico.VazaoInstantaneaMLs = 0;
}
#endregion
public static AtuadorModel CarregarParametrosIniciais()
{
AtuadorModel Atuador = new AtuadorModel()
{
_EnderecoCAN_Tx = 0x12,
_EnderecoCAN_Rx = 0x12,
_TaxaAmostragem = 1000,
RequisitarDados = true,
// 11 ~ 50
Sensores = new List<SensorModel>()
{
// pressao 11 ~ 20
new SensorModel()
{
ID = "PRSLN",
ID_Num = 11,
Descricao = "Pressão na linha principal",
Componente = S_Code.sPRS,
Aferir = true,
Mandatorio = true,
Funcoes = new List<FuncoesPinout>()
{
FuncoesPinout.Pressao,
},
Parametros = new List<SensorParametroModel>()
{
new SensorParametroModel()
{
Descricao = "Vmin",
Valor = "0",
QtdBytes = 1,
Escalar = false,
Enviar = false
},
new SensorParametroModel()
{
Descricao = "Vmax",
Valor = "4.5",
QtdBytes = 1,
Escalar = true,
Enviar = false
},
new SensorParametroModel()
{
Descricao = "Pmax",
Valor = "174",
QtdBytes = 1,
Escalar = false,
Enviar = false
},
},
ValoresLeituras = new List<SensorValoresLeituraModel>()
{
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Iniciado,
posicao = CanMessagePosicaoDados.Status,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.LeituraBruta,
posicao = CanMessagePosicaoDados.Dados1,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Pressao,
posicao = CanMessagePosicaoDados.Dados1,
indice = 1,
analise_health = true,
maximo = 120,
minimo = 0,
nominal = 20,
unidade_medida = "PSI"
}
}
},
// fluxo 21 ~ 30
new SensorModel()
{
ID = "FLXLN",
ID_Num = 21,
Descricao = "Fluxo na linha principal",
Componente = S_Code.sFLX,
Aferir = true,
Mandatorio = true,
Funcoes = new List<FuncoesPinout>()
{
FuncoesPinout.Fluxo,
},
Parametros = new List<SensorParametroModel>(),
ValoresLeituras = new List<SensorValoresLeituraModel>()
{
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Iniciado,
posicao = CanMessagePosicaoDados.Status,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.QtdPulsos,
posicao = CanMessagePosicaoDados.Dados1,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Periodo,
posicao = CanMessagePosicaoDados.Dados1,
indice = 1,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Fluxo,
posicao = CanMessagePosicaoDados.Dados1,
indice = 2,
analise_health = true,
maximo = 120,
minimo = 0,
nominal = 0,
unidade_medida = "mL/s"
}
}
},
// massa 31 ~ 35
new SensorModel()
{
ID = "MASRS",
ID_Num = 31,
Descricao = "Massa do reservatório de herbicida",
Componente = S_Code.sMAS,
Aferir = true,
Mandatorio = true,
Funcoes = new List<FuncoesPinout>()
{
FuncoesPinout.DT,
FuncoesPinout.SCK,
},
Parametros = new List<SensorParametroModel>()
{
new SensorParametroModel()
{
Descricao = "Offset",
Enviar = true,
Escalar = true,
QtdBytes = 2,
Valor = "0.0"
}
},
ValoresLeituras = new List<SensorValoresLeituraModel>()
{
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Iniciado,
posicao = CanMessagePosicaoDados.Status,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.LeituraBruta,
posicao = CanMessagePosicaoDados.Dados1,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Massa,
posicao = CanMessagePosicaoDados.Dados1,
indice = 1,
analise_health = true,
maximo = VariaveisEquipamento.CapacidadeReservatorio + 10.0,
minimo = VariaveisEquipamento.PercentualReservatorioMinCritio,
nominal = VariaveisEquipamento.CapacidadeReservatorio,
unidade_medida = "Kg"
}
}
},
// botoeira 36 ~ 40
new SensorModel()
{
ID = "BOTLAV",
ID_Num = 36,
Descricao = "Botão para lavagem do sistema hidráulico",
Componente = S_Code.sBOT,
Aferir = true,
Mandatorio = false,
Funcoes = new List<FuncoesPinout>()
{
FuncoesPinout.Botao,
},
Parametros = new List<SensorParametroModel>(),
ValoresLeituras = new List<SensorValoresLeituraModel>()
{
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Iniciado,
posicao = CanMessagePosicaoDados.Status,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Botao,
posicao = CanMessagePosicaoDados.Dados1,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.LeituraBruta,
posicao = CanMessagePosicaoDados.Dados1,
indice = 1,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.EstadoLeitura,
posicao = CanMessagePosicaoDados.Dados1,
indice = 2,
},
}
},
// magnetico 41 ~ 50
new SensorModel()
{
ID = "MAGESQ",
Descricao = "Angulo Barra Esquerda",
ID_Num = 41,
Componente = S_Code.sMAG,
Aferir = false,
Mandatorio = false,
Funcoes = new List<FuncoesPinout>()
{
FuncoesPinout.I2C,
},
Parametros = new List<SensorParametroModel>()
{
new SensorParametroModel()
{
Descricao = "Endereço I2C",
Valor = "0x36",
QtdBytes = 1,
Escalar = false,
Enviar = true
},
new SensorParametroModel()
{
Descricao = "Canal MUX",
Valor = "0",
QtdBytes = 1,
Escalar = false,
Enviar = true
},
},
ValoresLeituras = new List<SensorValoresLeituraModel>()
{
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Iniciado,
posicao = CanMessagePosicaoDados.Status,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.LeituraBruta,
posicao = CanMessagePosicaoDados.Dados1,
indice = 0,
unidade_medida = ""
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.LeituraAngulo,
posicao = CanMessagePosicaoDados.Dados1,
indice = 1,
unidade_medida = "°"
},
}
},
new SensorModel()
{
ID = "MAGDIR",
Descricao = "Angulo Barra Direita",
ID_Num = 42,
Componente = S_Code.sMAG,
Aferir = false,
Mandatorio = false,
Funcoes = new List<FuncoesPinout>()
{
FuncoesPinout.I2C,
},
Parametros = new List<SensorParametroModel>()
{
new SensorParametroModel()
{
Descricao = "Endereço I2C",
Valor = "0x36",
QtdBytes = 1,
Escalar = false,
Enviar = true
},
new SensorParametroModel()
{
Descricao = "Canal MUX",
Valor = "1",
QtdBytes = 1,
Escalar = false,
Enviar = true
},
},
ValoresLeituras = new List<SensorValoresLeituraModel>()
{
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Iniciado,
posicao = CanMessagePosicaoDados.Status,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.LeituraBruta,
posicao = CanMessagePosicaoDados.Dados1,
indice = 0,
unidade_medida = ""
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.LeituraAngulo,
posicao = CanMessagePosicaoDados.Dados1,
indice = 1,
unidade_medida = "°"
},
}
},
},
// 51 ~ 60
BombasPressurizadoras = new List<AtuadorBombaModel>()
{
new AtuadorBombaModel()
{
ID = "BMBLN",
ID_Num = 51,
Sensor_ID_Num = 11,
ComandoAtuar = false,
PressaoSP = 100,
PotenciaSP = 100,
Componente = S_Code.sBMB,
Comandar = true,
Mandatorio = true,
Funcoes = new List<FuncoesPinout>()
{
FuncoesPinout.IN1,
FuncoesPinout.IN2,
},
ValoresLeituras = new List<SensorValoresLeituraModel>()
{
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Iniciado,
posicao = CanMessagePosicaoDados.Status,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.MalhaFechada,
posicao = CanMessagePosicaoDados.Status,
indice = 1,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.EstadoLeitura,
posicao = CanMessagePosicaoDados.Dados1,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.PotenciaSP,
posicao = CanMessagePosicaoDados.Dados1,
indice = 1,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Potencia,
posicao = CanMessagePosicaoDados.Dados1,
indice = 2,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.PressaoSP,
posicao = CanMessagePosicaoDados.Dados1,
indice = 3,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Pressao,
posicao = CanMessagePosicaoDados.Dados1,
indice = 4,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.ModoControleBomba,
posicao = CanMessagePosicaoDados.Dados1,
indice = 5,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.QtdPulsos,
posicao = CanMessagePosicaoDados.Dados2,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.PotenciaSP,
posicao = CanMessagePosicaoDados.Dados2,
indice = 1,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Potencia,
posicao = CanMessagePosicaoDados.Dados2,
indice = 2,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Pressao,
posicao = CanMessagePosicaoDados.Dados2,
indice = 3,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.PressaoSP,
posicao = CanMessagePosicaoDados.Dados2,
indice = 4,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.BitmapDiagnostico,
posicao = CanMessagePosicaoDados.Dados2,
indice = 5,
},
},
Controle = new AtuadorBombaModel.BombaModoControleModel()
{
Modo = ModoControleBomba.PID,
PotenciaMinPID = 40,
Kp = 1.10,
Ki = 0.12,
Kd = 0.0,
}
},
new AtuadorBombaModel()
{
ID = "BMBAGT",
ID_Num = 52,
Sensor_ID_Num = -1,
ComandoAtuar = false,
Componente = S_Code.sBMB,
PotenciaSP = 100,
Comandar = true,
Mandatorio = true,
Funcoes = new List<FuncoesPinout>()
{
FuncoesPinout.IN1,
FuncoesPinout.IN2,
},
ValoresLeituras = new List<SensorValoresLeituraModel>()
{
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Iniciado,
posicao = CanMessagePosicaoDados.Status,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.MalhaFechada,
posicao = CanMessagePosicaoDados.Status,
indice = 1,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.EstadoLeitura,
posicao = CanMessagePosicaoDados.Dados1,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.PotenciaSP,
posicao = CanMessagePosicaoDados.Dados1,
indice = 1,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Potencia,
posicao = CanMessagePosicaoDados.Dados1,
indice = 2,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.PressaoSP,
posicao = CanMessagePosicaoDados.Dados1,
indice = 3,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Pressao,
posicao = CanMessagePosicaoDados.Dados1,
indice = 4,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.ModoControleBomba,
posicao = CanMessagePosicaoDados.Dados1,
indice = 5,
}
},
Controle = new AtuadorBombaModel.BombaModoControleModel()
{
Modo = ModoControleBomba.OnOff,
}
},
},
// 61 ~ 70
BicosPulverizadores = new List<AtuadorBicoModel>(),
};
for (int i = 0; i < VariaveisEquipamento.QuantidadeBicosPulverizadores; i++)
{
int posicao = (i + 1);
Atuador.BicosPulverizadores.Add(new AtuadorBicoModel()
{
ID = "B" + posicao.ToString("00"),
ID_Num = 60 + posicao,
Componente = S_Code.sBIC,
Posicao = posicao,
Inicializado = false,
ComMovimento = false,
AnguloNeutro = 90,
AnguloControle = 90,
AnguloAbertura = 30,
Comandar = true,
Mandatorio = true,
ComandoAtuar = false,
AtuacaoNivelAlto = true,
Funcoes = new List<FuncoesPinout>()
{
FuncoesPinout.Solenoide,
FuncoesPinout.Servo,
},
VazaoMediaMLs = 0,
ValoresLeituras = new List<SensorValoresLeituraModel>()
{
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.Iniciado,
posicao = CanMessagePosicaoDados.Status,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.EstadoLeitura,
posicao = CanMessagePosicaoDados.Dados1,
indice = 0,
},
new SensorValoresLeituraModel()
{
funcao = FuncoesPinout.ServoAnguloLeitura,
posicao = CanMessagePosicaoDados.Dados1,
indice = 1,
}
}
});
}
return Atuador;
}
public void LimparDados<T>(T _dados)
{
var Dados = _dados as AtuadorModel;
Dados.DadosLeitura = new AtuadorMensagemJsonModel();
Dados.DadosLeitura.Momento = DateTime.MinValue;
Dados.AtuPronto = false;
Dados.PercentualErvasTerreno = 0;
Dados.BombasPressurizadoras.ForEach(x => x.Funcoes = x.Funcoes.Distinct().ToList());
ReiniciarLeituras(Dados);
PrepararAtuadorParaOperacao(Dados, resetarComandos: true);
}
public void ReiniciarLeituras(AtuadorModel Dados)
{
Dados.Sensores.ForEach(x =>
{
x.ValoresLeituras.ForEach(l =>
{
l.atual = new CanCommandFreqModel<dynamic>();
});
});
Dados.BicosPulverizadores.ForEach(x =>
{
x.ComandoAtuar = false;
x.AnguloControle = 0;
x.Atuacoes = 0;
x.VazaoInstantaneaMLs = 0;
x.VolumeVazadoML = 0;
x.VazaoMediaMLs = 0;
x.TempoAtuado = 0;
x.TempoAtuadoEmMovimento = 0;
x.TempoComFluxo = 0;
x.ReiniciarHistoricoAtuacao(true);
x.ValoresLeituras.ForEach(l =>
{
l.atual = new CanCommandFreqModel<dynamic>();
});
});
Dados.BombasPressurizadoras.ForEach(x =>
{
x.ComandoAtuar = false;
x.PotenciaSP = 0;
x.PressaoSP = 0;
x.Atuacoes = 0;
x.TempoAtuado = 0;
x.TempoComPotencia = 0;
x.PotenciaAcumuladaPctSeg = 0;
x.PotenciaMediaAtiva = 0;
x.ValoresLeituras.ForEach(l =>
{
l.atual = new CanCommandFreqModel<dynamic>();
});
});
Dados.VolumeVazadoML = 0;
Dados.VazaoMediaMLs = 0;
Dados.TempoAtuado = 0;
Dados.AnguloBarraDireita = 0;
Dados.AnguloBarraEsquerda = 0;
Dados.PercentualErvasTerreno = 0;
lock (Dados._metricasLock)
{
Dados._ultimoTickMetricas = 0;
Dados._tempoMovimentoAnteriorSeg = 0;
Dados._bicosAtivosIntervaloAnterior.Clear();
Dados._bombasAtivasIntervaloAnterior.Clear();
Dados._potenciaBombasIntervaloAnterior.Clear();
Dados._vazaoIntervaloAnteriorMLs = 0;
}
}
public void PrepararAtuadorParaOperacao()
{
PrepararAtuadorParaOperacao(this, resetarComandos: false);
}
private void PrepararAtuadorParaOperacao(AtuadorModel Dados, bool resetarComandos)
{
var op = Variaveis.OperacaoEmAndamento;
var dadosAtuador = Dados;
var controle = op?.Parametros?.Controle;
if (op == null || dadosAtuador == null || controle == null)
return;
var bomba = dadosAtuador.BombaPressurizadora;
if (bomba != null)
{
if (resetarComandos)
bomba.ComandoAtuar = false;
/*
* Estes setpoints precisam existir antes do primeiro comando
* físico da bomba em operação automática.
*/
bomba.PotenciaSP = 100;
bomba.PressaoSP = controle.AtuPressaoLinha;
bomba.Controle.Modo = controle.AtuModoControle;
EnviarComandoFisicoBomba(
bomba,
CanMessagePosicaoDados.Command2,
setarControle: true,
status: 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;
if (agitador != null)
{
if (resetarComandos)
agitador.ComandoAtuar = false;
/*
* O agitador tem a própria fonte de verdade em
* HerbicideCounterService.AtualizarAgitadorCalda().
*/
agitador.PotenciaSP = 0;
agitador.PressaoSP = -1;
EnviarComandoFisicoBomba(
agitador,
CanMessagePosicaoDados.Command2,
setarControle: true,
status: true,
modo: agitador.Controle.Modo,
potenciaMinPid: agitador.Controle.PotenciaMinPID,
Kp: agitador.Controle.Kp,
Ki: agitador.Controle.Ki,
Kd: agitador.Controle.Kd
);
}
}
public void SincronizarBombaPressurizadoraAutomatica(bool forcar = false)
{
var op = Variaveis.OperacaoEmAndamento;
var controle = op?.Parametros?.Controle;
var bomba = BombaPressurizadora;
if (op == null || controle == null || bomba == null || !bomba.Inicializado)
return;
bool pulverizadorAutomatico = controle.PulverizadorAutomatico;
if (!pulverizadorAutomatico)
return;
bool operacaoConcluida = op.Sensoriamento?.Operacao?.StatusOperacaoAtual == StatusOperacao.Concluido;
bool atuacaoPermitida =
op.Sensoriamento.Operacao.OperacaoIniciada &&
!op.Sensoriamento.Operacao.Finalizando &&
!op.Sensoriamento.Operacao.Emergencia &&
!operacaoConcluida &&
!op.Sensoriamento.Operacao.Pausa;
/*
* Reaplica setpoints sempre antes de sincronizar.
* Assim, se ReiniciarLeituras() zerou a bomba no início da operação,
* ela não fica presa em potência/pressão zero.
*/
PrepararAtuadorParaOperacao();
bool statusDesejado =
bomba.Comandar &&
atuacaoPermitida &&
ContadorCarga.PulverizadorLiberado;
/*
* ComandoAtuar passa a representar também o comando lógico automático.
* Isso deixa o log BombaLigada coerente com o que o software deseja.
*/
bomba.ComandoAtuar = statusDesejado;
double potenciaEfetiva = statusDesejado ? bomba.PotenciaSP : 0;
double pressaoEfetiva = bomba.Sensor_ID_Num > -1 ? (statusDesejado ? bomba.PressaoSP : 0) : bomba.PressaoSP;
Estado estadoDesejado = statusDesejado ? Estado.Ligado : Estado.Desligado;
bool estadoDivergente = estadoDesejado != bomba._EstadoLeitura;
bool potenciaDivergente = Math.Abs(bomba._PotenciaSP - potenciaEfetiva) > 1.0;
bool pressaoDivergente = bomba.Sensor_ID_Num > -1 && Math.Abs(bomba._PressaoSP - pressaoEfetiva) > 0.05;
if (forcar || estadoDivergente || potenciaDivergente || pressaoDivergente)
{
EnviarComandoFisicoBomba(
bomba,
CanMessagePosicaoDados.Command1,
setarControle: false,
status: statusDesejado,
potencia: potenciaEfetiva,
pressao: pressaoEfetiva
);
}
}
public async Task<bool> VerificaDispositivoConectado()
{
var op = Variaveis.OperacaoEmAndamento;
if (!CanManager.CanService.IsConnected) return false;
if (op?.DispAtu == null) return false;
CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _AtuHandler);
RequisitarDadosDescoberta();
DateTime Inicio = DateTime.Now;
bool Respondido() => op.DispAtu.Dados.DadosLeitura.UltimoComandoRespondido > Inicio;
await FuncoesGlobais.AguardarCondicaoAsync(Respondido, 500, 50);
if (Conectado)
{
DefinirDispositivo();
op.DispAtu?.EnviarProtocolosConfiguracao();
}
return Conectado;
}
private void DefinirDispositivo()
{
var op = Variaveis.OperacaoEmAndamento;
if (!SerialService.DispositivosMapeados.Any(x => x.Dispositivo == Dispositivo && x.Mod_ID == Modulo_ID))
{
SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel()
{
Dispositivo = DadosLeitura.Dispositivo,
Endereco = CanManager.CanService._portName,
Versao = DadosLeitura.Versao.ToString(),
Mod_ID = Modulo_ID
});
op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo] = new Dictionary<string, bool>();
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Conectado, 0, "Dispositivo conectado");
}
}
private void RequisitarDadosDescoberta()
{
Variaveis.OperacaoEmAndamento.DispAtu?.AdicionarMensagemFila(Variaveis.ID_Num_sMOD, CanMessagePosicaoDados.Status, CanMessagePosicaoDados.Status, null, true, false, true);
}
public void RequisitarDadosModulo(int ID_Num, CanMessagePosicaoDados posicaoTx, CanMessagePosicaoDados posicaoRx, bool aguardaResposta)
{
Variaveis.OperacaoEmAndamento.DispAtu?.AdicionarMensagemFila(ID_Num, posicaoTx, posicaoRx, null, aguardaResposta);
}
public (int, CanMessagePosicaoDados, byte[]) ProtocoloComandoReset()
{
return (
Variaveis.ID_Num_sMOD,
CanMessagePosicaoDados.Command1,
new byte[]
{
(byte)Estado.Reset
}
);
}
public List<(int, CanMessagePosicaoDados, byte[])> ProtocoloConfiguracao(bool Conectar)
{
// Inicializar o dicionário
List<(int, CanMessagePosicaoDados, byte[])> config = new List<(int, CanMessagePosicaoDados, byte[])>();
if (!DadosLeitura.Conectado)
{
// Adicionar configuração do ProtocoloMOD
var protocoloMOD = ModConfig(Conectar);
config.AddRange(protocoloMOD);
}
bool configurarTodos = !DadosLeitura.Conectado && Conectar;
// Adicionar configurações dos Sensores que devem ser aferidos
//var sensoresConfig = SensoresConfig(Conectar, configurarTodos);
//config.AddRange(sensoresConfig);
// Adicionar configurações dos Bicos
//var bicosConfig = BicosConfig(Conectar, configurarTodos);
//config.AddRange(bicosConfig);
// Adicionar configurações das Bombas
//var bombasConfig = BombasConfig(Conectar, configurarTodos);
//config.AddRange(bombasConfig);
return config;
}
private List<(int, CanMessagePosicaoDados, byte[])> ModConfig(bool Conectar)
{
var config = new List<byte>
{
(byte)(Conectar ? 1 : 0),
(byte)(_TaxaAmostragem / 1000),
(byte)(RequisitarDados ? 1 : 0),
};
config.AddRange(PinoutModel.PinoProtocolo(Pinout, Funcoes, false));
return new List<(int, CanMessagePosicaoDados, byte[])>
{
(Variaveis.ID_Num_sMOD, CanMessagePosicaoDados.Config1, config.ToArray()),
};
}
private List<(int, CanMessagePosicaoDados, byte[])> SensoresConfig(bool Conectar, bool ConfigurarTodos)
{
var config = new List<(int, CanMessagePosicaoDados, byte[])>();
foreach (var sensor in Sensores.Where(x => (x.Aferir && (ConfigurarTodos ? true : x.Inicializado != Conectar)) || (!x.Aferir && x.Inicializado)))
{
if (!sensor.Aferir && sensor.Inicializado)
{
Conectar = false;
}
(CanMessagePosicaoDados posicao, byte[] payload) = sensor.ProtocoloConfiguracao(Pinout, Conectar, Dispositivo);
if (payload != null)
{
config.Add((sensor.ID_Num, posicao, payload));
}
}
return config;
}
private List<(int, CanMessagePosicaoDados, byte[])> BicosConfig(bool Conectar, bool ConfigurarTodos)
{
var config = new List<(int, CanMessagePosicaoDados, byte[])>();
foreach (var bico in BicosPulverizadores.Where(x => (x.Comandar && (ConfigurarTodos ? true : x.Inicializado != Conectar)) || (!x.Comandar && x.Inicializado)))
{
if (!bico.Comandar && bico.Inicializado)
{
Conectar = false;
}
(CanMessagePosicaoDados posicao, var payload) = bico.ProtocoloConfiguracao(Pinout, Conectar);
if (payload != null)
{
config.Add((bico.ID_Num, posicao, payload));
}
}
return config;
}
private List<(int, CanMessagePosicaoDados, byte[])> BombasConfig(bool Conectar, bool ConfigurarTodos)
{
var config = new List<(int, CanMessagePosicaoDados, byte[])>();
foreach (var bomba in BombasPressurizadoras.Where(x => (x.Comandar && (ConfigurarTodos ? true : x.Inicializado != Conectar)) || (!x.Comandar && x.Inicializado)))
{
if (!bomba.Comandar && bomba.Inicializado)
{
Conectar = false;
}
List<(CanMessagePosicaoDados, byte[])> dados = bomba.ProtocoloConfiguracao(Pinout, Conectar);
foreach (var dado in dados)
{
config.Add((bomba.ID_Num, dado.Item1, dado.Item2));
}
}
return config;
}
public void IniciarRegistrador()
{
if (!(tmrRegistrador?.IsRunning ?? false))
{
LimparDados(this);
PararRegistrador();
tmrRegistrador = new AsyncTaskTimerModel("tmrRegistrador_" + T_Code.Atu.ToString(), tmrRegistrador_Tick, _TaxaAmostragem);
tmrRegistrador.Start();
}
}
public void PararRegistrador()
{
tmrRegistrador?.Dispose();
}
public async Task tmrRegistrador_Tick()
{
var op = Variaveis.OperacaoEmAndamento;
if (Variaveis.Fechando) return;
tmrRegistrador.SetInterval(_TaxaAmostragem);
if (SerialService.DispositivosMapeados.Any(x => x.Dispositivo == Dispositivo) && (_VezesLeituraReconexao >= _TempoReconexaoComponentes))
{
op.DispAtu?.EnviarProtocolosConfiguracao();
_VezesLeituraReconexao = 0;
}
else
{
_VezesLeituraReconexao++;
}
if (DadosLeitura.Conectado)
{
if (RequisitarDados)
{
RequisitarDadosModulo(Variaveis.ID_Num_sTOD, CanMessagePosicaoDados.DadosAll, CanMessagePosicaoDados.DadosAll, true);
}
VerificaControleLeitura();
}
AtualizaComponentesIniciados();
}
private void VerificaControleLeitura()
{
if (Variaveis.Fechando)
return;
var op = Variaveis.OperacaoEmAndamento;
if (op?.DispAtu?.Dados?.Conectado != true)
return;
bool operacaoConcluida = op.Sensoriamento?.Operacao?.StatusOperacaoAtual == StatusOperacao.Concluido;
bool pulverizadorAutomatico = op.Parametros?.Controle?.PulverizadorAutomatico ?? false;
bool calibrando = op.Sensoriamento?.Operacao?.Calibrando == true;
/*
* Bloqueios absolutos.
*
* Nem mesmo a calibração deve conseguir atuar
* durante emergência, finalização ou após a
* operação ter sido encerrada.
*/
bool bloqueioGeral =
!op.Sensoriamento.Operacao.OperacaoIniciada ||
op.Sensoriamento.Operacao.Finalizando ||
op.Sensoriamento.Operacao.Emergencia ||
operacaoConcluida;
/*
* Durante a calibração, Pausa=true é intencional.
* Portanto, a pausa somente bloqueia atuação fora
* da calibração.
*/
bool atuacaoPermitida = !bloqueioGeral && (!op.Sensoriamento.Operacao.Pausa || calibrando);
/*
* BICOS
*
* ComandoAtuar continua representando a intenção
* gerada pelo Weed Worker, operador ou calibração.
*
* Este método apenas calcula o comando físico.
*/
foreach (var bico in BicosPulverizadores.Where(x => x != null && x.Inicializado))
{
bool comandoSolicitado = bico.Comandar && bico.ComandoAtuar;
bool statusDesejado = comandoSolicitado && atuacaoPermitida;
/*
* Fora da calibração, a pulverização automática
* somente pode abrir bicos com reservatório liberado.
*
* Durante a calibração, permite o pulso de teste
* explicitamente solicitado pelo método de calibração.
*/
if (pulverizadorAutomatico && !calibrando)
{
statusDesejado &= ContadorCarga.PulverizadorLiberado;
}
Estado estadoDesejado = statusDesejado ? Estado.Ligado : Estado.Desligado;
double anguloDesejado = statusDesejado ? bico.AnguloControle : bico.AnguloNeutro;
bool estadoDivergente = estadoDesejado != bico._EstadoLeitura;
/*
* Verifica o ângulo também quando desligado,
* para garantir o retorno à posição neutra.
*/
bool anguloDivergente = Math.Abs(anguloDesejado - bico._AnguloBicoLeitura) > 1.0;
if (estadoDivergente || anguloDivergente)
{
EnviarComandoFisicoBico(
bico,
CanMessagePosicaoDados.Command1,
setarControle: false,
status: statusDesejado,
angulo: anguloDesejado
);
}
}
/*
* BOMBAS
*/
foreach (var bomba in BombasPressurizadoras.Where(x => x != null && x.Inicializado))
{
bool ehBombaPrincipal = bomba.ID == BombaPressurizadora?.ID;
bool comandoSolicitado = bomba.Comandar && bomba.ComandoAtuar;
bool statusDesejado;
/*
* Durante a calibração, respeita exatamente
* o comando colocado por RealizarTestesIniciais().
*
* Isso vale tanto para a bomba quanto para o
* agitador.
*/
if (calibrando)
{
statusDesejado = comandoSolicitado && atuacaoPermitida;
}
/*
* Fora da calibração, a bomba principal automática
* deve permanecer ligada enquanto a operação estiver
* autorizada e houver produto suficiente.
*
* Ela não depende de um ComandoAtuar antigo.
*/
else if (ehBombaPrincipal && pulverizadorAutomatico)
{
statusDesejado = bomba.Comandar && atuacaoPermitida && ContadorCarga.PulverizadorLiberado;
/*
* Mantém o modelo coerente com o comando automático.
* Sem isso, o log BombaLigada pode ficar false mesmo quando
* o controle automático deseja a bomba ligada.
*/
bomba.ComandoAtuar = statusDesejado;
}
/*
* Agitador e comandos manuais.
*
* Para o agitador, AtualizarAgitadorCalda()
* já definiu ComandoAtuar e PotenciaSP.
*/
else
{
statusDesejado = comandoSolicitado && atuacaoPermitida;
}
Estado estadoDesejado = statusDesejado ? Estado.Ligado : Estado.Desligado;
/*
* Valores efetivamente enviados ao hardware.
*
* Os setpoints armazenados no modelo não são
* alterados quando existir um bloqueio temporário.
*/
double potenciaEfetiva = statusDesejado ? bomba.PotenciaSP : 0;
double pressaoEfetiva;
if (bomba.Sensor_ID_Num > -1)
{
pressaoEfetiva = statusDesejado ? bomba.PressaoSP : 0;
}
else
{
/*
* Mantém -1 como marcador de bomba sem
* sensor de pressão, como o agitador.
*/
pressaoEfetiva = bomba.PressaoSP;
}
bool estadoDivergente = estadoDesejado != bomba._EstadoLeitura;
bool potenciaDivergente = Math.Abs(bomba._PotenciaSP - potenciaEfetiva) > 1.0;
bool pressaoDivergente = bomba.Sensor_ID_Num > -1 && Math.Abs(bomba._PressaoSP - pressaoEfetiva) > 0.05;
if (estadoDivergente || potenciaDivergente || pressaoDivergente)
{
EnviarComandoFisicoBomba(
bomba,
CanMessagePosicaoDados.Command1,
setarControle: false,
status: statusDesejado,
potencia: potenciaEfetiva,
pressao: pressaoEfetiva
);
}
}
}
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 setarControle,
bool status,
double? angulo = null
)
{
var dispAtu = Variaveis.OperacaoEmAndamento?.DispAtu;
if (dispAtu?.Dados?.Conectado != true || bico == null)
return;
bool comandoControle = bico.ComandoAtuar;
double anguloControle = bico.AnguloControle;
try
{
bico.ComandoAtuar = status;
if (angulo.HasValue)
bico.AnguloControle = angulo.Value;
(_, _, byte[] data) = bico.ProtocoloComando();
dispAtu.AdicionarMensagemFila(
bico.ID_Num,
dado,
dado,
data,
false
);
bico.UltimoComandoEnviado = DateTime.Now;
bico.UltimoStatusDesejado = status;
if (angulo.HasValue)
bico.UltimoAnguloDesejado = angulo.Value;
}
finally
{
if (!setarControle)
{
bico.ComandoAtuar = comandoControle;
bico.AnguloControle = anguloControle;
}
}
}
public void EnviarComandoFisicoBomba(
AtuadorBombaModel bomba,
CanMessagePosicaoDados dado,
bool setarControle,
bool status,
double? potencia = null,
double? pressao = null,
ModoControleBomba? modo = null,
double? potenciaMinPid = null,
double? Kp = null,
double? Ki = null,
double? Kd = null,
bool? desligarSemBicoAberto = null
)
{
var dispAtu = Variaveis.OperacaoEmAndamento?.DispAtu;
if (dispAtu?.Dados?.Conectado != true || bomba == null)
return;
bool comandoControle = bomba.ComandoAtuar;
double potenciaControle = bomba.PotenciaSP;
double pressaoControle = bomba.PressaoSP;
ModoControleBomba modoControle = bomba.Controle.Modo;
bool desligarSemBicoAbertoControle = bomba.Controle.DesligarSemBicosAbertos;
double potenciaMinPidControle = bomba.Controle.PotenciaMinPID;
double kpControle = bomba.Controle.Kp;
double kiControle = bomba.Controle.Ki;
double kdControle = bomba.Controle.Kd;
bool ehComandoEstado = dado == CanMessagePosicaoDados.Command1;
if (ehComandoEstado)
{
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 (desligarSemBicoAberto.HasValue) bomba.Controle.DesligarSemBicosAbertos = desligarSemBicoAberto.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;
(_, _, byte[] data) = bomba.ProtocoloComando(dado);
dispAtu.AdicionarMensagemFila(bomba.ID_Num, dado, dado, data, false);
bomba.UltimoComandoEnviado = DateTime.Now;
bomba.UltimoStatusDesejado = status;
if (potencia.HasValue) bomba.UltimaPotenciaDesejada = potencia.Value;
if (pressao.HasValue) bomba.UltimaPressaoDesejada = pressao.Value;
if (!setarControle)
{
bomba.ComandoAtuar = comandoControle;
bomba.PotenciaSP = potenciaControle;
bomba.PressaoSP = pressaoControle;
bomba.Controle.Modo = modoControle;
bomba.Controle.DesligarSemBicosAbertos = desligarSemBicoAbertoControle;
bomba.Controle.PotenciaMinPID = potenciaMinPidControle;
bomba.Controle.Kp = kpControle;
bomba.Controle.Ki = kiControle;
bomba.Controle.Kd = kdControle;
}
}
private void AtualizaComponentesIniciados()
{
DateTime Agora = DateTime.Now;
bool modConectado = DadosLeitura.UltimoComandoRespondido.AddMilliseconds(TempoLimiteConexao) > Agora;
if (!modConectado && DadosLeitura.Conectado)
{
DadosLeitura.Conectado = false;
}
foreach (var bomba in BombasPressurizadoras.Where(x => x.Inicializado && x.UltimaLeitura.AddMilliseconds(TempoLimiteConexao) < Agora))
{
bomba.Inicializado = false;
}
foreach (var bico in BicosPulverizadores.Where(x => x.Inicializado && x.UltimaLeitura.AddMilliseconds(TempoLimiteConexao) < Agora))
{
bico.Inicializado = false;
bico.AtualizarEstadoTrecho(false);
}
foreach (var sensor in Sensores.Where(x => x.Inicializado && x.UltimaLeitura.AddMilliseconds(TempoLimiteConexao) < Agora))
{
sensor.Inicializado = false;
}
}
private async Task<bool> RealizarTestesIniciais(bool forcar)
{
var op = Variaveis.OperacaoEmAndamento;
if (!op.Sensoriamento.Operacao.ModsCalibragem.ContainsKey(Dispositivo))
op.Sensoriamento.Operacao.ModsCalibragem.Add(Dispositivo, new Dictionary<string, bool>());
var calAtu = op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo];
// Ligar bomba
foreach (var Bomba in BombasPressurizadoras.Where(x => x.Comandar))
{
if (!forcar && op.ModuloEstaCalibrado(Dispositivo, Bomba.ID))
continue;
op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, false);
// Sem herbicida no reservatorio, bomba nao precisa ligar
if (!Bomba.Inicializado || ContadorCarga.PercentualAtual < VariaveisEquipamento.PercentualReservatorioMin)
{
Variaveis.MostrarLog($"[AtuadorModel] [RealizarTestesIniciais] Bomba não iniciada ou nível do reservatório baixo, não irá ligar a bomba {Bomba.ID}");
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Alerta, 50, $"Bomba não iniciada ou nível do reservatório baixo, não irá ligar a bomba {Bomba.ID}", id: Bomba.ID);
op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, false);
continue;
}
bool EhBombaPressurizadora = Bomba.ID == BombaPressurizadora.ID;
double pressaoTeste = 80.0;
if (EhBombaPressurizadora)
{
EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command2,
setarControle: true,
status: true,
modo: ModoControleBomba.OnOff,
desligarSemBicoAberto: false
);
EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command1,
setarControle: true,
status: true,
potencia: 100,
pressao: pressaoTeste
);
}
else
{
bool ligar = op.Parametros.Controle.AtuAgitadorModo != ModoAgitadorCalda.SemAgitacao;
EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command1,
setarControle: true,
status: ligar,
potencia: 100,
pressao: -1
);
}
if (Bomba.Sensor_ID_Num == -1)
{
Variaveis.MostrarLog($"[AtuadorModel] [RealizarTestesIniciais] Bomba {Bomba.ID} não possui um sensor de pressão, será controlada mecanicamente pelo pressostato");
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Alerta, 50, $"Bomba {Bomba.ID} não possui um sensor de pressão, será controlada mecanicamente pelo pressostato", id: Bomba.ID);
bool bombaOk = true;
if (!EhBombaPressurizadora)
{
bool BombaLigou() => Bomba.ComandoAtuar == (Bomba._EstadoLeitura == Estado.Ligado);
await FuncoesGlobais.AguardarCondicaoAsync(BombaLigou, 3000, 200);
bombaOk = BombaLigou();
}
op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, bombaOk);
continue;
}
await FuncoesGlobais.AguardarCondicaoAsync(() => Bomba.AtingiuPressao(pressaoTeste), 3000, 200);
bool BombaOk = Bomba.AtingiuPressao(pressaoTeste);
op.MarcarModuloCalibrado(Dispositivo, Bomba.ID, BombaOk);
if (EhBombaPressurizadora)
{
EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command2,
setarControle: true,
status: true,
modo: op.Parametros.Controle.AtuModoControle,
desligarSemBicoAberto: true,
potenciaMinPid: Bomba.Controle.PotenciaMinPID,
Kp: Bomba.Controle.Kp,
Ki: Bomba.Controle.Ki,
Kd: Bomba.Controle.Kd
);
EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command1,
setarControle: true,
status: op.Parametros.Controle.PulverizadorAutomatico,
potencia: 100,
pressao: op.Parametros.Controle.AtuPressaoLinha
);
}
else
{
bool ligar = op.Parametros.Controle.AtuAgitadorModo != ModoAgitadorCalda.SemAgitacao;
EnviarComandoFisicoBomba(
Bomba,
CanMessagePosicaoDados.Command1,
setarControle: true,
status: ligar,
potencia: 100,
pressao: -1
);
}
}
// Testar os bicos
foreach (var Bico in BicosPulverizadores.Where(x => x.Comandar))
{
calAtu.TryGetValue(Bico.ID, out var cal);
if (!forcar && cal && calAtu[Bico.ID]) continue;
op.MarcarModuloCalibrado(Dispositivo, Bico.ID, false);
if (Bico.Inicializado)
{
EnviarComandoFisicoBico(
Bico,
CanMessagePosicaoDados.Command1,
setarControle: true,
status: true
);
await Task.Delay(200);
EnviarComandoFisicoBico(
Bico,
CanMessagePosicaoDados.Command1,
setarControle: true,
status: false
);
await Task.Delay(200);
op.MarcarModuloCalibrado(Dispositivo, Bico.ID, true);
}
}
await Task.Delay(1000);
var idsEsperados = BombasPressurizadoras
.Where(x => x.Comandar)
.Select(x => x.ID)
.Concat(BicosPulverizadores
.Where(x => x.Comandar)
.Select(x => x.ID))
.ToList();
bool sucesso = idsEsperados.All(id => op.ModuloEstaCalibrado(Dispositivo, id));
return sucesso;
}
public List<Task> RealizarCalibragemAsync(bool forcar)
{
var op = Variaveis.OperacaoEmAndamento;
List<Task> tasks = new List<Task>();
if (op?.DispAtu?.Dados == null)
return tasks;
tasks.Add(Task.Run(async () =>
{
try
{
bool sucesso = await op.DispAtu.Dados.RealizarTestesIniciais(forcar);
var calAtu = op.Sensoriamento.Operacao.ModsCalibragem[Dispositivo];
var v = calAtu.Select(x => $"{x.Key} " + (x.Value ? "Sim" : "Não"));
if (sucesso)
{
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Teste do módulo {Modulo_ID} finalizado com sucesso: " + string.Join(", ", v));
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Operante, 100, $"Teste do módulo {Modulo_ID} finalizado com sucesso: " + string.Join(", ", v));
}
else
{
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Teste do módulo {Modulo_ID} finalizado com pendências: " + string.Join(", ", v));
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Alerta, 50, $"Teste do módulo {Modulo_ID} finalizado com pendências: " + string.Join(", ", v));
}
}
catch (Exception ex)
{
Variaveis.MostrarLog($"[AtuadorModel] [RealizarCalibragemAsync] Erro no teste do módulo {Modulo_ID}: {ex.Message}");
op.Sensoriamento.InserirLog(Dispositivo, StatusModulo.Falha, 0, $"Erro no teste do módulo {Modulo_ID}: {ex.Message}");
throw;
}
}));
return tasks;
}
public void RequisitarDadosSensoresExternos()
{
if (SensorAnguloBarraEsquerda != null)
RequisitarDadosModulo(SensorAnguloBarraEsquerda.ID_Num, CanMessagePosicaoDados.Dados1, CanMessagePosicaoDados.Dados1, true);
if (SensorAnguloBarraDireita != null)
RequisitarDadosModulo(SensorAnguloBarraDireita.ID_Num, CanMessagePosicaoDados.Dados1, CanMessagePosicaoDados.Dados1, true);
}
public void AtualizarDadosSensoresExternos(S_Code componente, int id_num)
{
switch (componente)
{
case S_Code.sMAG:
{
var sensor = Sensores.FirstOrDefault(x => x.ID_Num == id_num);
if (sensor == null) return;
double angulo = sensor.ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.LeituraAngulo)?.atual?.valor ?? double.NaN;
if (sensor.ID_Num == SensorAnguloBarraEsquerda.ID_Num)
AnguloBarraEsquerda = angulo;
else if (sensor.ID_Num == SensorAnguloBarraDireita.ID_Num)
AnguloBarraDireita = angulo;
}
break;
}
}
}
public class AtuadorBicoModel
{
public string ID { get; set; }
public int ID_Num { get; set; }
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; }
public bool ComandoAtuar { get; set; }
public bool Comandar { get; set; }
public bool Mandatorio { get; set; }
public bool Liberado
{
get
{
return !Mandatorio || (Comandar && Inicializado);
}
}
public List<FuncoesPinout> Funcoes { get; set; }
public double VazaoMediaMLs { get; set; }
public double VazaoInstantaneaMLs { get; set; }
public double VolumeVazadoML { get; set; }
public double TempoComFluxo { get; set; }
public bool Inicializado { get; set; }
public DateTime UltimaLeitura
{
get
{
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
}
}
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; }
public Estado _EstadoLeitura => (Estado)(ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.EstadoLeitura)?.atual?.valor ?? 0);
public double _AnguloBicoLeitura => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.ServoAnguloLeitura)?.atual? .valor ?? 0;
public DateTime UltimoComandoEnviado { get; set; } = DateTime.MinValue;
public bool UltimoStatusDesejado { get; set; } = false;
public double UltimoAnguloDesejado { get; set; } = 0;
public (CanMessagePosicaoDados, byte[]) ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
{
var pino = pinout.Pinos.FirstOrDefault(p => p.ComponenteID == ID);
var data = new byte[]
{
(byte)Componente,
(byte)(conectar ? 1 : 0),
(byte)(AtuacaoNivelAlto ? 1 : 0),
0x00, //(byte)Servo_ID_Num,
(byte)(pino?.Barramento ?? TiposBarramentos.CONTROLADOR),
(byte)(pino?.Pino ?? PinoutUtils.PINO_INVALIDO)
};
return (CanMessagePosicaoDados.Config1, data);
}
public (int, CanMessagePosicaoDados, byte[]) ProtocoloComando()
{
return (
ID_Num,
CanMessagePosicaoDados.Command1,
new byte[]
{
(byte)(ComandoAtuar ? Estado.Ligado : Estado.Desligado),
(byte)AnguloControle
}
);
}
private readonly object _trechosLock = new object();
private List<PontoGeo> _trechoAtivo;
public List<List<PontoGeo>> TrechosAtuado { get; set; } = new List<List<PontoGeo>>();
public int Atuacoes { get; set; } = 0;
public double TempoAtuado { get; set; } = 0;
public double TempoAtuadoEmMovimento { get; set; }
public Estado _estadoLeituraAnterior { get; private set; } = Estado.Desligado;
/// <summary>
/// Atualiza o ciclo de vida do trecho com base no estado real do bico.
/// </summary>
public void AtualizarEstadoTrecho(bool atuandoAgora)
{
lock (_trechosLock)
{
bool atuavaAntes = _estadoLeituraAnterior == Estado.Ligado;
// Borda de subida: OFF -> ON
if (!atuavaAntes && atuandoAgora)
{
_trechoAtivo = new List<PontoGeo>();
if (TrechosAtuado == null)
TrechosAtuado = new List<List<PontoGeo>>();
TrechosAtuado.Add(_trechoAtivo);
Atuacoes++;
}
// Borda de descida: ON -> OFF
else if (atuavaAntes && !atuandoAgora)
{
/*
* Se foi um pulso tão curto que nenhum GGA chegou,
* remove o trecho vazio, mas mantém a contagem da atuação.
*/
if (_trechoAtivo != null && _trechoAtivo.Count == 0)
{
TrechosAtuado?.Remove(_trechoAtivo);
}
_trechoAtivo = null;
}
/*
* Recuperação defensiva:
* o estado diz ligado, mas a referência ativa foi perdida,
* por exemplo após carga, clone ou reconstrução do objeto.
*/
else if (atuandoAgora && _trechoAtivo == null)
{
_trechoAtivo = new List<PontoGeo>();
if (TrechosAtuado == null)
TrechosAtuado = new List<List<PontoGeo>>();
TrechosAtuado.Add(_trechoAtivo);
}
_estadoLeituraAnterior = atuandoAgora ? Estado.Ligado : Estado.Desligado;
}
}
public bool AdicionarPontoTrechoAtivo(double latitude, double longitude)
{
lock (_trechosLock)
{
if (_trechoAtivo == null)
return false;
_trechoAtivo.Add(new PontoGeo()
{
Latitude = latitude,
Longitude = longitude
});
return true;
}
}
public void ReiniciarHistoricoAtuacao(bool limparTrechos = true)
{
lock (_trechosLock)
{
_trechoAtivo = null;
_estadoLeituraAnterior = Estado.Desligado;
Atuacoes = 0;
if (limparTrechos)
{
if (TrechosAtuado == null)
TrechosAtuado = new List<List<PontoGeo>>();
else
TrechosAtuado.Clear();
}
}
}
public AtuadorBicoModel Clone()
{
AtuadorBicoModel clone = new AtuadorBicoModel()
{
Comandar = Comandar,
AtuacaoNivelAlto = AtuacaoNivelAlto,
Atuacoes = Atuacoes,
ComandoAtuar = ComandoAtuar,
ComMovimento = ComMovimento,
AnguloAbertura = AnguloAbertura,
AnguloControle = AnguloControle,
UltimoAnguloDesejado = UltimoAnguloDesejado,
UltimoComandoEnviado = UltimoComandoEnviado,
UltimoStatusDesejado = UltimoStatusDesejado,
Inicializado = Inicializado,
Mandatorio = Mandatorio,
Componente = Componente,
Funcoes = Funcoes,
ID = ID,
ID_Num = ID_Num,
VolumeVazadoML = VolumeVazadoML,
TrechosAtuado =
TrechosAtuado?
.Select(trecho =>
trecho?
.Select(ponto => new PontoGeo()
{
Latitude = ponto.Latitude,
Longitude = ponto.Longitude
})
.ToList()
?? new List<PontoGeo>()
)
.ToList()
?? new List<List<PontoGeo>>(),
VazaoInstantaneaMLs = VazaoInstantaneaMLs,
_estadoLeituraAnterior = _estadoLeituraAnterior,
ValoresLeituras = new List<SensorValoresLeituraModel>(ValoresLeituras),
VazaoMediaMLs = VazaoMediaMLs,
Posicao = Posicao,
TempoAtuado = TempoAtuado,
TempoAtuadoEmMovimento = TempoAtuadoEmMovimento,
AnguloNeutro = AnguloNeutro,
TempoComFluxo = TempoComFluxo,
};
return clone;
}
}
public class AtuadorBombaModel
{
public string ID { get; set; }
public int ID_Num { get; set; }
public bool ComandoAtuar { get; set; }
public double PotenciaSP { get; set; }
public double PressaoSP { get; set; }
public S_Code Componente { get; set; } = S_Code.sBMB;
public int Sensor_ID_Num { get; set; }
public bool Comandar { get; set; }
public bool Mandatorio { get; set; }
public bool Liberado
{
get
{
return !Mandatorio || (Comandar && Inicializado);
}
}
public List<FuncoesPinout> Funcoes { get; set; }
public bool Inicializado { get; set; }
public DateTime UltimaLeitura
{
get
{
return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue;
}
}
public List<SensorValoresLeituraModel> ValoresLeituras { get; set; }
public BombaModoControleModel Controle { get; set; }
public Estado _EstadoLeitura => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.EstadoLeitura)?.atual?.valor ?? Estado.Desligado;
public double _Potencia => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Potencia)?.atual?.valor ?? 0;
public bool _BombaLigada => _Potencia > 0;
public double _PressaoSP => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.PressaoSP)?.atual?.valor ?? 0;
public double _PotenciaSP => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.PotenciaSP)?.atual?.valor ?? 0;
public double _PressaoAtual => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados1 && x.funcao == FuncoesPinout.Pressao)?.atual?.valor ?? 0;
public int _QtdBicosLigados => (int)(ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.QtdPulsos)?.atual?.valor ?? 0);
public double _PotenciaFeedForwared => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.PotenciaSP)?.atual?.valor ?? 0;
public double _PotenciaFinal => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.Potencia)?.atual?.valor ?? 0;
public double _PressaoSensor => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.Pressao)?.atual?.valor ?? 0;
public double _PressaoSPControle => ValoresLeituras?.FirstOrDefault(x => x.posicao == CanMessagePosicaoDados.Dados2 && x.funcao == FuncoesPinout.PressaoSP)?.atual?.valor ?? 0;
private int _BitmapDiagnostico
{
get
{
try
{
var valor = ValoresLeituras?
.FirstOrDefault(x =>
x.posicao == CanMessagePosicaoDados.Dados2 &&
x.funcao == FuncoesPinout.BitmapDiagnostico)
?.atual?.valor;
if (valor == null)
return 0;
return Convert.ToInt32(valor);
}
catch
{
return 0;
}
}
}
private bool TemFlagDiagnostico(int bit)
{
return (_BitmapDiagnostico & (1 << bit)) != 0;
}
public bool _PressaoSPForaDoMapa => TemFlagDiagnostico(0);
public bool _FluxoInsuficiente => TemFlagDiagnostico(1);
public bool _RecuperacaoFluxoAtiva => TemFlagDiagnostico(2);
public bool _CavitacaoSuspeita => TemFlagDiagnostico(3);
public bool _Cavitada => TemFlagDiagnostico(4);
public bool _CorteSegurancaAtivo => TemFlagDiagnostico(5);
public bool _PressaoSPLimitadoPorCapacidade => TemFlagDiagnostico(6);
public int Atuacoes { get; set; }
/// <summary>
/// Tempo em segundos com estado real Ligado.
/// </summary>
public double TempoAtuado { get; set; }
/// <summary>
/// Tempo em segundos com potência real maior que zero.
/// </summary>
public double TempoComPotencia { get; set; }
public double PotenciaAcumuladaPctSeg { get; set; }
public double PotenciaMediaAtiva { get; set; }
public DateTime UltimoComandoEnviado { get; set; } = DateTime.MinValue;
public bool UltimoStatusDesejado { get; set; } = false;
public double UltimaPotenciaDesejada { get; set; } = 0;
public double UltimaPressaoDesejada { get; set; } = 0;
public List<(CanMessagePosicaoDados, byte[])> ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar)
{
List<byte> config1 = new List<byte>()
{
(byte)Componente,
(byte)(conectar ? 1 : 0),
(byte)PressaoSP,
(byte)Sensor_ID_Num,
(byte)(pinout.Pinos.FirstOrDefault(x => x.ComponenteID == ID && x.Funcao == FuncoesPinout.IN1)?.Pino ?? PinoutUtils.PINO_INVALIDO),
(byte)(pinout.Pinos.FirstOrDefault(x => x.ComponenteID == ID && x.Funcao == FuncoesPinout.IN2)?.Pino ?? PinoutUtils.PINO_INVALIDO),
};
return new List<(CanMessagePosicaoDados, byte[])>()
{
(CanMessagePosicaoDados.Config1, config1.ToArray()),
};
}
public (int, CanMessagePosicaoDados, byte[]) ProtocoloComando(CanMessagePosicaoDados comando)
{
byte ConverterPIDParaByte(double valor)
{
// Escala x10:
// 8.0 -> 80
// 0.4 -> 4
// 1.5 -> 15
valor = FuncoesMatematicas.Clamp(valor, 0.0, 25.5);
return (byte)Math.Round(valor * 10.0);
}
switch (comando)
{
case CanMessagePosicaoDados.Command1:
return (
ID_Num,
comando,
new byte[]
{
(byte)(ComandoAtuar ? Estado.Ligado : Estado.Desligado),
(byte)(PotenciaSP),
(byte)(PressaoSP)
}
);
case CanMessagePosicaoDados.Command2:
return (
ID_Num,
comando,
new byte[]
{
(byte)Controle.Modo,
(byte)(Controle.DesligarSemBicosAbertos ? 1 : 0),
(byte)Controle.PotenciaMinPID,
ConverterPIDParaByte(Controle.Kp),
ConverterPIDParaByte(Controle.Ki),
ConverterPIDParaByte(Controle.Kd),
}
);
default:
throw new ArgumentOutOfRangeException(
nameof(comando),
comando,
"Comando CAN não suportado pela bomba pressurizadora.");
}
}
public bool AtingiuPressao(double? pressaoMinima = null)
{
double alvo = pressaoMinima
?? Variaveis.OperacaoEmAndamento?.Parametros?.Controle?.AtuPressaoLinha
?? -1;
return _PressaoAtual >= alvo;
}
public class BombaModoControleModel
{
public ModoControleBomba Modo { get; set; } = ModoControleBomba.OnOff;
public bool DesligarSemBicosAbertos { get; set; } = true;
public double PotenciaMinPID { get; set; } = 40.0;
public double Kp { get; set; } = 0.08;
public double Ki { get; set; } = 0.008;
public double Kd { get; set; } = 0.0;
}
}
public class CalibragemVazao
{
public bool Calibrando { get; set; } = false;
public DateTime IniciadoEm { get; set; }
public double TempoCalibragem { get; set; } = 10000.0;
public double VolumeComputado { get; set; } = 0;
public List<double> PressoesAferidas { get; set; } = new List<double>();
public double MassaInicial { get; set; } = 0.0;
public double MassaFinal { get; set; } = 0.0;
public int QtdPulsos { get; set; } = 0;
public double Periodo { get; set; } = 0.0;
public double PressaoMedia
{
get
{
return PressoesAferidas.Average();
}
}
public double MassaPerdida
{
get
{
return (MassaInicial - MassaFinal) * 1000;
}
}
public double Q
{
get
{
return (double)VolumeComputado / (double)Periodo;
}
}
public double VolumeCalculado { get; set; } = 0.0;
public double d
{
get
{
return (double)MassaPerdida / (double)VolumeComputado;
}
}
public double x
{
get
{
return (double)VolumeComputado / (double)QtdPulsos;
}
}
public double Q2
{
get
{
return (double)x * (double)QtdPulsos;
}
}
}
public class AtuadorFuncoesGerais
{
public static void DesenharAtuadores(object sender, PaintEventArgs e, List<AtuadorBicoLogModel> Bicos, bool BombaLigada, double PressaoLinha, double TempoBombaAtuada)
{
Panel myPanel = (Panel)sender;
// Habilita o double buffering temporariamente
myPanel.GetType().GetProperty("DoubleBuffered",
System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic)
?.SetValue(myPanel, true, null);
Graphics g = e.Graphics;
int QuantidadeBicos = Bicos.Count;
int panelWidth = myPanel.Width;
int circleDiameter = 40; // Diâmetro dos círculos grandes
int smallCircleDiameter = circleDiameter / 2; // Diâmetro dos círculos pequenos
int spaceBetweenCircles = (panelWidth - (circleDiameter * QuantidadeBicos)) / (QuantidadeBicos + 1);
int yPosition = (myPanel.Height - circleDiameter) / 3; // Centralizado verticalmente
for (int i = 0; i < QuantidadeBicos; i++)
{
int xPosition = spaceBetweenCircles + (i * (circleDiameter + spaceBetweenCircles));
// Desenha o contorno do círculo
g.DrawEllipse(Pens.Red, xPosition, yPosition, circleDiameter, circleDiameter);
// Preenche o círculo se o bico estiver aceso
if (Bicos[i].Atuado)
{
g.FillEllipse(Brushes.Red, xPosition, yPosition, circleDiameter, circleDiameter);
}
// Número do bico
string bicoNumero = Bicos[i].ID;
Font font = new Font("Arial", 10, FontStyle.Bold);
SizeF stringSize = g.MeasureString(bicoNumero, font);
float textXPosition = xPosition + (circleDiameter - stringSize.Width) / 2;
float textYPosition = yPosition + (circleDiameter - stringSize.Height) / 2;
g.DrawString(bicoNumero, font, Brushes.Black, textXPosition, textYPosition);
// Média de vazão (dentro do círculo, abaixo do número do bico)
string mediaFluxoTexto = $"{Bicos[i].MediaNivelFluxo:0.0} mL/s";
Font fontMediaFluxo = new Font("Arial", 6, FontStyle.Regular);
SizeF stringSizeMediaFluxo = g.MeasureString(mediaFluxoTexto, fontMediaFluxo);
float textXPositionMediaFluxo = xPosition + circleDiameter + 3;
float textYPositionMediaFluxo = textYPosition + 3; // Abaixo do número do bico
g.DrawString(mediaFluxoTexto, fontMediaFluxo, Brushes.Gray, textXPositionMediaFluxo, textYPositionMediaFluxo);
// Quantidade de atuações (abaixo do círculo)
string bicoAtuacao = Bicos[i].Atuacoes.ToString("000");
Font fontA = new Font("Arial", 8, FontStyle.Bold);
SizeF stringSizeA = g.MeasureString(bicoAtuacao, fontA);
float textXPositionA = xPosition + (circleDiameter - stringSizeA.Width) / 2;
float textYPositionA = circleDiameter + yPosition + 5;
g.DrawString(bicoAtuacao, fontA, Brushes.Black, textXPositionA, textYPositionA);
// Tempo atuado em segundos (acima do círculo)
string tempoAtuadoSegundos = $"{(Bicos[i].TempoAtuado):0.0}s";
Font fontTempo = new Font("Arial", 7, FontStyle.Regular);
SizeF stringSizeTempo = g.MeasureString(tempoAtuadoSegundos, fontTempo);
float textXPositionTempo = xPosition + (circleDiameter - stringSizeTempo.Width) / 2;
float textYPositionTempo = yPosition - stringSizeTempo.Height - 3;
g.DrawString(tempoAtuadoSegundos, fontTempo, Brushes.Gray, textXPositionTempo, textYPositionTempo);
// Círculo pequeno de status
int smallCircleXPosition = Convert.ToInt32(xPosition + circleDiameter - smallCircleDiameter / 1.3);
int smallCircleYPosition = Convert.ToInt32(yPosition + circleDiameter - smallCircleDiameter / 1.3);
Pen smallCirclePen = Pens.Blue;
Brush smallCircleBrush = Bicos[i].StatusBico ? Brushes.Blue : Brushes.Transparent;
g.DrawEllipse(smallCirclePen, smallCircleXPosition, smallCircleYPosition, smallCircleDiameter, smallCircleDiameter);
g.FillEllipse(smallCircleBrush, smallCircleXPosition, smallCircleYPosition, smallCircleDiameter, smallCircleDiameter);
}
// Representação da bomba hidráulica no canto inferior direito
int bombaSize = 20; // Tamanho do círculo principal da bomba
int bombaXPosition = myPanel.Width - bombaSize - 20; // 20px de margem do canto direito
int bombaYPosition = myPanel.Height - bombaSize - 20; // 20px de margem do canto inferior
// Desenha o corpo da bomba (um círculo)
g.DrawEllipse(Pens.Black, bombaXPosition, bombaYPosition, bombaSize, bombaSize);
g.FillEllipse(BombaLigada ? Brushes.Green : Brushes.Gray, bombaXPosition, bombaYPosition, bombaSize, bombaSize);
// Define o centro do círculo para ajudar no desenho das hélices
int centerX = bombaXPosition + bombaSize / 2;
int centerY = bombaYPosition + bombaSize / 2;
int heliceSize = bombaSize - 6; // Ajuste para manter as hélices dentro do círculo
// Desenha hélices no formato de arcos ou semi-círculos dentro do círculo
Pen helicePen = new Pen(Color.Black, 1);
g.DrawArc(helicePen, centerX - heliceSize / 2, centerY - heliceSize / 2, heliceSize, heliceSize, 45, 180);
g.DrawArc(helicePen, centerX - heliceSize / 2, centerY - heliceSize / 2, heliceSize, heliceSize, 225, 180);
// Desenha o "cano" da bomba (retângulo)
int tuboLargura = 5;
int tuboAltura = 15;
int tuboXPosition = bombaXPosition + bombaSize / 2 - tuboLargura / 2;
int tuboYPosition = bombaYPosition - tuboAltura;
g.FillRectangle(Brushes.Black, tuboXPosition, tuboYPosition, tuboLargura, tuboAltura);
// Texto para o tempo de atuação acima da bomba
string tempoAtuacaoTexto = $"{(TempoBombaAtuada):0.0}s";
Font fontTempoAtuacao = new Font("Arial", 6, FontStyle.Regular);
SizeF stringSizeTempoAtuacao = g.MeasureString(tempoAtuacaoTexto, fontTempoAtuacao);
float textXPositionTempoAtuacao = bombaXPosition + bombaSize / 2 - stringSizeTempoAtuacao.Width / 2;
float textYPositionTempoAtuacao = bombaYPosition - stringSizeTempoAtuacao.Height - 16; // 2px acima do círculo
g.DrawString(tempoAtuacaoTexto, fontTempoAtuacao, Brushes.Gray, textXPositionTempoAtuacao, textYPositionTempoAtuacao);
// Texto pequeno para a pressão ao lado da bomba
string pressaoTexto = $"{PressaoLinha:0.0} psi";
Font fontPressao = new Font("Arial", 6, FontStyle.Regular);
SizeF stringSizePressao = g.MeasureString(pressaoTexto, fontPressao);
float textXPositionPressao = bombaXPosition + bombaSize / 2 - stringSizePressao.Width / 2;
float textYPositionPressao = bombaYPosition + bombaSize + 2; // 2px abaixo do círculo
g.DrawString(pressaoTexto, fontPressao, Brushes.Black, textXPositionPressao, textYPositionPressao);
}
}
public class AtuadorMensagemJsonModel
{
public DateTime Momento { get; set; }
public bool Conectado { get; set; }
public T_Code Dispositivo { get; set; }
public int Versao { get; set; }
public string Mod_ID { get; set; }
public int QualidadeWifi { get; set; }
public int LatenciaLoop { get; set; }
public List<AtuadorBicoModel> BicosPulverizadores { get; set; }
public List<AtuadorBombaModel> BombasPressurizadoras { get; set; }
public List<SensorModel> Sensores { get; set; }
public List<SensorServoModel> Servos { get; set; }
public DateTime UltimoComandoRespondido { get; set; }
}
public class AtuHandler : ICanMessageHandler
{
public T_Code Dispositivo { get; set; } = Variaveis.OperacaoEmAndamento.DispAtu?.Dados?.Dispositivo ?? T_Code.Atu;
private double ConverterByteParaDouble(byte[] data, int idx)
{
int raw = (data[idx] << 8) | data[idx + 1];
short _short = (short)raw;
double _value = _short / 100.0;
return _value;
}
private ushort Converter2BytesU16(byte[] data, int idx)
{
return (ushort)((data[idx] << 8) | data[idx + 1]);
}
private double ConverterAngle100(byte[] data, int idx)
{
ushort v = (ushort)((data[idx] << 8) | data[idx + 1]);
return v / 100.0;
}
private S_Code DetectarComponenteDoID_Num(int ID_Num)
{
if (ID_Num == Variaveis.ID_Num_sMOD) return S_Code.sMOD;
if (ID_Num == Variaveis.ID_Num_sTOD) return S_Code.sTOD;
var Disp = Variaveis.OperacaoEmAndamento.DispAtu.Dados;
var sensor = Disp.Sensores.FirstOrDefault(x => x.ID_Num == ID_Num);
var bico = Disp.BicosPulverizadores.FirstOrDefault(x => x.ID_Num == ID_Num);
var bomba = Disp.BombasPressurizadoras.FirstOrDefault(x => x.ID_Num == ID_Num);
return sensor?.Componente ?? bico?.Componente ?? bomba?.Componente ?? S_Code.sVZO;
}
private void AtualizarDados(int CompID_Num, CanMessagePosicaoDados Posicao, S_Code componente, List<dynamic> Parametros)
{
var Dados = Variaveis.OperacaoEmAndamento.DispAtu.Dados;
var sensor = Dados.Sensores.FirstOrDefault(x => x.ID_Num == CompID_Num);
var bomba = Dados.BombasPressurizadoras.FirstOrDefault(x => x.ID_Num == CompID_Num);
var bico = Dados.BicosPulverizadores.FirstOrDefault(x => x.ID_Num == CompID_Num);
List<SensorValoresLeituraModel> valoresLeituras = new List<SensorValoresLeituraModel>();
if (sensor != null) valoresLeituras = sensor.ValoresLeituras;
else if (bomba != null) valoresLeituras = bomba.ValoresLeituras;
else if (bico != null) valoresLeituras = bico.ValoresLeituras;
for (int i = 0; i < valoresLeituras.Where(x => x.posicao == Posicao).Count(); i++)
{
try
{
var valor = valoresLeituras.FirstOrDefault(x => x.posicao == Posicao && x.indice == i);
if (valor != null)
{
var v = Parametros.Count > i ? (Parametros[i]) : -1;
if (v is sbyte || v is byte || v is short || v is ushort || v is int || v is uint || v is long || v is ulong || v is float || v is double || v is decimal)
{
v += valor.offset;
valor.atual.valor = v;
}
else
{
valor.atual.valor = v;
}
if (valor.funcao == FuncoesPinout.Iniciado && valor.posicao == CanMessagePosicaoDados.Status)
{
if (sensor != null)
sensor.Inicializado = v;
if (bomba != null)
bomba.Inicializado = v;
if (bico != null)
bico.Inicializado = v;
}
}
}
catch (Exception ex)
{
Variaveis.MostrarLog(
$"[AtuHandler.AtualizarDados] " +
$"Erro ao atualizar componente={componente}, " +
$"ID_Num={CompID_Num}, " +
$"posicao={Posicao}: {ex}"
);
}
}
}
public void ProcessarMensagem(CanMessage mensagem)
{
CanMessagePosicaoDados posicao = (CanMessagePosicaoDados)mensagem.DataRx[0];
byte ID_Num = mensagem.DataRx[1];
var data = mensagem.DataRx;
S_Code componente = DetectarComponenteDoID_Num(ID_Num);
var Dados = Variaveis.OperacaoEmAndamento.DispAtu.Dados;
//Console.WriteLine($"[ATU] Componente={componente.ToString()}, ID_Num={ID_Num}, Data={FuncoesGlobais.ConverterComandoBytesParaTexto(data)}");
switch (componente)
{
case S_Code.sMOD:
{
switch (posicao)
{
case CanMessagePosicaoDados.Status:
{
T_Code tipoModulo = (T_Code)data[2];
bool conectado = data[3] == 1;
int versao = data[4];
//Console.WriteLine($"[ATU] Resposta {ID_Num} recebida. Tipo={tipoModulo}, Conectado={conectado}, Versão={versao}");
Dados.DadosLeitura.Conectado = conectado;
Dados.DadosLeitura.Dispositivo = tipoModulo;
Dados.DadosLeitura.Versao = versao;
break;
}
}
break;
}
case S_Code.sTOD:
{
switch (posicao)
{
case CanMessagePosicaoDados.DadosAll:
{
double latenciaLoop = ConverterByteParaDouble(data, 2) * 100;
Dados.DadosLeitura.LatenciaLoop = Convert.ToInt32(latenciaLoop);
//Console.WriteLine($"[ATU] Ciclo concluido. Latencia de loop: {latenciaLoop} ms");
break;
}
}
break;
}
case S_Code.sBIC:
{
switch (posicao)
{
case CanMessagePosicaoDados.Status:
{
bool Iniciado = data[2] == 1;
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { Iniciado });
var bico = Dados.BicosPulverizadores.FirstOrDefault(x => x.ID_Num == ID_Num);
if (bico != null && !Iniciado)
bico.AtualizarEstadoTrecho(false);
break;
}
case CanMessagePosicaoDados.Dados1:
{
Estado estado = (Estado)data[2];
int angulo = data[3];
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { estado, angulo });
break;
}
}
break;
}
case S_Code.sFLX:
{
switch (posicao)
{
case CanMessagePosicaoDados.Status:
{
bool Iniciado = data[2] == 1;
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { Iniciado });
break;
}
case CanMessagePosicaoDados.Dados1:
{
int qtdPulsos = data[2];
ushort periodo = Converter2BytesU16(data, 3);
double vazao = ConverterByteParaDouble(data, 5);
//Variaveis.MostrarLog($"[AtuadorModel] Vazao: {vazao}, pulsos: {qtdPulsos}, Periodo: {periodo}");
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { qtdPulsos, periodo, vazao });
break;
}
}
break;
}
case S_Code.sMAS:
{
switch (posicao)
{
case CanMessagePosicaoDados.Status:
{
bool Iniciado = data[2] == 1;
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { Iniciado });
break;
}
case CanMessagePosicaoDados.Dados1:
{
double raw = ConverterByteParaDouble(data, 2);
double massa = ConverterByteParaDouble(data, 4);
//Variaveis.MostrarLog($"[AtuadorModel] Massa: {massa}, Raw: {raw}");
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { raw, massa });
break;
}
}
break;
}
case S_Code.sPRS:
{
switch (posicao)
{
case CanMessagePosicaoDados.Status:
{
bool Iniciado = data[2] == 1;
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { Iniciado });
break;
}
case CanMessagePosicaoDados.Dados1:
{
ushort raw = Converter2BytesU16(data, 2);
double pressao = ConverterByteParaDouble(data, 4);
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { raw, pressao });
break;
}
}
break;
}
case S_Code.sBMB:
{
switch (posicao)
{
case CanMessagePosicaoDados.Status:
{
bool iniciado = data.Length > 2 && data[2] == 1;
bool malhaFechada = data.Length > 3 && data[3] == 1;
AtualizarDados(
ID_Num,
posicao,
componente,
new List<dynamic>() {
iniciado,
malhaFechada,
}
);
var bomba = Dados.BombasPressurizadoras?.FirstOrDefault(x => x.ID_Num == ID_Num);
break;
}
case CanMessagePosicaoDados.Dados1:
{
Estado estado = data.Length > 2 ? (Estado)data[2] : Estado.Desligado;
int potenciaSp = data.Length > 3 ? data[3] : 0;
int percentualPotencia = data.Length > 4 ? data[4] : 0;
int pressaoSp = data.Length > 5 ? data[5] : 0;
int pressaoAtual = data.Length > 6 ? data[6] : 0;
ModoControleBomba modo = data.Length > 7 ? (ModoControleBomba)data[7] : ModoControleBomba.OnOff;
AtualizarDados(
ID_Num,
posicao,
componente,
new List<dynamic>()
{
estado,
potenciaSp,
percentualPotencia,
pressaoSp,
pressaoAtual,
modo
}
);
break;
}
case CanMessagePosicaoDados.Dados2:
{
int qtdBicosLigados = data.Length > 2 ? data[2] : 0;
int potenciaFeedForward = data.Length > 3 ? data[3] : 0;
int potenciaFinal = data.Length > 4 ? data[4] : 0;
int pressaoSensor = data.Length > 5 ? data[5] : 0;
int pressaoSpControle = data.Length > 6 ? data[6] : 0;
byte flags = data.Length > 7 ? data[7] : (byte)0;
AtualizarDados(
ID_Num,
posicao,
componente,
new List<dynamic>()
{
qtdBicosLigados,
potenciaFeedForward,
potenciaFinal,
pressaoSensor,
pressaoSpControle,
flags
}
);
break;
}
}
break;
}
case S_Code.sMAG:
{
switch (posicao)
{
case CanMessagePosicaoDados.Status:
{
bool Iniciado = data[2] == 1;
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { Iniciado });
break;
}
case CanMessagePosicaoDados.Dados1:
{
ushort raw = Converter2BytesU16(data, 2);
double angle = ConverterAngle100(data, 4);
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { raw, angle });
//Console.WriteLine($"[sMAG] {ID_Num}, Raw: {raw}, Angulo: {angle}");
break;
}
}
break;
}
case S_Code.sBOT:
{
switch (posicao)
{
case CanMessagePosicaoDados.Status:
{
bool Iniciado = data[2] == 1;
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { Iniciado });
break;
}
case CanMessagePosicaoDados.Dados1:
{
bool pressionado = data[2] == 1;
bool leitura = data[3] == 1;
bool mudou = data[4] == 1;
AtualizarDados(ID_Num, posicao, componente, new List<dynamic>() { pressionado, leitura, mudou });
break;
}
}
break;
}
}
Dados.DadosLeitura.UltimoComandoRespondido = DateTime.Now;
Variaveis.OperacaoEmAndamento.DispAtu.Dados.AtualizarDadosSensoresExternos(componente, ID_Num);
}
}
}