agrobot_base/AgroBase/AgroBase/Services/ModbusService.cs

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 };
}
}
}