473 lines
16 KiB
C#
473 lines
16 KiB
C#
using AgroBase.Forms;
|
|
using AgroBase.Models;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.IO.Ports;
|
|
using System.Linq;
|
|
using System.Threading.Tasks;
|
|
using static AgroBase.Models.Enums;
|
|
|
|
namespace AgroBase.Services
|
|
{
|
|
public class ModbusService
|
|
{
|
|
public static readonly object _lock = new object();
|
|
public static int TaxaAmostragem { get; set; } = 500;
|
|
public static bool Verificando { get; set; } = false;
|
|
public static bool EnvioLiberado { get; set; } = true;
|
|
|
|
public static SerialPort _PortaModbus;
|
|
public static bool Iniciado
|
|
{
|
|
get
|
|
{
|
|
if (!SerialService.DispositivosMapeados.Any(x => SerialService.DispositivosModbus.Contains(x.Dispositivo)))
|
|
{
|
|
return false;
|
|
}
|
|
if (_PortaModbus != null && !_PortaModbus.IsOpen)
|
|
{
|
|
try
|
|
{
|
|
_PortaModbus.Open();
|
|
}
|
|
catch
|
|
{
|
|
_PortaModbus = null;
|
|
}
|
|
}
|
|
return _PortaModbus != null && _PortaModbus.IsOpen;
|
|
}
|
|
}
|
|
|
|
public static List<ModbusComandoModel> FilaComandos = new List<ModbusComandoModel>();
|
|
private static readonly object FilaLock = new object();
|
|
private static AsyncTaskTimerModel tmrLoopComandos;
|
|
private static AsyncTaskTimerModel tmrReceberDados;
|
|
|
|
|
|
public static void IniciarRotinas()
|
|
{
|
|
PararRotinas();
|
|
|
|
tmrLoopComandos = new AsyncTaskTimerModel("tmrLoopComandos", tmrLoopComandos_Tick, 100);
|
|
tmrLoopComandos.Start();
|
|
|
|
tmrReceberDados = new AsyncTaskTimerModel("tmrReceberDados", tmrReceberDados_Tick, TaxaAmostragem);
|
|
tmrReceberDados.Start();
|
|
}
|
|
|
|
public static void PararRotinas()
|
|
{
|
|
tmrLoopComandos?.Dispose();
|
|
tmrReceberDados?.Dispose();
|
|
}
|
|
|
|
public static void DefinirPortaCOM(SerialPort Porta)
|
|
{
|
|
if (!Iniciado)
|
|
{
|
|
Porta.Close();
|
|
}
|
|
|
|
if (_PortaModbus == null || !Iniciado)
|
|
{
|
|
lock (_lock)
|
|
{
|
|
_PortaModbus = new SerialPort();
|
|
_PortaModbus.BaudRate = 9600;
|
|
_PortaModbus.PortName = Porta.PortName;
|
|
}
|
|
}
|
|
|
|
if (_PortaModbus != null && !_PortaModbus.IsOpen)
|
|
{
|
|
_PortaModbus.Open();
|
|
}
|
|
|
|
IniciarRotinas();
|
|
}
|
|
|
|
public static void LimparDadosPorta()
|
|
{
|
|
_PortaModbus.Close();
|
|
_PortaModbus = null;
|
|
|
|
PararRotinas();
|
|
}
|
|
|
|
private static void DescartarDadosBuffer()
|
|
{
|
|
if (_PortaModbus != null)
|
|
{
|
|
_PortaModbus.DiscardInBuffer();
|
|
_PortaModbus.DiscardOutBuffer();
|
|
}
|
|
}
|
|
|
|
private static async Task tmrReceberDados_Tick()
|
|
{
|
|
if (Iniciado)
|
|
{
|
|
if (PZEMService.Iniciado && !PZEMService.ModoLeitura)
|
|
{
|
|
AdicionarComandoNaFila(new ModbusComandoModel()
|
|
{
|
|
Dispositivo = T_Code.Pzm,
|
|
Endereco = PZEMService.slaveAddress,
|
|
Comando = PZEMService.ComandoAquisicaoDados(),
|
|
Verificacao = false,
|
|
TamanhoEsperado = 16,
|
|
TimeoutResposta = 1000,
|
|
});
|
|
}
|
|
if (WT901CService.Iniciado)
|
|
{
|
|
AdicionarComandoNaFila(new ModbusComandoModel()
|
|
{
|
|
Dispositivo = T_Code.Wit,
|
|
Endereco = WT901CService.slaveAddress,
|
|
Comando = WT901CService.ComandoAquisicaoDados(),
|
|
Verificacao = false,
|
|
TamanhoEsperado = 101,
|
|
TimeoutResposta = 1000,
|
|
});
|
|
}
|
|
if (UltrasonicA05Service.Iniciado)
|
|
{
|
|
AdicionarComandoNaFila(new ModbusComandoModel()
|
|
{
|
|
Dispositivo = T_Code.A05,
|
|
Endereco = UltrasonicA05Service.slaveAddress,
|
|
Comando = UltrasonicA05Service.ComandoAquisicaoDados(),
|
|
Verificacao = false,
|
|
TamanhoEsperado = 13,
|
|
TimeoutResposta = 1000,
|
|
});
|
|
}
|
|
}
|
|
|
|
tmrReceberDados.SetInterval(TaxaAmostragem);
|
|
}
|
|
|
|
private static async Task tmrLoopComandos_Tick()
|
|
{
|
|
if (EnvioLiberado && !Verificando && !frmInstancial.Fechando)
|
|
{
|
|
EnvioLiberado = false;
|
|
|
|
try
|
|
{
|
|
List<ModbusComandoModel> comandosParaEnviar;
|
|
|
|
lock (FilaLock)
|
|
{
|
|
comandosParaEnviar = FilaComandos.Where(x => !x.Enviado).OrderByDescending(x => x.Verificacao).ThenBy(x => x.Momento).ToList();
|
|
}
|
|
|
|
foreach (var Comando in comandosParaEnviar)
|
|
{
|
|
await EnviarComando(Comando);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RegisrarErrosComunicacao(T_Code.Mbs, 0, $"Erro ao processar fila de comandos. Erro={ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
EnvioLiberado = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
private static void RegisrarErrosComunicacao(T_Code dispositivo, byte addr, string mensagem)
|
|
{
|
|
var Caminho = Variaveis.CaminhoLogsDispositivos;
|
|
if (!Directory.Exists(Caminho))
|
|
{
|
|
Directory.CreateDirectory(Caminho);
|
|
}
|
|
|
|
var Arquivo = dispositivo.ToString() + "_err.txt";
|
|
Caminho += "/" + Arquivo;
|
|
|
|
mensagem = DateTime.Now.ToString("dd/MM/yyyy HH:mm:ss:fff") + " - Porta " + (_PortaModbus?.PortName ?? "COM") + " - " + addr + ": " + mensagem + Environment.NewLine;
|
|
|
|
if (!File.Exists(Caminho))
|
|
{
|
|
File.WriteAllText(Caminho, mensagem);
|
|
}
|
|
else
|
|
{
|
|
File.AppendAllText(Caminho, mensagem);
|
|
}
|
|
}
|
|
|
|
|
|
public static void AdicionarComandoNaFila(ModbusComandoModel Comando)
|
|
{
|
|
lock (FilaLock)
|
|
{
|
|
if (!FilaComandos.Any(x => x.Dispositivo == Comando.Dispositivo && x.Comando.SequenceEqual(Comando.Comando) && !x.Enviado))
|
|
{
|
|
FilaComandos.Add(Comando);
|
|
}
|
|
}
|
|
}
|
|
|
|
public static async Task EnviarComando(ModbusComandoModel Comando)
|
|
{
|
|
if (_PortaModbus != null && _PortaModbus.IsOpen)
|
|
{
|
|
try
|
|
{
|
|
//await Task.Delay(100);
|
|
DescartarDadosBuffer();
|
|
|
|
Comando.ErroEnvio = !SerialService.EnviarDadosPortaSerial(_PortaModbus, Comando.Comando, 0, Comando.Comando.Length);
|
|
|
|
Comando.Enviado = true;
|
|
|
|
if (!Comando.ErroEnvio)
|
|
{
|
|
Comando.Resposta = await SerialService.LerDadosDaPortaSerialAsync(_PortaModbus, Comando.TamanhoEsperado, Comando.TimeoutResposta);
|
|
}
|
|
|
|
Comando.RespondidoEm = DateTime.Now;
|
|
|
|
ProcessarResposta(Comando);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
RegisrarErrosComunicacao(Comando.Dispositivo, Comando.Endereco, $"Erro ao processar fila de comandos. Erro={ex.Message}");
|
|
}
|
|
finally
|
|
{
|
|
DescartarDadosBuffer();
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void ProcessarResposta(ModbusComandoModel Comando)
|
|
{
|
|
bool sucesso = false;
|
|
|
|
switch (Comando.Dispositivo)
|
|
{
|
|
case T_Code.Pzm:
|
|
{
|
|
sucesso = PZEMService.ProcessarResposta(Comando.Resposta);
|
|
break;
|
|
}
|
|
case T_Code.Wit:
|
|
{
|
|
sucesso = WT901CService.ProcessarResposta(Comando.Resposta, Comando.Comando);
|
|
break;
|
|
}
|
|
case T_Code.A05:
|
|
{
|
|
sucesso = UltrasonicA05Service.DecifrarRespostaAquisicao(Comando.Resposta);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
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 VerifyChecksum(byte[] bytes)
|
|
{
|
|
if (bytes.Length < 2) return false;
|
|
|
|
// Extrai o CRC recebido dos últimos dois bytes da mensagem
|
|
byte[] receivedCrc = new byte[] { bytes[bytes.Length - 2], bytes[bytes.Length - 1] };
|
|
|
|
// Extrai os dados da mensagem sem o CRC
|
|
byte[] data = new byte[bytes.Length - 2];
|
|
Array.Copy(bytes, data, bytes.Length - 2);
|
|
|
|
// Calcula o CRC dos dados
|
|
byte[] computedCrc = Crc16(data);
|
|
|
|
// Verifica se o CRC calculado é igual ao CRC recebido
|
|
return receivedCrc[0] == computedCrc[0] && receivedCrc[1] == computedCrc[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 int ByteArrayToDecimal(byte[] bytes)
|
|
{
|
|
if (bytes == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(bytes));
|
|
}
|
|
|
|
switch (bytes.Length)
|
|
{
|
|
case 2:
|
|
// Converter 2 bytes para short e depois para decimal
|
|
int shortValue = BitConverter.ToInt32(bytes, 0);
|
|
return shortValue;
|
|
|
|
case 4:
|
|
// Converter 4 bytes para int e depois para decimal
|
|
int intValue = BitConverter.ToInt32(bytes, 0);
|
|
return intValue;
|
|
|
|
default:
|
|
throw new ArgumentException("O array de bytes deve ter 2 ou 4 elementos.");
|
|
}
|
|
}
|
|
|
|
public static byte[] ComandoSetParametro(byte slaveAddress, ModbusParametrosModel Parametro, float valor)
|
|
{
|
|
Parametro.UltimaRequisicao = DateTime.Now;
|
|
|
|
// Convertendo o valor para o formato correto (LSB = 0.01)
|
|
int valorInt = (int)(valor * Parametro.Escala); // Convertendo para inteiro conforme a resolução
|
|
byte functionCode = 0x06;
|
|
ushort registerAddress = Parametro.Registro;
|
|
|
|
// Construindo o comando
|
|
List<byte> command = new List<byte>();
|
|
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 slaveAddress, ModbusParametrosModel Parametro)
|
|
{
|
|
Parametro.UltimaRequisicao = DateTime.Now;
|
|
|
|
byte functionCode = 0x03;
|
|
ushort registerAddress = Parametro.Registro;
|
|
ushort numberOfRegisters = Parametro.N_Registros; // Lendo apenas um registro
|
|
|
|
// Construindo o comando
|
|
List<byte> command = new List<byte>();
|
|
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 bool FindModbus(byte[] sendByte, byte[] returnByte, out byte[] modbus)
|
|
{
|
|
|
|
int height = sendByte[4];
|
|
int low = sendByte[5];
|
|
|
|
// 得到长度
|
|
int len = 5 + (height << 8 | low) * 2;
|
|
|
|
// 如果没有返回结果,或者返回结果根本不够长
|
|
if (returnByte == null || returnByte.Length < len)
|
|
{
|
|
modbus = new byte[0];
|
|
return false;
|
|
}
|
|
|
|
// 遍历返回结果查找
|
|
for (int i = 0; i <= returnByte.Length - len; i++)
|
|
{
|
|
|
|
byte rAddr = returnByte[i];
|
|
byte mark = 0x03;
|
|
|
|
byte[] cCrc = GetCrc16(returnByte.Skip(i).Take(len - 2).ToArray());
|
|
byte rCrcH = returnByte[i + len - 2];
|
|
byte rCrcL = returnByte[i + len - 1];
|
|
// 如果全部通过
|
|
if (sendByte[0] == rAddr && mark == sendByte[1] && rCrcH == cCrc[0] && rCrcL == cCrc[1])
|
|
{
|
|
modbus = returnByte.Skip(i).Take(len).ToArray();
|
|
return true;
|
|
}
|
|
}
|
|
|
|
modbus = new byte[0];
|
|
return false;
|
|
}
|
|
|
|
private static byte[] GetCrc16(byte[] bytes)
|
|
{
|
|
byte crcRegister_H = 0xFF, crcRegister_L = 0xFF;// 预置一个值为 0xFFFF 的 16 位寄存器
|
|
|
|
byte polynomialCode_H = 0xA0, polynomialCode_L = 0x01;// 多项式码 0xA001
|
|
|
|
for (int i = 0; i < bytes.Length; i++)
|
|
{
|
|
crcRegister_L = (byte)(crcRegister_L ^ bytes[i]);
|
|
|
|
for (int j = 0; j < 8; j++)
|
|
{
|
|
byte tempCRC_H = crcRegister_H;
|
|
byte tempCRC_L = crcRegister_L;
|
|
|
|
crcRegister_H = (byte)(crcRegister_H >> 1);
|
|
crcRegister_L = (byte)(crcRegister_L >> 1);
|
|
// 高位右移前最后 1 位应该是低位右移后的第 1 位:如果高位最后一位为 1 则低位右移后前面补 1
|
|
if ((tempCRC_H & 0x01) == 0x01)
|
|
{
|
|
crcRegister_L = (byte)(crcRegister_L | 0x80);
|
|
}
|
|
|
|
if ((tempCRC_L & 0x01) == 0x01)
|
|
{
|
|
crcRegister_H = (byte)(crcRegister_H ^ polynomialCode_H);
|
|
crcRegister_L = (byte)(crcRegister_L ^ polynomialCode_L);
|
|
}
|
|
}
|
|
}
|
|
|
|
return new byte[] { crcRegister_L, crcRegister_H };
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|