using System; using System.Collections.Generic; using System.IO.Ports; using System.Linq; using System.Text; using System.Threading; using System.Threading.Tasks; namespace AvaloniaApplication1.ViewModels { public class ModBusMaster { public byte slaveId { get; set; } = 0x01; // Slave ID (your ESP slave ID) //byte functionCode = 0x05; // Function code for Write Single Coil //byte coilAddressHigh = 0x00; // Coil address (MSB) //byte coilAddressLow = 0x00; // Coil address (LSB) //byte coilValueHigh = 0x00; // Coil value (0xFF00 to turn on, 0x0000 to turn off) //byte coilValueLow = 0x00; // Function to read coils public async Task ReadCoils(SerialPort port, ushort startAddress, ushort numberOfCoils) { try { byte functionCode = 0x01; // Function code for Read Coils byte[] frame = new byte[6]; // Request frame size (without CRC) // Construct the request frame frame[0] = slaveId; // Slave address frame[1] = functionCode; // Function code (0x01 for Read Coils) frame[2] = (byte)(startAddress >> 8); // Start address high byte frame[3] = (byte)(startAddress & 0xFF); // Start address low byte frame[4] = (byte)(numberOfCoils >> 8); // Number of coils high byte frame[5] = (byte)(numberOfCoils & 0xFF); // Number of coils low byte // Calculate CRC and append it to the frame byte[] crc = CalculateCRC(frame); byte[] fullFrame = new byte[frame.Length + crc.Length]; Array.Copy(frame, fullFrame, frame.Length); Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length); // Send the frame over the serial port port.DiscardOutBuffer(); port.DiscardInBuffer(); port.Write(fullFrame, 0, fullFrame.Length); Thread.Sleep(500); Console.WriteLine("Read Coils request sent."); // Buffer for the response (modbus slave response size can vary) byte[] response = new byte[256]; int bytesRead = port.Read(response, 0, response.Length); Console.WriteLine("Response Coils sent."); // Ensure that the response is valid (check slave address and function code) if (response[0] != slaveId || response[1] != functionCode) { Console.WriteLine("Invalid response or error in slave response."); return null; } // Calculate the number of bytes based on the number of coils int expectedByteCount = (numberOfCoils + 7) / 8; if (response[2] != expectedByteCount) { Console.WriteLine("Incorrect byte count in the response."); return null; } // The response bytes are in bit form, and we need to extract them bool[] coilStates = new bool[numberOfCoils]; // Array to store the coil states int byteIndex = 3; // Start reading from byte 3 (after slave ID and function code) byte coilByte = response[byteIndex]; // The byte representing the coil states // Loop over all coils and check the corresponding bit for (int coilIndex = 0; coilIndex < numberOfCoils; coilIndex++) { // Check the state of the coil by checking the corresponding bit in coilByte coilStates[coilIndex] = coilByte == 1 ? true : false; /*(coilByte & (1 << (7 - coilIndex))) != 0;*/ } if (coilStates==null) { Console.WriteLine("status wael: null"); } else { Console.WriteLine($"status: {coilStates[0]}"); } return coilStates; } catch (Exception ex) { Console.WriteLine($"error wael: {ex.Message}"); return null; } } // Function to write a single coil public async Task WriteSingleCoil(ushort coilAddress, bool state) { byte functionCode = 0x05; byte[] frame = new byte[6]; frame[0] = slaveId; // Slave address frame[1] = functionCode; // Function code frame[2] = (byte)(coilAddress >> 8); // Coil address high byte frame[3] = (byte)(coilAddress & 0xFF); // Coil address low byte frame[4] = (byte)(state ? 0xFF : 0x00); // Coil value (ON=0xFF, OFF=0x00) frame[5] = (byte)(0x00); // Coil value (ON=0xFF, OFF=0x00) // Calculate CRC and append it byte[] crc = CalculateCRC(frame); byte[] fullFrame = new byte[frame.Length + crc.Length]; Array.Copy(frame, fullFrame, frame.Length); Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length); return fullFrame; } // Function to write multiple coils public byte[] WriteMultipleCoils( ushort startAddress, bool[] coilValues) { byte functionCode = 0x0F; int byteCount = (coilValues.Length + 7) / 8; // Calculate the number of bytes needed byte[] frame = new byte[5 + byteCount]; // Basic frame without CRC frame[0] = slaveId; // Slave address frame[1] = functionCode; // Function code frame[2] = (byte)(startAddress >> 8); // Start address high byte frame[3] = (byte)(startAddress & 0xFF); // Start address low byte frame[4] = (byte)(coilValues.Length); // Number of coils frame[5] = (byte)byteCount; // Number of bytes to follow (coil values) for (int i = 0; i < coilValues.Length; i++) { int byteIndex = 5 + 1 + (i / 8); // Start at byte 6, accounting for byte count if (coilValues[i]) { frame[byteIndex] |= (byte)(1 << (i % 8)); // Set the bit corresponding to the coil } } // Calculate CRC and append it byte[] crc = CalculateCRC(frame); byte[] fullFrame = new byte[frame.Length + crc.Length]; Array.Copy(frame, fullFrame, frame.Length); Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length); return fullFrame; } // Function to write multiple registers public async Task WriteSingleRegister(ushort startAddress, int value) { byte functionCode = 0x06; // Function code for writing a single register byte[] frame = new byte[6]; // Basic frame without CRC frame[0] = slaveId; // Slave address frame[1] = functionCode; // Function code frame[2] = (byte)(startAddress >> 8); // Start address high byte frame[3] = (byte)(startAddress & 0xFF); // Start address low byte frame[4] = (byte)(value >> 8); // Register value high byte frame[5] = (byte)(value & 0xFF); // Register value low byte // Calculate CRC and append it byte[] crc = CalculateCRC(frame); byte[] fullFrame = new byte[frame.Length + crc.Length]; Array.Copy(frame, fullFrame, frame.Length); Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length); return fullFrame; } public async Task WriteSingleRegister(ushort startAddress, ushort value) { byte functionCode = 0x06; // Function code for writing a single register byte[] frame = new byte[6]; // Basic frame without CRC frame[0] = slaveId; // Slave address frame[1] = functionCode; // Function code frame[2] = (byte)(startAddress >> 8); // Start address high byte frame[3] = (byte)(startAddress & 0xFF); // Start address low byte frame[4] = (byte)(value >> 8); // Register value high byte frame[5] = (byte)(value & 0xFF); // Register value low byte // Calculate CRC and append it byte[] crc = CalculateCRC(frame); byte[] fullFrame = new byte[frame.Length + crc.Length]; Array.Copy(frame, fullFrame, frame.Length); Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length); return fullFrame; } public async Task ReadHoldingRegister(ushort startAddress, ushort numberOfRegisters) { byte functionCode = 0x03; // Function code for reading holding registers byte[] frame = new byte[6]; // Basic frame without CRC frame[0] = slaveId; // Slave address frame[1] = functionCode; // Function code frame[2] = (byte)(startAddress >> 8); // Start address high byte frame[3] = (byte)(startAddress & 0xFF); // Start address low byte frame[4] = (byte)(numberOfRegisters >> 8); // Number of registers high byte frame[5] = (byte)(numberOfRegisters & 0xFF); // Number of registers low byte // Calculate CRC and append it byte[] crc = CalculateCRC(frame); byte[] fullFrame = new byte[frame.Length + crc.Length]; Array.Copy(frame, fullFrame, frame.Length); Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length); return fullFrame; } public async Task ReadInputRegisters(ushort startAddress, ushort numberOfRegisters) { byte functionCode = 0x04; // Function code for reading input registers byte[] frame = new byte[6]; // Basic frame without CRC frame[0] = slaveId; // Slave address frame[1] = functionCode; // Function code frame[2] = (byte)(startAddress >> 8); // Start address high byte frame[3] = (byte)(startAddress & 0xFF); // Start address low byte frame[4] = (byte)(numberOfRegisters >> 8); // Number of registers high byte frame[5] = (byte)(numberOfRegisters & 0xFF); // Number of registers low byte // Calculate CRC and append it byte[] crc = CalculateCRC(frame); byte[] fullFrame = new byte[frame.Length + crc.Length]; Array.Copy(frame, fullFrame, frame.Length); Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length); return fullFrame; } public async Task WriteMultipleRegisters(ushort startAddress, float[] values) { byte functionCode = 0x10; // Function code for writing multiple registers byte byteCount = (byte)(values.Length * 2); // Total number of bytes for values byte[] frame = new byte[7 + byteCount]; // Frame without CRC frame[0] = slaveId; // Slave address frame[1] = functionCode; // Function code frame[2] = (byte)(startAddress >> 8); // Start address high byte frame[3] = (byte)(startAddress & 0xFF); // Start address low byte frame[4] = (byte)(values.Length >> 8); // Number of registers high byte frame[5] = (byte)(values.Length & 0xFF); // Number of registers low byte frame[6] = byteCount; // Byte count for (int i = 0; i < values.Length; i++) { short val = unchecked((short)values[i]); frame[7 + i * 2] = (byte)(val >> 8); // Register value high byte frame[8 + i * 2] = (byte)(val & 0xFF); // Register value low byte1 } // Calculate CRC and append it byte[] crc = CalculateCRC(frame); byte[] fullFrame = new byte[frame.Length + crc.Length]; Array.Copy(frame, fullFrame, frame.Length); Array.Copy(crc, 0, fullFrame, frame.Length, crc.Length); return fullFrame; } // Function to calculate CRC16 for Modbus RTU frame public byte[] CalculateCRC(byte[] data) { ushort crc = 0xFFFF; foreach (byte byteData in data) { crc ^= byteData; for (int i = 8; i > 0; i--) { if ((crc & 0x0001) == 0x0001) { crc >>= 1; crc ^= 0xA001; } else { crc >>= 1; } } } return new byte[] { (byte)(crc & 0xFF), (byte)((crc >> 8) & 0xFF) }; } } }