2024-11-05 10:31:38 +03:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.IO.Ports;
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Windows.Forms;
|
2024-12-16 11:35:20 +03:00
|
|
|
|
using static System.Net.Mime.MediaTypeNames;
|
2024-11-05 10:31:38 +03:00
|
|
|
|
|
|
|
|
|
|
namespace Gidrolock_Modbus_Scanner
|
|
|
|
|
|
{
|
|
|
|
|
|
public static class Modbus
|
|
|
|
|
|
{
|
2024-12-12 15:14:13 +03:00
|
|
|
|
public static SerialPort port = new SerialPort();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public static event EventHandler<ModbusResponseEventArgs> ResponseReceived = delegate { };
|
|
|
|
|
|
|
|
|
|
|
|
public static void Init()
|
|
|
|
|
|
{
|
|
|
|
|
|
port.DataReceived += new System.IO.Ports.SerialDataReceivedEventHandler(PortDataReceived);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-05 10:31:38 +03:00
|
|
|
|
#region Build Message
|
2024-12-17 16:36:42 +03:00
|
|
|
|
public static byte[] BuildReadMessage(byte modbusID, byte functionCode, ushort address, ushort length, ref byte[] message)
|
2024-11-05 10:31:38 +03:00
|
|
|
|
{
|
|
|
|
|
|
//Array to receive CRC bytes:
|
|
|
|
|
|
byte[] CRC = new byte[2];
|
|
|
|
|
|
|
2024-12-06 16:32:10 +03:00
|
|
|
|
message[0] = modbusID;
|
|
|
|
|
|
message[1] = functionCode;
|
|
|
|
|
|
message[2] = (byte)(address >> 8);
|
|
|
|
|
|
message[3] = (byte)address;
|
2024-11-05 16:57:05 +03:00
|
|
|
|
message[4] = (byte)(length >> 8);
|
|
|
|
|
|
message[5] = (byte)length;
|
2024-11-05 10:31:38 +03:00
|
|
|
|
|
|
|
|
|
|
GetCRC(message, ref CRC);
|
|
|
|
|
|
message[message.Length - 2] = CRC[0];
|
|
|
|
|
|
message[message.Length - 1] = CRC[1];
|
2024-12-06 16:32:10 +03:00
|
|
|
|
string msg = ByteArrayToString(message);
|
2024-12-09 16:24:02 +03:00
|
|
|
|
//Console.WriteLine("Message: " + msg);
|
2024-12-06 16:32:10 +03:00
|
|
|
|
return message;
|
2024-11-05 10:31:38 +03:00
|
|
|
|
}
|
2024-12-17 16:36:42 +03:00
|
|
|
|
public static byte[] BuildWriteSingleMessage(byte modbusID, byte functionCode, ushort address, byte[] data)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (functionCode == 0x05 || functionCode == 0x06)
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] _message = new byte[8];
|
|
|
|
|
|
byte[] CRC = new byte[2];
|
|
|
|
|
|
|
|
|
|
|
|
_message[0] = modbusID;
|
|
|
|
|
|
_message[1] = functionCode;
|
|
|
|
|
|
_message[2] = (byte)(address >> 8);
|
|
|
|
|
|
_message[3] = (byte)address;
|
|
|
|
|
|
_message[4] = data[0];
|
|
|
|
|
|
_message[5] = data[1];
|
|
|
|
|
|
GetCRC(_message, ref CRC);
|
|
|
|
|
|
_message[6] = CRC[0];
|
|
|
|
|
|
_message[7] = CRC[1];
|
2024-12-17 16:55:55 +03:00
|
|
|
|
Console.WriteLine("Message: " + ByteArrayToString(_message));
|
2024-12-17 16:36:42 +03:00
|
|
|
|
return _message;
|
|
|
|
|
|
}
|
|
|
|
|
|
else return new byte[1] { 0xFF };
|
|
|
|
|
|
}
|
2024-11-05 10:31:38 +03:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2024-11-11 10:43:58 +03:00
|
|
|
|
#region Read Functions
|
2024-12-06 16:32:10 +03:00
|
|
|
|
public static async Task<bool> ReadRegAsync(SerialPort port, byte slaveID, FunctionCode functionCode, ushort address, ushort length)
|
2024-11-05 10:31:38 +03:00
|
|
|
|
{
|
|
|
|
|
|
//Ensure port is open:
|
|
|
|
|
|
if (port.IsOpen)
|
|
|
|
|
|
{
|
|
|
|
|
|
//Clear in/out buffers:
|
|
|
|
|
|
port.DiscardOutBuffer();
|
|
|
|
|
|
port.DiscardInBuffer();
|
2024-11-05 16:57:05 +03:00
|
|
|
|
|
|
|
|
|
|
//Read functions are always 8 bytes long
|
2024-11-05 10:31:38 +03:00
|
|
|
|
byte[] message = new byte[8];
|
2024-12-17 16:36:42 +03:00
|
|
|
|
|
2024-11-05 10:31:38 +03:00
|
|
|
|
//Build outgoing modbus message:
|
2024-12-17 16:36:42 +03:00
|
|
|
|
BuildReadMessage(slaveID, (byte)functionCode, address, length, ref message);
|
2024-11-05 16:57:05 +03:00
|
|
|
|
|
2024-11-05 10:31:38 +03:00
|
|
|
|
//Send modbus message to Serial Port:
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
await Task.Run(() => { port.Write(message, 0, message.Length); });
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception err)
|
|
|
|
|
|
{
|
2024-12-17 16:36:42 +03:00
|
|
|
|
MessageBox.Show(err.Message, "aeiou");
|
2024-11-05 10:31:38 +03:00
|
|
|
|
port.Close();
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show("Порт не открыт");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
2024-12-17 16:36:42 +03:00
|
|
|
|
|
2024-11-11 10:43:58 +03:00
|
|
|
|
#region Write Single Coil/Register
|
2024-12-17 16:36:42 +03:00
|
|
|
|
public static async Task<bool> WriteSingleAsync(SerialPort port, FunctionCode functionCode, byte slaveID, ushort address, ushort value)
|
2024-11-11 10:43:58 +03:00
|
|
|
|
{
|
2024-12-17 16:36:42 +03:00
|
|
|
|
//Ensure port is open:
|
|
|
|
|
|
if (!port.IsOpen)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
port.Open();
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception err)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show(err.Message);
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
//Clear in/out buffers:
|
|
|
|
|
|
port.DiscardOutBuffer();
|
|
|
|
|
|
port.DiscardInBuffer();
|
|
|
|
|
|
|
|
|
|
|
|
//Build outgoing modbus message:
|
|
|
|
|
|
byte[] _value = BitConverter.GetBytes(value);
|
|
|
|
|
|
Array.Reverse(_value);
|
|
|
|
|
|
|
|
|
|
|
|
byte[] message = BuildWriteSingleMessage(slaveID, (byte)functionCode, address, _value);
|
|
|
|
|
|
|
|
|
|
|
|
//Send modbus message to Serial Port:
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
port.Write(message, 0, message.Length);
|
2024-12-17 16:55:55 +03:00
|
|
|
|
Console.WriteLine("Write message sent successfully");
|
2024-12-17 16:36:42 +03:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception err)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show(err.Message);
|
|
|
|
|
|
port.Close();
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2024-12-17 15:56:05 +03:00
|
|
|
|
|
2024-11-11 10:43:58 +03:00
|
|
|
|
}
|
2024-11-11 10:45:55 +03:00
|
|
|
|
#endregion
|
2024-11-11 10:43:58 +03:00
|
|
|
|
public static bool ParseResponse(byte[] res, ref string verbose)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
int dataLength = (int)res[2];
|
|
|
|
|
|
if (res.Length < dataLength + 4)
|
|
|
|
|
|
{
|
|
|
|
|
|
verbose = "Сообщение устройства не соответствует ожидаемой длине!";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
//TODO: Check CRC
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
catch
|
|
|
|
|
|
{
|
|
|
|
|
|
verbose = "Сообщение устройства не соответствует ожидаемой длине!";
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2024-12-17 16:36:42 +03:00
|
|
|
|
|
2024-11-11 10:43:58 +03:00
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-17 16:36:42 +03:00
|
|
|
|
public static string ByteArrayToString(byte[] bytes, bool cleanEmpty = true)
|
2024-12-12 15:14:13 +03:00
|
|
|
|
{
|
2024-12-17 16:36:42 +03:00
|
|
|
|
byte[] res;
|
|
|
|
|
|
|
|
|
|
|
|
if (cleanEmpty)
|
|
|
|
|
|
res = CleanByteArray(bytes);
|
|
|
|
|
|
else res = bytes;
|
2024-12-12 15:14:13 +03:00
|
|
|
|
|
|
|
|
|
|
string dataString = BitConverter.ToString(res);
|
|
|
|
|
|
string result = "";
|
|
|
|
|
|
for (int i = 0; i < dataString.Length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (dataString[i] == '-')
|
|
|
|
|
|
result += " ";
|
|
|
|
|
|
else result += dataString[i];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
|
}
|
|
|
|
|
|
public static byte[] CleanByteArray(byte[] bytes)
|
2024-11-05 16:57:05 +03:00
|
|
|
|
{
|
|
|
|
|
|
int length = bytes.Length - 1;
|
|
|
|
|
|
// snip off the empty bytes at the end
|
|
|
|
|
|
for (int i = length; i >= 0; i--)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (bytes[i] != 0)
|
|
|
|
|
|
{
|
|
|
|
|
|
length = i + 1;
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
byte[] res = new byte[length];
|
2024-12-12 15:14:13 +03:00
|
|
|
|
for (int i = 0; i < length; i++) { res[i] = bytes[i]; }
|
2024-11-05 16:57:05 +03:00
|
|
|
|
|
2024-12-12 15:14:13 +03:00
|
|
|
|
return res;
|
2024-11-05 16:57:05 +03:00
|
|
|
|
}
|
2024-12-06 16:32:10 +03:00
|
|
|
|
|
2024-12-12 15:14:13 +03:00
|
|
|
|
|
2024-12-06 16:32:10 +03:00
|
|
|
|
#region CRC Computation
|
|
|
|
|
|
static void GetCRC(byte[] message, ref byte[] CRC)
|
|
|
|
|
|
{
|
|
|
|
|
|
//Function expects a modbus message of any length as well as a 2 byte CRC array in which to
|
|
|
|
|
|
//return the CRC values:
|
|
|
|
|
|
|
|
|
|
|
|
ushort CRCFull = 0xFFFF;
|
|
|
|
|
|
byte CRCHigh = 0xFF, CRCLow = 0xFF;
|
|
|
|
|
|
char CRCLSB;
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < (message.Length) - 2; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
CRCFull = (ushort)(CRCFull ^ message[i]);
|
|
|
|
|
|
|
|
|
|
|
|
for (int j = 0; j < 8; j++)
|
|
|
|
|
|
{
|
|
|
|
|
|
CRCLSB = (char)(CRCFull & 0x0001);
|
|
|
|
|
|
CRCFull = (ushort)((CRCFull >> 1) & 0x7FFF);
|
|
|
|
|
|
|
|
|
|
|
|
if (CRCLSB == 1)
|
|
|
|
|
|
CRCFull = (ushort)(CRCFull ^ 0xA001);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
CRC[1] = CRCHigh = (byte)((CRCFull >> 8) & 0xFF);
|
|
|
|
|
|
CRC[0] = CRCLow = (byte)(CRCFull & 0xFF);
|
|
|
|
|
|
}
|
|
|
|
|
|
#endregion
|
2024-12-12 15:14:13 +03:00
|
|
|
|
|
|
|
|
|
|
static void PortDataReceived(object sender, EventArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
byte[] message = new byte[port.BytesToRead];
|
|
|
|
|
|
port.Read(message, 0, 3);
|
|
|
|
|
|
int length = (int)message[2];
|
|
|
|
|
|
for (int i = 0; i < length + 2; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
port.Read(message, i + 3, 1);
|
|
|
|
|
|
}
|
2024-12-16 15:52:01 +03:00
|
|
|
|
|
2024-12-12 15:14:13 +03:00
|
|
|
|
byte[] data = new byte[length];
|
|
|
|
|
|
for (int i = 0; i < length; i++)
|
|
|
|
|
|
{
|
|
|
|
|
|
data[i] = message[i + 3];
|
|
|
|
|
|
}
|
|
|
|
|
|
string dataCleaned = ByteArrayToString(message);
|
|
|
|
|
|
|
|
|
|
|
|
port.DiscardInBuffer();
|
|
|
|
|
|
|
2024-12-16 11:35:20 +03:00
|
|
|
|
ResponseReceived.Invoke(null, new ModbusResponseEventArgs(message, data, Encoding.UTF8.GetString(data)));
|
2024-12-12 15:14:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception err)
|
|
|
|
|
|
{
|
|
|
|
|
|
MessageBox.Show(err.Message);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-11-05 10:31:38 +03:00
|
|
|
|
}
|
2024-12-12 15:14:13 +03:00
|
|
|
|
|
|
|
|
|
|
public class ModbusResponseEventArgs : EventArgs
|
|
|
|
|
|
{
|
2024-12-16 11:35:20 +03:00
|
|
|
|
public byte[] Message { get; set; }
|
|
|
|
|
|
public byte[] Data { get; set; }
|
|
|
|
|
|
public string Text { get; set; }
|
|
|
|
|
|
public ModbusResponseEventArgs(byte[] message, byte[] data, string text)
|
2024-12-12 15:14:13 +03:00
|
|
|
|
{
|
2024-12-16 11:35:20 +03:00
|
|
|
|
this.Message = message;
|
|
|
|
|
|
this.Data = data;
|
|
|
|
|
|
this.Text = text;
|
2024-12-12 15:14:13 +03:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2024-12-06 16:32:10 +03:00
|
|
|
|
}
|