using AgroBase.Services; using AgroBase.Services.Operadores; using System; using System.Collections.Generic; 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 PingsConsiderarConexao { get; set; } = 5; public static int TempoLimiteConexao { get { return PingsConsiderarConexao * (Variaveis.OperacaoEmAndamento?.DispSen?._TaxaAmostragem ?? 1000); } } public bool Conectado { get { return DadosLeitura.UltimoComandoRespondido.AddMilliseconds(PingsConsiderarConexao * _TaxaAmostragem) > DateTime.Now; } } public bool ConexaoAtiva { get { return HealthWorkerService.ModulosSaude.Any(x => x.modulo == Dispositivo && x.status == StatusModulo.Operante); } } public bool AtuPronto { get; set; } = false; public static PinoutDataModel Pinout { get; set; } public static List Funcoes { get; set; } = new List() { FuncoesPinout.SDA, FuncoesPinout.SCL, }; //public int QuantidadeBicos { get; set; } = 4; public double DensidadeHerbicida { get; set; } = 1.0; public List BicosPulverizadores { get; set; } = new List(); public List BombasPressurizadoras { get; set; } = new List(); public List Sensores { get; set; } = new List(); public string ScriptDeteccaoErvas { get; set; } = PythonService.ScriptWeedDetector; public AsyncTaskTimerModel tmrRegistrador; private int _TempoReconexaoComponentes = 5; private int _VezesLeituraReconexao = 0; public AtuadorMensagemJsonModel DadosLeitura { get; set; } = new AtuadorMensagemJsonModel(); public AtuHandler _AtuHandler = new AtuHandler(); #region MASSA public double _MassaRef { get; set; } = 0.8; public double _MassaLeituraRef { get; set; } = 40876.37; public double _MassaLeituraExtra { get; set; } = 1895800; // 2015000.0; public double MassaReservatorio { get { return Sensores.FirstOrDefault(x => x.Componente == S_Code.sMAS)?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Massa)?.atual?.valor ?? 0; } } public double VolumeReservatorio { get { return MassaReservatorio / DensidadeHerbicida; } } public double PercentualReservatorio { get { return VolumeReservatorio / (Variaveis.OperacaoEmAndamento.Parametros.CapacidadeReservatorio ?? VariaveisEquipamento.CapacidadeReservatorio) * 100.0; } } #endregion #region VAZAO public double VolumeVazaoAferido { get; set; } = 0; public double _FluxoMlRef { get; set; } = 600; public double _FluxoPulsoRef { get; set; } = 538; public double VazaoFluxoMLpSInstantanea { get { return Sensores.FirstOrDefault(x => x.Componente == S_Code.sFLX)?.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Fluxo)?.atual?.valor ?? 0; } } private double _somaVazao; // Acumula a soma da vazão para calcular a média private int _contadorLeituras; // Conta o número de leituras feitas private DateTime _ultimoRegistroVazao; // Armazena o horário da última atualização public double VazaoFluxoMLpSMedia { get; set; } public double VolumeVazaoML { get; set; } public DateTime InicioAtuacao { get; set; } = DateTime.MinValue; public double TempoAtuado { get; set; } = 0; public void AtualizaTempoAtuadoGeral() { bool algumBicoAtivo = BicosPulverizadores.Any(x => x.Inicializado && x.ComandoAtuar); if (algumBicoAtivo) { if (InicioAtuacao == DateTime.MinValue) { InicioAtuacao = DateTime.Now; } else { TempoAtuado += (DateTime.Now - InicioAtuacao).TotalMilliseconds; InicioAtuacao = DateTime.Now; } } else if (InicioAtuacao != DateTime.MinValue) { TempoAtuado += (DateTime.Now - InicioAtuacao).TotalMilliseconds; InicioAtuacao = DateTime.MinValue; } AtualizaPercentualErvas(); AtualizaVazaoEVolume(); } public void AtualizaPercentualErvas() { double tempoTotalOperacao = Variaveis.OperacaoEmAndamento.Parametros.DadosLeitura.TempoDecorridoSeg * 1000.0; if (tempoTotalOperacao > 0) { var percentual = Math.Round((TempoAtuado / tempoTotalOperacao) * 100.0, 2); PercentualErvasTerreno = percentual; } else { PercentualErvasTerreno = 0; } } public void AtualizaVazaoEVolume() { // Captura a vazão instantânea double vazaoInstantanea = VazaoFluxoMLpSInstantanea; if (_ultimoRegistroVazao == DateTime.MinValue) { _ultimoRegistroVazao = DateTime.Now; return; } // Calcula o tempo decorrido desde a última atualização (em segundos) double intervaloSegundos = (DateTime.Now - _ultimoRegistroVazao).TotalSeconds; _ultimoRegistroVazao = DateTime.Now; if (intervaloSegundos > 0) { // Atualiza o volume total vazado VolumeVazaoML += vazaoInstantanea * intervaloSegundos; // Acumula a vazão para o cálculo da média _somaVazao += vazaoInstantanea; _contadorLeituras++; } VazaoFluxoMLpSMedia = _contadorLeituras > 0 ? _somaVazao / _contadorLeituras : 0; var BicosAtuados = BicosPulverizadores.Where(x => x.Inicializado && x.ComandoAtuar).ToList(); double vazaoInstantaneaBico = vazaoInstantanea / ((double)BicosAtuados.Count()); foreach (var Bico in BicosAtuados) { Bico.MediaNivelFluxo = vazaoInstantaneaBico; } } #endregion #region PRESSAO 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 { get { return Sensores.FirstOrDefault(x => x.Componente == S_Code.sPRS)?.ValoresLeituras.FirstOrDefault(x => x.funcao == FuncoesPinout.Pressao)?.atual?.valor ?? 0; } } public double PressaoLinhaCalc { get { //double _LeituraPressao = ((FirmwareSensorPressao)Sensores.FirstOrDefault(x => x.Componente == S_Code.sPRS)?.Leitura)?.Leitura ?? 0; double _LeituraPressao = 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; public static AtuadorModel CarregarParametrosIniciais() { AtuadorModel Atuador = new AtuadorModel() { _EnderecoCAN_Tx = 0x12, _EnderecoCAN_Rx = 0x12, _TaxaAmostragem = 1000, RequisitarDados = true, BicosPulverizadores = new List(), BombasPressurizadoras = new List() { new AtuadorBombaModel() { ID = "BOMBA", ID_Num = 51, Componente = S_Code.sBMB, Comandar = true, Mandatorio = true, Funcoes = new List() { FuncoesPinout.ENA, FuncoesPinout.CS, FuncoesPinout.INA, FuncoesPinout.INB, FuncoesPinout.PWM, }, ValoresLeituras = new List() { new SensorValoresLeituraModel() { funcao = FuncoesPinout.Iniciado, posicao = CanMessagePosicaoDados.Status, indice = 0, }, new SensorValoresLeituraModel() { funcao = FuncoesPinout.EstadoLeitura, posicao = CanMessagePosicaoDados.Dados1, indice = 0, }, new SensorValoresLeituraModel() { funcao = FuncoesPinout.Potencia, posicao = CanMessagePosicaoDados.Dados1, indice = 1, }, new SensorValoresLeituraModel() { funcao = FuncoesPinout.PressaoSP, posicao = CanMessagePosicaoDados.Dados1, indice = 2, }, new SensorValoresLeituraModel() { funcao = FuncoesPinout.Pressao, posicao = CanMessagePosicaoDados.Dados1, indice = 3, } } } }, Sensores = new List() { new SensorModel() { ID = "FLXLN", ID_Num = 61, Descricao = "Fluxo na linha principal", Componente = S_Code.sFLX, Aferir = true, Mandatorio = true, Funcoes = new List() { FuncoesPinout.Fluxo, }, Parametros = new List(), ValoresLeituras = new List() { new SensorValoresLeituraModel() { funcao = FuncoesPinout.Iniciado, posicao = CanMessagePosicaoDados.Status, indice = 0, }, new SensorValoresLeituraModel() { funcao = FuncoesPinout.Fluxo, posicao = CanMessagePosicaoDados.Dados1, indice = 0, analise_health = true, maximo = 20, minimo = 0, nominal = 0, unidade_medida = "mL/s" } } }, new SensorModel() { ID = "MASRS", ID_Num = 62, Descricao = "Massa do reservatório de herbicida", Componente = S_Code.sMAS, Aferir = true, Mandatorio = true, Funcoes = new List() { FuncoesPinout.DT, FuncoesPinout.SCK, }, Parametros = new List() { new SensorParametroModel() { Descricao = "Offset", Enviar = true, Escalar = false, QtdBytes = 2, Valor = "1895800" } }, ValoresLeituras = new List() { new SensorValoresLeituraModel() { funcao = FuncoesPinout.Iniciado, posicao = CanMessagePosicaoDados.Status, indice = 0, }, new SensorValoresLeituraModel() { funcao = FuncoesPinout.Massa, posicao = CanMessagePosicaoDados.Dados1, indice = 0, analise_health = true, maximo = 35, minimo = 0, nominal = 30, unidade_medida = "Kg" } } }, new SensorModel() { ID = "PRSLN", ID_Num = 63, Descricao = "Pressão na linha principal", Componente = S_Code.sPRS, Aferir = true, Mandatorio = true, Funcoes = new List() { FuncoesPinout.Pressao, }, Parametros = new List() { 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() { new SensorValoresLeituraModel() { funcao = FuncoesPinout.Iniciado, posicao = CanMessagePosicaoDados.Status, indice = 0, }, new SensorValoresLeituraModel() { funcao = FuncoesPinout.Pressao, posicao = CanMessagePosicaoDados.Dados1, indice = 0, analise_health = true, maximo = 120, minimo = 0, nominal = 85, unidade_medida = "PSI" } } }, }, }; for (int i = 0; i < Variaveis.OperacaoEmAndamento.Parametros.QtdBicos; i++) { int posicao = (i + 1); Atuador.BicosPulverizadores.Add(new AtuadorBicoModel() { ID = "B" + posicao.ToString("00"), ID_Num = 70 + posicao, Componente = S_Code.sBIC, Posicao = posicao, Atuacoes = 0, Inicializado = false, TempoAtuado = 0, AnguloControle = 0, AnguloAbertura = 110, Comandar = true, Mandatorio = true, ComandoAtuar = false, AtuacaoNivelAlto = true, Funcoes = new List() { FuncoesPinout.Solenoide, }, MediaNivelFluxo = 0, ValoresLeituras = new List() { new SensorValoresLeituraModel() { funcao = FuncoesPinout.Iniciado, posicao = CanMessagePosicaoDados.Status, indice = 0, }, new SensorValoresLeituraModel() { funcao = FuncoesPinout.EstadoLeitura, posicao = CanMessagePosicaoDados.Dados1, indice = 0, } } }); } return Atuador; } public void LimparDados(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); } public void ReiniciarLeituras(AtuadorModel Dados) { Dados.Sensores.ForEach(x => { x.ValoresLeituras.ForEach(l => { l.atual = new CanCommandFreqModel(); }); }); Dados.BicosPulverizadores.ForEach(x => { x.ComandoAtuar = false; x.Atuacoes = 0; x.TempoAtuado = 0; x.InicioAtuacao = DateTime.MinValue; x.ValoresLeituras.ForEach(l => { l.atual = new CanCommandFreqModel(); }); }); Dados.BombasPressurizadoras.ForEach(x => { x.ComandoAtuar = false; x.ValoresLeituras.ForEach(l => { l.atual = new CanCommandFreqModel(); }); }); Dados.VolumeVazaoAferido = 0; Dados._somaVazao = 0; Dados._contadorLeituras = 0; Dados._ultimoRegistroVazao = DateTime.MinValue; Dados.VolumeVazaoML = 0; Dados.VazaoFluxoMLpSMedia = 0; Dados.TempoAtuado = 0; Dados.InicioAtuacao = DateTime.MinValue; } public async Task VerificaDispositivoConectado() { if (!CanManager.CanService.IsConnected) return false; if (Variaveis.OperacaoEmAndamento.DispAtu == null) return false; CanManager.CanService.RegistrarHandler(_EnderecoCAN_Rx, _AtuHandler); RequisitarDadosModulo(Variaveis.ID_Num_sMOD, CanMessagePosicaoDados.Status, CanMessagePosicaoDados.Status, true); DateTime Inicio = DateTime.Now; bool Respondido() => Variaveis.OperacaoEmAndamento.DispAtu.Dados.DadosLeitura.UltimoComandoRespondido > Inicio; await FuncoesGlobais.AguardarCondicaoAsync(Respondido, 500, 50); if (Conectado) { DefinirDispositivo(); Variaveis.OperacaoEmAndamento.DispAtu?.EnviarProtocolosConfiguracao(); } return Conectado; } private void DefinirDispositivo() { 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 }); Variaveis.OperacaoEmAndamento.ModsCalibragem[Dispositivo] = new Dictionary(); } } public void RequisitarDadosModulo(int ID_Num, CanMessagePosicaoDados posicaoTx, CanMessagePosicaoDados posicaoRx, bool aguardaResposta) { Variaveis.OperacaoEmAndamento.DispAtu?.AdicionarMensagemFila(ID_Num, posicaoTx, posicaoRx, null, aguardaResposta); } 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)(Conectar ? 1 : 0), (byte)(_TaxaAmostragem / 10), (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() { if (Variaveis.Fechando) { return; } tmrRegistrador.SetInterval(_TaxaAmostragem); if (SerialService.DispositivosMapeados.Any(x => x.Dispositivo == T_Code.Atu) && (_VezesLeituraReconexao >= _TempoReconexaoComponentes)) { Variaveis.OperacaoEmAndamento.DispAtu?.EnviarProtocolosConfiguracao(); _VezesLeituraReconexao = 0; } else { _VezesLeituraReconexao++; } if (DadosLeitura.Conectado) { if (RequisitarDados) { RequisitarDadosModulo(Variaveis.ID_Num_sTOD, CanMessagePosicaoDados.DadosAll, CanMessagePosicaoDados.DadosAll, true); } VerificaControleLeitura(); } AtualizaComponentesIniciados(); } public (bool, List) StatusModulosATU() { var Bicos = BicosPulverizadores.Where(x => x.Comandar).ToList(); var BicosComFalha = Bicos.Where(x => !x.Liberado).ToList(); bool _bicosOperantes = !BicosComFalha.Any(); var _bomba = BombasPressurizadoras.FirstOrDefault(); var _sensorFluxo = Sensores.FirstOrDefault(x => x.Componente == S_Code.sFLX); bool _sensorFluxoOperante = !_sensorFluxo?.Liberado ?? false; var _sensorMassa = Sensores.FirstOrDefault(x => x.Componente == S_Code.sMAS); bool _sensorMassaOperante = !_sensorMassa?.Liberado ?? false; var _sensorPressao = Sensores.FirstOrDefault(x => x.Componente == S_Code.sPRS); bool _sensorPressaoOperante = !_sensorPressao?.Liberado ?? false; List ModsComFalha = BicosComFalha.Select(x => x.ID).ToList(); bool _bombaOperante = _bomba?.Liberado ?? false; if (!_bombaOperante) { ModsComFalha.Add(_bomba?.ID); } if (!_sensorFluxoOperante) { ModsComFalha.Add(_sensorFluxo.ID); } if (!_sensorMassaOperante) { ModsComFalha.Add(_sensorMassa.ID); } if (!_sensorPressaoOperante) { ModsComFalha.Add(_sensorPressao.ID); } bool _atuadorOperante = _bombaOperante && _bicosOperantes && _sensorPressaoOperante && _sensorMassaOperante && _sensorFluxoOperante; return (_atuadorOperante, ModsComFalha); } private void VerificaControleLeitura() { if (true) // !frmDiagnosticos.Diagnosticando { foreach (var bico in BicosPulverizadores.Where(x => x.Comandar && x.Inicializado)) { Estado leituraEstado = (Estado?)(bico.ValoresLeituras.FirstOrDefault(x => x.funcao == FuncoesPinout.EstadoLeitura)?.atual?.valor) ?? Estado.Desligado; Estado comandoEstado = bico.ComandoAtuar ? Estado.Ligado : Estado.Desligado; if (leituraEstado != comandoEstado) { GeneralJoystick.EnviarComandoAtuador(bico.Componente, bico.ID, Status: bico.ComandoAtuar); } } var bomba = BombasPressurizadoras.FirstOrDefault(x => x.Comandar && x.Inicializado); if (bomba != null) { bool leituraEstado = (bomba.ValoresLeituras?.FirstOrDefault(x => x.funcao == FuncoesPinout.Potencia)?.atual?.valor ?? 0) > 0; // Desliga a bomba caso esteja ligada e o nivel de herbicida caiu if (PercentualReservatorio < VariaveisEquipamento.PercentualReservatorioMin && leituraEstado == true) { bomba.ComandoAtuar = false; } // Religa a bomba caso esteja desligada, o nivel de herbicida esta estavel e o pulverizador automatico esta ligado else if (PercentualReservatorio >= VariaveisEquipamento.PercentualReservatorioMin && leituraEstado == false && Variaveis.OperacaoEmAndamento.Parametros.Controle.PulverizadorAutomatico) { bomba.ComandoAtuar = true; } if (bomba.ComandoAtuar != leituraEstado) { GeneralJoystick.EnviarComandoAtuador(bomba.Componente, bomba.ID, Status: bomba.ComandoAtuar); } } } } 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; } foreach (var sensor in Sensores.Where(x => x.Inicializado && x.UltimaLeitura.AddMilliseconds(TempoLimiteConexao) < Agora)) { sensor.Inicializado = false; } } public async Task RealizarTestesIniciais(bool forcar) { var calAtu = Variaveis.OperacaoEmAndamento.ModsCalibragem[T_Code.Atu]; // Ligar bomba foreach (var Bomba in BombasPressurizadoras.Where(x => x.Comandar)) { calAtu.TryGetValue(Bomba.ID, out var cal); if (!forcar && cal && calAtu[Bomba.ID]) continue; calAtu[Bomba.ID] = false; // Sem herbicida no reservatorio, bomba nao precisa ligar if (!Bomba.Inicializado || PercentualReservatorio < VariaveisEquipamento.PercentualReservatorioMin) { break; } Variaveis.OperacaoEmAndamento.Controle.BombaAtuada = true; GeneralJoystick.EnviarComandoAtuador(Bomba.Componente, Bomba.ID, Status: true); await FuncoesGlobais.AguardarCondicaoAsync(Bomba.AtingiuPressao, 3000, 200); bool BombaOk = Bomba.AtingiuPressao(); if (BombaOk) { calAtu[Bomba.ID] = true; } else { foreach (var Bico in BicosPulverizadores.Where(x => x.Comandar && x.Inicializado)) { GeneralJoystick.EnviarComandoAtuador(Bico.Componente, Bico.ID, Status: true); } await Task.Delay(3000); foreach (var Bico in BicosPulverizadores.Where(x => x.Comandar && x.Inicializado)) { GeneralJoystick.EnviarComandoAtuador(Bico.Componente, Bico.ID, Status: false); } await FuncoesGlobais.AguardarCondicaoAsync(Bomba.AtingiuPressao, 4000, 200); BombaOk = Bomba.AtingiuPressao(); if (BombaOk) { calAtu[Bomba.ID] = true; } } } // 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; calAtu[Bico.ID] = false; if (Bico.Inicializado) { GeneralJoystick.EnviarComandoAtuador(Bico.Componente, Bico.ID, Status: true); await Task.Delay(200); GeneralJoystick.EnviarComandoAtuador(Bico.Componente, Bico.ID, Status: false); calAtu[Bico.ID] = true; } } await Task.Delay(1000); bool sucesso = calAtu.All(x => x.Value); return sucesso; } public List RealizarCalibragemAsync(bool forcar) { List tasks = new List(); var DispAtu = Variaveis.OperacaoEmAndamento.DispAtu; if (DispAtu != null) { tasks.Add(DispAtu.Dados.RealizarTestesIniciais(forcar).ContinueWith(t => { if (t.IsFaulted) Console.WriteLine($"Erro no referenciamento do módulo {Modulo_ID}: {t.Exception?.InnerException?.Message}"); if (t.IsCompleted) { var calAtu = Variaveis.OperacaoEmAndamento.ModsCalibragem[T_Code.Atu]; var v = calAtu.Select(x => $"{x.Key} " + (x.Value ? "Sim" : "Não")); Console.WriteLine($"Task de referenciamento do módulo {Modulo_ID} finalizada: " + string.Join(", ", v)); } })); } return tasks; } } 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 double AnguloControle { get; set; } public double AnguloAbertura { get; set; } private bool _atuado; public bool ComandoAtuar { get { return _atuado; } set { _atuado = value; AtualizaTempoAtuado(); } } public bool Comandar { get; set; } public bool Mandatorio { get; set; } public bool Liberado { get { return !Mandatorio || (Comandar && Inicializado); } } public List Funcoes { get; set; } public double MediaNivelFluxo { 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 ValoresLeituras { get; set; } = new List(); 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), (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) } ); } public int Atuacoes { get; set; } = 0; public DateTime InicioAtuacao { get; set; } = DateTime.MinValue; public double TempoAtuado { get; set; } = 0; private void AtualizaTempoAtuado() { if (!Variaveis.OperacaoEmAndamento.Iniciado) return; if (_atuado) { if (InicioAtuacao == DateTime.MinValue) { InicioAtuacao = DateTime.Now; } else { TempoAtuado += (DateTime.Now - InicioAtuacao).TotalMilliseconds; InicioAtuacao = DateTime.Now; } } else if (InicioAtuacao != DateTime.MinValue) { TempoAtuado += (DateTime.Now - InicioAtuacao).TotalMilliseconds; InicioAtuacao = DateTime.MinValue; } if (Variaveis.OperacaoEmAndamento.DispAtu != null) { Variaveis.OperacaoEmAndamento.DispAtu.Dados.AtualizaTempoAtuadoGeral(); } } public AtuadorBicoModel Clone() { AtuadorBicoModel clone = new AtuadorBicoModel() { Comandar = Comandar, AtuacaoNivelAlto = AtuacaoNivelAlto, Atuacoes = Atuacoes, ComandoAtuar = ComandoAtuar, AnguloAbertura = AnguloAbertura, AnguloControle = AnguloControle, Inicializado = Inicializado, Mandatorio = Mandatorio, Componente = Componente, Funcoes = Funcoes, ID = ID, ID_Num = ID_Num, InicioAtuacao = InicioAtuacao, ValoresLeituras = new List(ValoresLeituras), MediaNivelFluxo = MediaNivelFluxo, Posicao = Posicao, TempoAtuado = TempoAtuado }; return clone; } } public class AtuadorBombaModel { public string ID { get; set; } public int ID_Num { get; set; } public bool ComandoAtuar { get; set; } public S_Code Componente { get; set; } = S_Code.sBMB; public bool Comandar { get; set; } public bool Mandatorio { get; set; } public bool Liberado { get { return !Mandatorio || (Comandar && Inicializado); } } public List Funcoes { get; set; } = new List(); public bool Inicializado { get; set; } public DateTime UltimaLeitura { get { return ValoresLeituras.OrderByDescending(x => x.atual.lidoEm).FirstOrDefault()?.atual?.lidoEm ?? DateTime.MinValue; } } public List ValoresLeituras { get; set; } = new List(); public List<(CanMessagePosicaoDados, byte[])> ProtocoloConfiguracao(PinoutDataModel pinout, bool conectar) { List config1 = new List() { (byte)Componente, (byte)(conectar ? 1 : 0), (byte)Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuPressaoLinha, (byte)(pinout.Pinos.FirstOrDefault(x => x.ComponenteID == ID && x.Funcao == FuncoesPinout.ENA)?.Pino ?? PinoutUtils.PINO_INVALIDO), (byte)(pinout.Pinos.FirstOrDefault(x => x.Funcao == FuncoesPinout.CS)?.Pino ?? PinoutUtils.PINO_INVALIDO), }; List config2 = new List() { (byte)Componente, (byte)(pinout.Pinos.FirstOrDefault(x => x.Funcao == FuncoesPinout.INA)?.Pino ?? PinoutUtils.PINO_INVALIDO), (byte)(pinout.Pinos.FirstOrDefault(x => x.Funcao == FuncoesPinout.INB)?.Pino ?? PinoutUtils.PINO_INVALIDO), (byte)(pinout.Pinos.FirstOrDefault(x => x.Funcao == FuncoesPinout.PWM)?.Pino ?? PinoutUtils.PINO_INVALIDO), }; return new List<(CanMessagePosicaoDados, byte[])>() { (CanMessagePosicaoDados.Config1, config1.ToArray()), (CanMessagePosicaoDados.Config2, config2.ToArray()), }; } public (int, CanMessagePosicaoDados, byte[]) ProtocoloComando() { return ( ID_Num, CanMessagePosicaoDados.Command1, new byte[] { (byte)(ComandoAtuar ? Estado.Ligado : Estado.Desligado), (byte)(Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuPressaoLinha) } ); } public bool AtingiuPressao() => Variaveis.OperacaoEmAndamento.DispAtu.Dados.PressaoLinha >= Variaveis.OperacaoEmAndamento.Parametros.Controle.AtuPressaoLinha; } 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 PressoesAferidas { get; set; } = new List(); 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 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 / 1000.0):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 / 1000.0):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 FirmwareBombaPressurizadora : IFirmwareSensorBase { public DateTime Momento { get; set; } public string ID { get; set; } public int ID_Num { get; set; } public bool Iniciado { get; set; } public CanCommandFreqModel Estado { get; set; } = new CanCommandFreqModel(); public CanCommandFreqModel Potencia { get; set; } = new CanCommandFreqModel(); public CanCommandFreqModel PressaoSP { get; set; } = new CanCommandFreqModel(); public CanCommandFreqModel Pressao { get; set; } = new CanCommandFreqModel(); } 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 BicosPulverizadores { get; set; } = new List(); public List BombasPressurizadoras { get; set; } = new List(); public List Sensores { get; set; } = new List(); 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 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 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 valoresLeituras = new List(); 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 (Posicao == CanMessagePosicaoDados.Status) { if (sensor != null) sensor.Inicializado = v; if (bomba != null) bomba.Inicializado = v; if (bico != null) bico.Inicializado = v; } } } catch (Exception 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() { Iniciado }); break; } case CanMessagePosicaoDados.Dados1: { Estado estado = (Estado)data[2]; AtualizarDados(ID_Num, posicao, componente, new List() { estado }); break; } } break; } case S_Code.sFLX: { switch (posicao) { case CanMessagePosicaoDados.Status: { bool Iniciado = data[2] == 1; AtualizarDados(ID_Num, posicao, componente, new List() { Iniciado }); break; } case CanMessagePosicaoDados.Dados1: { double vazao = ConverterByteParaDouble(data, 2); AtualizarDados(ID_Num, posicao, componente, new List() { vazao }); break; } } break; } case S_Code.sMAS: { switch (posicao) { case CanMessagePosicaoDados.Status: { bool Iniciado = data[2] == 1; AtualizarDados(ID_Num, posicao, componente, new List() { Iniciado }); break; } case CanMessagePosicaoDados.Dados1: { double massa = ConverterByteParaDouble(data, 2); AtualizarDados(ID_Num, posicao, componente, new List() { massa }); break; } } break; } case S_Code.sPRS: { switch (posicao) { case CanMessagePosicaoDados.Status: { bool Iniciado = data[2] == 1; AtualizarDados(ID_Num, posicao, componente, new List() { Iniciado }); break; } case CanMessagePosicaoDados.Dados1: { double pressao = ConverterByteParaDouble(data, 2); AtualizarDados(ID_Num, posicao, componente, new List() { pressao }); break; } } break; } case S_Code.sBMB: { switch (posicao) { case CanMessagePosicaoDados.Status: { bool Iniciado = data[2] == 1; AtualizarDados(ID_Num, posicao, componente, new List() { Iniciado }); break; } case CanMessagePosicaoDados.Dados1: { Estado estado = (Estado)data[2]; int percentualPotencia = data[3]; int pressaoSp = data[4]; int pressaoAtual = data[5]; AtualizarDados(ID_Num, posicao, componente, new List() { estado, percentualPotencia, pressaoSp, pressaoAtual }); break; } } break; } } Dados.DadosLeitura.UltimoComandoRespondido = DateTime.Now; } } }