using AgroBase.Models; using CefSharp.WinForms; using CefSharp; using System; using System.Collections.Generic; using System.Globalization; using System.IO.Ports; using System.Linq; using System.Windows.Forms; using Newtonsoft.Json; using Emgu.CV.CvEnum; using Emgu.CV.Aruco; using static AgroBase.Services.SerialService; using System.Threading.Tasks; using AgroBase.Forms; namespace AgroBase.Services { public class PZEMService { public static bool Iniciado = false; public static SerialPort PortaPZEM = null; public static PZEMModel UltimaLeitura = new PZEMModel(); public static PZEMModel PenultimaLeitura = new PZEMModel(); public static double TensaoMinima { get; set; } = 30; public static double TensaoMaxima { get; set; } = 42; private static byte slaveAddress = 0x01; private static Dictionary CodigosFuncoes = new Dictionary() { { "LeituraParametro", 0x03 }, { "AquisicaoDados", 0x04 }, { "GravacaoParametro", 0x06 }, { "ResetEnergia", 0x42 }, { "ErroGravaParametro", 0x86 }, { "Calibrar", 0x41 }, { "ErroCalibrar", 0xC1 }, }; public static Dictionary Parametros = new Dictionary() { { "AlarmeTensaoMaxima", 0x0000 }, { "AlarmeTensaoMinima", 0x0001 }, { "EnderecoRTU", 0x0002 }, { "EscalaCorrenteAtual", 0x0003 }, }; public static void AtualizarPortaCOM(SerialPort Porta) { try { Porta.BaudRate = 9600; Porta.DataReceived -= PortaPZEM_DataReceived; Porta.DataReceived += PortaPZEM_DataReceived; if (!Porta.IsOpen) { Porta.Open(); } //var Comando = ComandoCalibragem(); var Comando = ComandoGetParametro(Parametros.First().Value); string comandoHex = BitConverter.ToString(Comando).Replace("-", ""); EnviarComando(Porta, Comando); } catch (Exception ex) { Porta.Close(); } } public static void EnviarComando(SerialPort _Porta, byte[] Comando) { if (_Porta != null && _Porta.IsOpen) { _Porta.Write(Comando, 0, Comando.Length); } } public static void PortaPZEM_DataReceived(object sender, SerialDataReceivedEventArgs e) { if (Iniciado) { if (PortaPZEM.IsOpen) { PenultimaLeitura = UltimaLeitura; int bytesToRead = PortaPZEM.BytesToRead; byte[] buffer = new byte[bytesToRead]; PortaPZEM.Read(buffer, 0, bytesToRead); DecifrarRespostaAquisicao(buffer); } } else { var Porta = ((SerialPort)sender); int bytesToRead = Porta.BytesToRead; byte[] buffer = new byte[bytesToRead]; Porta.Read(buffer, 0, bytesToRead); var Res = DecifrarRespostaAquisicao(buffer); if (Res != null) { PortaPZEM = Porta; Iniciado = true; SerialService.DispositivosMapeados.Add(new DispositivoDetalhesModel() { PortaCOM = Porta.PortName, Dispositivo = Enuns.T_Code.Pzm, Erro = !Iniciado, Versao = 17 }); ReceberDados().ConfigureAwait(false); } else { Porta.DataReceived -= PortaPZEM_DataReceived; Porta.Close(); } } } private static async Task ReceberDados() { EnviarComando(PortaPZEM, ComandoAquisicaoDados()); await Task.Delay(5000); return await ReceberDados(); } #region Comandos public static byte[] ComandoAquisicaoDados() { byte functionCode = CodigosFuncoes["AquisicaoDados"]; ushort startAddress = 0x0000; ushort numOfRegisters = 0x0008; List command = new List(); command.Add(slaveAddress); command.Add(functionCode); command.AddRange(BitConverter.GetBytes(startAddress).Reverse()); // Big endian command.AddRange(BitConverter.GetBytes(numOfRegisters).Reverse()); // Big endian byte[] crc = Crc16(command.ToArray()); // Supondo que você tenha a implementação do CRC16 command.AddRange(crc); return command.ToArray(); } public static byte[] ComandoSetParametro(byte Parametro, float valor) { // Convertendo o valor para o formato correto (LSB = 0.01) int valorInt = (int)(valor * 100); // Convertendo para inteiro conforme a resolução byte functionCode = CodigosFuncoes["GravacaoParametro"]; ushort registerAddress = Parametro; // Construindo o comando List command = new List(); command.Add(slaveAddress); command.Add(functionCode); command.AddRange(BitConverter.GetBytes(registerAddress).Reverse()); // Little endian command.AddRange(BitConverter.GetBytes((ushort)valorInt).Reverse()); // Little endian byte[] crc = Crc16(command.ToArray()); // Supondo que você tenha a implementação do CRC16 command.AddRange(crc); return command.ToArray(); } public static byte[] ComandoGetParametro(byte Parametro) { byte functionCode = CodigosFuncoes["LeituraParametro"]; ushort registerAddress = Parametro; ushort numberOfRegisters = 0x0001; // Lendo apenas um registro // Construindo o comando List command = new List(); command.Add(slaveAddress); command.Add(functionCode); command.AddRange(BitConverter.GetBytes(registerAddress).Reverse()); // Big endian command.AddRange(BitConverter.GetBytes(numberOfRegisters).Reverse()); // Big endian byte[] crc = Crc16(command.ToArray()); // Supondo que você tenha a implementação do CRC16 command.AddRange(crc); return command.ToArray(); } public static byte[] ComandoResetEnergia() { // Código de função específico para reset de energia byte functionCode = CodigosFuncoes["ResetEnergia"]; // Construindo o comando List command = new List(); command.Add(slaveAddress); command.Add(functionCode); // Adicionando o CRC ao comando byte[] crc = Crc16(command.ToArray()); // Supondo que você tenha a implementação do CRC16 command.AddRange(crc); return command.ToArray(); } public static byte[] ComandoCalibragem() { byte functionCode = CodigosFuncoes["Calibrar"]; // Bytes fixos para o comando de calibragem byte[] bytesComando = new byte[] { 0xF8, functionCode, 0x37, 0x21 }; // Calculando o CRC para os bytes do comando byte[] crc = Crc16(bytesComando); // Adicionando o CRC ao comando List comandoCalibragem = new List(bytesComando); comandoCalibragem.AddRange(crc); return comandoCalibragem.ToArray(); } #endregion public static byte[] Crc16(byte[] data) { ushort crc = 0xFFFF; foreach (byte pos in data) { crc ^= pos; for (int i = 0; i < 8; i++) { if ((crc & 1) != 0) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return new byte[] { (byte)crc, (byte)(crc >> 8) }; } public static bool VerificarCrc(string respostaHex) { byte[] respostaBytes = HexStringToByteArray(respostaHex); byte[] crcRecebido = new byte[] { respostaBytes[respostaBytes.Length - 2], respostaBytes[respostaBytes.Length - 1] }; byte[] dados = new byte[respostaBytes.Length - 2]; Array.Copy(respostaBytes, 0, dados, 0, dados.Length); byte[] crcCalculado = Crc16(dados); return crcRecebido[0] == crcCalculado[0] && crcRecebido[1] == crcCalculado[1]; } public static byte[] HexStringToByteArray(string hex) { int NumberChars = hex.Length; byte[] bytes = new byte[NumberChars / 2]; for (int i = 0; i < NumberChars; i += 2) bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); return bytes; } public static PZEMModel DecifrarRespostaAquisicao(byte[] resposta) { try { /*if (!VerificarCrc(resposta)) { Console.Write("CRC inválido. A resposta pode estar corrompida."); return null; }*/ if (resposta[1] == CodigosFuncoes["AquisicaoDados"]) { int numBytes = resposta[2]; if (numBytes == 16) // Se o número de bytes de dados for 16 { PZEMModel modelo = new PZEMModel() { DataHora = DateTime.Now, }; var tensao_raw = "0x" + resposta[3].ToString("X") + resposta[4].ToString("X"); modelo.Tensao = Convert.ToInt32(tensao_raw, 16) * 0.02; // 0.01; var corrente_raw = "0x" + resposta[5].ToString("X") + resposta[6].ToString("X"); modelo.Corrente = Convert.ToInt32(corrente_raw, 16) * 0.002; // 0.0015; var potencia_raw = "0x" + resposta[7].ToString("X") + resposta[8].ToString("X") + resposta[9].ToString("X") + resposta[10].ToString("X"); modelo.Potencia = Convert.ToInt32(potencia_raw, 16) * 0.0001; // 0.1; var energia_raw = "0x" + resposta[11].ToString("X") + resposta[12].ToString("X") + resposta[13].ToString("X") + resposta[14].ToString("X"); modelo.Energia = Convert.ToInt32(energia_raw, 16) * 1.0; var almMax_raw = "0x" + resposta[15].ToString("X") + resposta[16].ToString("X"); modelo.AlarmeTensaoMax = almMax_raw == "0xFFFF"; var almMin_raw = "0x" + resposta[17].ToString("X") + resposta[18].ToString("X"); modelo.AlarmeTensaoMin = almMin_raw == "0xFFFF"; double percent = FuncoesGlobais.Map(modelo.Tensao, TensaoMinima, TensaoMaxima, 0, 100); Variaveis.OperacaoEmAndamento.Sensoriamento.PorcentagemBateria = percent; frmInstancial.frmAcompanhamento.AtualizarSensores("Bateria: " + modelo.Tensao.ToString("0.00") + " V - " + modelo.Corrente.ToString("0.00") + " A - " + modelo.Potencia.ToString("0.00") + " W"); UltimaLeitura = modelo; Console.WriteLine("PZEM Tensão: " + modelo.Tensao.ToString("0.00") + " - Corrente: " + modelo.Corrente.ToString("0.00")) ; return modelo; } else { Console.WriteLine("Número de bytes de dados não esperado."); } } else if (resposta[1] == CodigosFuncoes["LeituraParametro"]) { var parametro_raw = "0x" + resposta[3].ToString("X") + resposta[4].ToString("X"); double Valor = Convert.ToInt16(parametro_raw, 16) * 0.01; MessageBox.Show("Valor do parâmetro: " + Valor); } else if (resposta[1] == CodigosFuncoes["GravacaoParametro"]) { MessageBox.Show("Parâmetro gravado com sucesso!"); } else if (resposta[1] == CodigosFuncoes["ErroGravaParametro"]) { MessageBox.Show("Erro ao gravar parâmetro!"); } else if (resposta[1] == CodigosFuncoes["Calibrar"]) { MessageBox.Show("Calibragem do PZEM-017 finalizada com sucesso!"); } else if (resposta[1] == CodigosFuncoes["ErroCalibrar"]) { MessageBox.Show("Erro ao calibrar o PZEM-017!"); } else { Console.WriteLine("Resposta inválida ou código de função incorreto."); } return new PZEMModel(); } catch (Exception ex) { } return null; } } }