3217 lines
130 KiB
C#
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);
|
|
}
|
|
}
|
|
|
|
|
|
}
|