├── UModbusTCPExample.zip ├── README.md ├── UModbusTCPHelpers.cs ├── Editor └── UModbusTCPEditor.cs └── UModbusTCP.cs /UModbusTCPExample.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nodlag/umodbustcp/HEAD/UModbusTCPExample.zip -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UModbusTCP 2 | UModbusTCP implements a modbus TCP master driver for Unity3D. 3 | 4 | Created by Javier Garrido Galdon @nodlag 5 | 6 | UModbusTCP implements a modbus TCP master driver for Unity. 7 | 8 | I know that exist some libraries to connect modbus TCP with your C# software. I tried some drivers like EasyModbusTCP and another paid solutions but neither of this drivers runs fine in Unity3D. 9 | 10 | For this reason I wrote this driver that works fine in Unity3d. 11 | 12 | You can use completely free. 13 | 14 | https://www.youtube.com/watch?v=hr6H9A9E8HA 15 | 16 | Thanks & enjoy. 17 | -------------------------------------------------------------------------------- /UModbusTCPHelpers.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | public static class UModbusTCPHelpers { 5 | 6 | public static byte[] GetBytesOfInt(int _iValue) { 7 | //byte[] oResult = BitConverter.GetBytes(_iValue); 8 | byte[] oResult = new byte[2]; 9 | oResult[1] = (byte)(_iValue >> 8); 10 | oResult[0] = (byte)_iValue; 11 | if(BitConverter.IsLittleEndian) { 12 | Array.Reverse(oResult); 13 | } 14 | return oResult; 15 | } 16 | 17 | public static byte[] GetBytesOfBool(bool _bValue) { 18 | if(!_bValue) { 19 | byte[] oResult = { 00, 00 }; 20 | return oResult; 21 | } else { 22 | byte[] oResult = { 00, 01 }; 23 | return oResult; 24 | } 25 | } 26 | 27 | public static int[] GetIntsOfBytes(byte[] _oArray) { 28 | if(BitConverter.IsLittleEndian) { 29 | Array.Reverse(_oArray); 30 | } 31 | List oListResult = new List(); 32 | if(_oArray.Length < 2) { 33 | int iValue = _oArray[0]; 34 | oListResult.Add(iValue); 35 | } else { 36 | for(int i = 0; i < _oArray.Length; i += 2) { 37 | int iValue = _oArray[i + 1] << 8 | _oArray[i]; 38 | oListResult.Add(iValue); 39 | } 40 | } 41 | oListResult.Reverse(); 42 | return oListResult.ToArray(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /Editor/UModbusTCPEditor.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | using System.Collections; 4 | 5 | // Custom Editor using SerializedProperties. 6 | [CustomEditor(typeof(UModbusTCP))] 7 | public class UModbusTCPEditor : Editor { 8 | 9 | Color COLOR_CONNECTED = new Color(0.0f, 1.0f, 0.0f, 0.4f); 10 | Color COLOR_NOT_CONNECTED = new Color(1.0f, 0.0f, 0.0f, 0.4f); 11 | 12 | public override void OnInspectorGUI() { 13 | //Target 14 | UModbusTCP oUModbusTCP = (UModbusTCP)target; 15 | 16 | //Setup Styles 17 | GUIStyle oGUIStyleStatus = new GUIStyle(); 18 | oGUIStyleStatus.fontStyle = FontStyle.Bold; 19 | oGUIStyleStatus.alignment = TextAnchor.MiddleCenter; 20 | 21 | GUIStyle oGUIStyleStatusResult = new GUIStyle(); 22 | oGUIStyleStatusResult.alignment = TextAnchor.MiddleCenter; 23 | oGUIStyleStatusResult.fixedHeight = 40f; 24 | 25 | GUIStyle oGUIStyleStatusResultInfo = new GUIStyle(); 26 | oGUIStyleStatusResultInfo.alignment = TextAnchor.MiddleCenter; 27 | 28 | //Editor 29 | GUILayout.Space(20); 30 | GUILayout.BeginHorizontal(); 31 | GUILayout.Label("STATUS", oGUIStyleStatus); 32 | GUILayout.EndHorizontal(); 33 | GUILayout.Space(10); 34 | if(oUModbusTCP.connected) { 35 | //Style 36 | GUIStyle oGUIStyleStatusText = new GUIStyle(); 37 | oGUIStyleStatusText.normal.background = MakeTex(1000, 1, COLOR_CONNECTED); 38 | //Status text 39 | GUILayout.BeginHorizontal(oGUIStyleStatusText); 40 | GUILayout.Label("CONNECTED", oGUIStyleStatusResult); 41 | GUILayout.EndHorizontal(); 42 | GUILayout.Space(10); 43 | GUILayout.BeginHorizontal(); 44 | GUILayout.Label(string.Format("IP: {0}", oUModbusTCP.ip), oGUIStyleStatusResultInfo); 45 | GUILayout.EndHorizontal(); 46 | GUILayout.Space(10); 47 | GUILayout.BeginHorizontal(); 48 | GUILayout.Label(string.Format("PORT: {0}", oUModbusTCP.port), oGUIStyleStatusResultInfo); 49 | GUILayout.EndHorizontal(); 50 | } else { 51 | //Style 52 | GUIStyle oGUIStyleStatusText = new GUIStyle(); 53 | oGUIStyleStatusText.normal.background = MakeTex(1000, 1, COLOR_NOT_CONNECTED); 54 | //Status text 55 | GUILayout.BeginHorizontal(oGUIStyleStatusText); 56 | GUILayout.Label("NOT CONNECTED", oGUIStyleStatusResult); 57 | GUILayout.EndHorizontal(); 58 | } 59 | GUILayout.Space(40); 60 | 61 | } 62 | 63 | private Texture2D MakeTex(int _iWidth, int _iHeight, Color _oColor) { 64 | Color[] oPix = new Color[_iWidth * _iHeight]; 65 | 66 | for(int i = 0; i < oPix.Length; i++) { 67 | oPix[i] = _oColor; 68 | } 69 | 70 | Texture2D oResult = new Texture2D(_iWidth, _iHeight); 71 | oResult.SetPixels(oPix); 72 | oResult.Apply(); 73 | 74 | return oResult; 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /UModbusTCP.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine; 3 | using System.Collections; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | 7 | /// 8 | /// UModbusTCP implements a modbus TCP master driver for Unity. 9 | /// Created by Javier Garrido Galdon @nodlag 10 | /// Version 1.0.0 11 | /// Writen using Hungarian Notation https://www.reactos.org/wiki/Hungarian_Notation 12 | /// Based on 'Modbus TCP class' of Stephan Stricker https://www.codeproject.com/Tips/16260/Modbus-TCP-class 13 | /// This class supports the following commands: 14 | /// 15 | /// Read coils 16 | /// Read discrete inputs 17 | /// Write single coil 18 | /// Write multiple cooils 19 | /// Read holding register 20 | /// Read input register 21 | /// Write single register 22 | /// Write multiple register 23 | /// 24 | /// All commands can be sent in synchronous or asynchronous mode. If a value is accessed 25 | /// in synchronous mode the program will stop and wait for slave to response. If the 26 | /// slave didn't answer within a specified time a timeout exception is called. 27 | /// The class uses multi threading for both synchronous and asynchronous access. For 28 | /// the communication two lines are created. This is necessary because the synchronous 29 | /// thread has to wait for a previous command to finish. 30 | /// 31 | /// 32 | public class UModbusTCP : MonoBehaviour { 33 | 34 | //------------------------------------------------------------------------ 35 | //Constants for access 36 | const byte FUNCTION_READ_COIL = 1; 37 | const byte FUNCTION_READ_DISCRETE_INPUTS = 2; 38 | const byte FUNCTION_READ_HOLDING_REGISTER = 3; 39 | const byte FUNCTION_READ_INPUT_REGISTER = 4; 40 | const byte FUNCTION_WRITE_SINGLE_COIL = 5; 41 | const byte FUNCTION_WRITE_SINGLE_REGISTER = 6; 42 | const byte FUNCTION_WRITE_MULTIPLE_COILS = 15; 43 | const byte FUNCTION_WRITE_MULTIPLE_REGISTER = 16; 44 | const byte FUNCTION_READ_WRITE_MULTIPLE_REGISTER = 23; 45 | 46 | /// Constant for exception illegal function. 47 | public const byte EXCEPTION_ILEGAL_FUNCTION = 1; 48 | /// Constant for exception illegal data address. 49 | public const byte EXCEPTION_ILEGAL_DATA_ADDRESS = 2; 50 | /// Constant for exception illegal data value. 51 | public const byte EXCEPTION_ILEGAL_DATA_VALUE = 3; 52 | /// Constant for exception slave device failure. 53 | public const byte EXCEPTION_SLAVE_DEVICA_FAILURE = 4; 54 | /// Constant for exception acknowledge. 55 | public const byte EXCEPTION_ACKNOWLEDGE = 5; 56 | /// Constant for exception slave is busy/booting up. 57 | public const byte EXCEPTION_SLAVE_IS_BUSY = 6; 58 | /// Constant for exception gate path unavailable. 59 | public const byte EXCEPTION_GATE_PATH_UNAVAILABLE = 10; 60 | /// Constant for exception not connected. 61 | public const byte EXCEPTION_NOT_CONNECTED = 253; 62 | /// Constant for exception connection lost. 63 | public const byte EXCEPTION_CONNECTION_LOST = 254; 64 | /// Constant for exception response timeout. 65 | public const byte EXCEPTION_TIMEOUT = 255; 66 | /// Constant for exception wrong offset. 67 | private const byte EXCEPTION_WRONG_OFFSET = 128; 68 | /// Constant for exception send fail. 69 | private const byte EXCEPTION_SEND_FAIL = 100; 70 | 71 | //------------------------------------------------------------------------ 72 | //Unity singleton 73 | static UModbusTCP m_oInstance; 74 | 75 | public static UModbusTCP Instance { 76 | get { 77 | if(m_oInstance == null) { 78 | GameObject oGameObject = new GameObject(); 79 | oGameObject.name = "UModbusTCP"; 80 | m_oInstance = oGameObject.AddComponent(); 81 | } 82 | return m_oInstance; 83 | } 84 | } 85 | 86 | void Awake() { 87 | if(m_oInstance != null && m_oInstance != this) { 88 | Destroy(GetComponent()); 89 | } else { 90 | m_oInstance = this; 91 | } 92 | } 93 | 94 | //------------------------------------------------------------------------ 95 | //Connection mode 96 | public enum CONNECTION_MODE { 97 | LINEAR, 98 | PARALLEL, 99 | } 100 | 101 | //------------------------------------------------------------------------ 102 | //Private vars 103 | static string m_sIp; 104 | static ushort m_usPort; 105 | static ushort m_usConnectTimeout = 500; 106 | static ushort m_usTimeout = 500; 107 | static ushort m_usRefresh = 10; 108 | static bool m_bConnected = false; 109 | static bool m_bSyncConnected = false; 110 | static bool m_bAsyncConnected = false; 111 | 112 | //Sockets 113 | Socket m_oAsyncTCPSocket; 114 | byte[] m_bAsyncTCPSocketBuffer = new byte[2048]; 115 | 116 | Socket m_oSyncTCPSocket; 117 | byte[] m_bSyncTCPSocketBuffer = new byte[2048]; 118 | 119 | //------------------------------------------------------------------------ 120 | /// Response data event. This event is called when new data arrives 121 | public delegate void ResponseData(ushort _usId, byte _bUnit, byte _bFunction, byte[] _bData); 122 | /// Response data event. This event is called when new data arrives 123 | public event ResponseData OnResponseData; 124 | /// Exception data event. This event is called when the data is incorrect 125 | public delegate void ExceptionData(ushort _usId, byte _bUnit, byte _bFunction, byte _bException); 126 | /// Exception data event. This event is called when the data is incorrect 127 | public event ExceptionData OnException; 128 | 129 | //------------------------------------------------------------------------ 130 | /// Response timeout. If the slave didn't answers within in this time an exception is called. 131 | /// The default value is 500ms. 132 | public ushort timeout { 133 | get { return m_usTimeout; } 134 | set { m_usTimeout = value; } 135 | } 136 | 137 | //------------------------------------------------------------------------ 138 | /// Connect timeout. If the slave didn't answers within in this time an exception is called. 139 | /// The default value is 500ms. 140 | public ushort connectTimeout { 141 | get { return m_usConnectTimeout; } 142 | set { m_usConnectTimeout = value; } 143 | } 144 | 145 | //------------------------------------------------------------------------ 146 | /// Refresh timer for slave answer. The class is polling for answer every X ms. 147 | /// The default value is 10ms. 148 | public ushort refresh { 149 | get { return m_usRefresh; } 150 | set { m_usRefresh = value; } 151 | } 152 | 153 | //------------------------------------------------------------------------ 154 | /// Shows if a connection is active. 155 | public bool connected { 156 | get { return m_bConnected; } 157 | } 158 | 159 | //------------------------------------------------------------------------ 160 | /// Shows ip of connection activ. 161 | public string ip { 162 | get { return m_sIp; } 163 | } 164 | 165 | //------------------------------------------------------------------------ 166 | /// Shows port of connection active. 167 | public ushort port { 168 | get { return m_usPort; } 169 | } 170 | 171 | 172 | //------------------------------------------------------------------------ 173 | /// Create master instance without parameters. 174 | public UModbusTCP() { 175 | 176 | } 177 | 178 | //------------------------------------------------------------------------ 179 | /// Create master instance with parameters. 180 | /// IP adress of modbus slave. 181 | /// Port number of modbus slave. Usually port 502 is used. 182 | public UModbusTCP(string _sIp, ushort _usPort) { 183 | m_sIp = _sIp; 184 | m_usPort = _usPort; 185 | Connect(_sIp, _usPort); 186 | } 187 | 188 | //------------------------------------------------------------------------ 189 | /// Start connection to slave. 190 | /// IP adress of modbus slave. 191 | /// Port number of modbus slave. Usually port 502 is used. 192 | public void ConnectWithCoroutine(string _sIp, ushort _usPort, CONNECTION_MODE _eConnectionMode = CONNECTION_MODE.LINEAR) { 193 | m_sIp = _sIp; 194 | m_usPort = _usPort; 195 | StartCoroutine(ConnectCoroutine(_sIp, _usPort, _eConnectionMode)); 196 | } 197 | 198 | public void StopConnectionCoroutine() { 199 | StopCoroutine("ConnectCoroutine"); 200 | m_sIp = "0.0.0.0"; 201 | m_usPort = 502; 202 | m_bConnected = false; 203 | m_bSyncConnected = false; 204 | m_bAsyncConnected = false; 205 | } 206 | 207 | //------------------------------------------------------------------------ 208 | /// Start connection to slave. 209 | /// IP adress of modbus slave. 210 | /// Port number of modbus slave. Usually port 502 is used. 211 | public void Connect(string _sIp, ushort _usPort, CONNECTION_MODE _eConnectionMode = CONNECTION_MODE.LINEAR) { 212 | m_sIp = _sIp; 213 | m_usPort = _usPort; 214 | try { 215 | IPAddress oIp; 216 | if(IPAddress.TryParse(_sIp, out oIp) == false) { 217 | IPHostEntry hst = Dns.GetHostEntry(_sIp); 218 | _sIp = hst.AddressList[0].ToString(); 219 | } 220 | // ---------------------------------------------------------------- 221 | // Connect asynchronous client 222 | m_oAsyncTCPSocket = new Socket(IPAddress.Parse(_sIp).AddressFamily, SocketType.Stream, ProtocolType.Tcp); 223 | m_oAsyncTCPSocket.ReceiveTimeout = m_usConnectTimeout; 224 | m_oAsyncTCPSocket.SendTimeout = m_usConnectTimeout; 225 | switch(_eConnectionMode) { 226 | case CONNECTION_MODE.PARALLEL: 227 | m_oAsyncTCPSocket.BeginConnect(new IPEndPoint(IPAddress.Parse(_sIp), _usPort), new AsyncCallback(ConnectCoroutineCallback), null); 228 | break; 229 | case CONNECTION_MODE.LINEAR: 230 | default: 231 | m_oAsyncTCPSocket.Connect(new IPEndPoint(IPAddress.Parse(_sIp), _usPort)); 232 | break; 233 | } 234 | #if !UNITY_ANDROID 235 | m_oAsyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, m_usTimeout); 236 | m_oAsyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, m_usTimeout); 237 | m_oAsyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1); 238 | #endif 239 | // ---------------------------------------------------------------- 240 | // Connect synchronous client 241 | m_oSyncTCPSocket = new Socket(IPAddress.Parse(_sIp).AddressFamily, SocketType.Stream, ProtocolType.Tcp); 242 | m_oSyncTCPSocket.ReceiveTimeout = m_usConnectTimeout; 243 | m_oSyncTCPSocket.SendTimeout = m_usConnectTimeout; 244 | switch(_eConnectionMode) { 245 | case CONNECTION_MODE.PARALLEL: 246 | m_oSyncTCPSocket.BeginConnect(new IPEndPoint(IPAddress.Parse(_sIp), _usPort), new AsyncCallback(ConnectCoroutineCallback), null); 247 | break; 248 | case CONNECTION_MODE.LINEAR: 249 | default: 250 | m_oSyncTCPSocket.Connect(new IPEndPoint(IPAddress.Parse(_sIp), _usPort)); 251 | break; 252 | } 253 | #if !UNITY_ANDROID 254 | m_oSyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, m_usTimeout); 255 | m_oSyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, m_usTimeout); 256 | m_oSyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1); 257 | #endif 258 | if(_eConnectionMode == CONNECTION_MODE.LINEAR) { 259 | m_bConnected = true; 260 | } 261 | } catch(System.IO.IOException _oError) { 262 | m_bConnected = false; 263 | m_bSyncConnected = false; 264 | m_bAsyncConnected = false; 265 | throw (_oError); 266 | } 267 | } 268 | 269 | public IEnumerator ConnectCoroutine(string _sIp, ushort _usPort, CONNECTION_MODE _eConnectionMode = CONNECTION_MODE.LINEAR) { 270 | m_sIp = _sIp; 271 | m_usPort = _usPort; 272 | try { 273 | m_bConnected = false; 274 | m_bSyncConnected = false; 275 | m_bAsyncConnected = false; 276 | IPAddress oIp; 277 | if(IPAddress.TryParse(_sIp, out oIp) == false) { 278 | IPHostEntry hst = Dns.GetHostEntry(_sIp); 279 | _sIp = hst.AddressList[0].ToString(); 280 | } 281 | //---------------------------------------------------------------- 282 | //Connect asynchronous client 283 | m_oAsyncTCPSocket = new Socket(IPAddress.Parse(_sIp).AddressFamily, SocketType.Stream, ProtocolType.Tcp); 284 | m_oAsyncTCPSocket.ReceiveTimeout = m_usConnectTimeout; 285 | m_oAsyncTCPSocket.SendTimeout = m_usConnectTimeout; 286 | switch(_eConnectionMode) { 287 | case CONNECTION_MODE.PARALLEL: 288 | m_oAsyncTCPSocket.BeginConnect(new IPEndPoint(IPAddress.Parse(_sIp), _usPort), new AsyncCallback(ConnectCoroutineCallback), null); 289 | break; 290 | case CONNECTION_MODE.LINEAR: 291 | default: 292 | m_oAsyncTCPSocket.Connect(new IPEndPoint(IPAddress.Parse(_sIp), _usPort)); 293 | break; 294 | } 295 | #if !UNITY_ANDROID 296 | m_oAsyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, m_usTimeout); 297 | m_oAsyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, m_usTimeout); 298 | m_oAsyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1); 299 | #endif 300 | //---------------------------------------------------------------- 301 | //Connect synchronous client 302 | m_oSyncTCPSocket = new Socket(IPAddress.Parse(_sIp).AddressFamily, SocketType.Stream, ProtocolType.Tcp); 303 | m_oSyncTCPSocket.ReceiveTimeout = m_usConnectTimeout; 304 | m_oSyncTCPSocket.SendTimeout = m_usConnectTimeout; 305 | switch(_eConnectionMode) { 306 | case CONNECTION_MODE.PARALLEL: 307 | m_oSyncTCPSocket.BeginConnect(new IPEndPoint(IPAddress.Parse(_sIp), _usPort), new AsyncCallback(ConnectCoroutineCallback), null); 308 | break; 309 | case CONNECTION_MODE.LINEAR: 310 | default: 311 | m_oSyncTCPSocket.Connect(new IPEndPoint(IPAddress.Parse(_sIp), _usPort)); 312 | break; 313 | } 314 | #if !UNITY_ANDROID 315 | m_oSyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, m_usTimeout); 316 | m_oSyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, m_usTimeout); 317 | m_oSyncTCPSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.NoDelay, 1); 318 | #endif 319 | if(_eConnectionMode == CONNECTION_MODE.LINEAR) { 320 | m_bConnected = true; 321 | } 322 | } catch { 323 | m_bConnected = false; 324 | m_bSyncConnected = false; 325 | m_bAsyncConnected = false; 326 | Debug.Log("Error connection on ConnectCoroutine"); 327 | } 328 | yield break; 329 | } 330 | 331 | void ConnectCoroutineCallback(IAsyncResult _oResult) { 332 | m_bAsyncConnected = m_oAsyncTCPSocket.Connected; 333 | m_bSyncConnected = m_oSyncTCPSocket.Connected; 334 | if(m_bAsyncConnected && m_bSyncConnected) { 335 | m_bConnected = true; 336 | } else { 337 | m_bConnected = false; 338 | } 339 | } 340 | 341 | //------------------------------------------------------------------------ 342 | /// Stop connection to slave. 343 | public void Disconnect() { 344 | Dispose(); 345 | } 346 | 347 | //------------------------------------------------------------------------ 348 | /// Destroy master instance. 349 | ~UModbusTCP() { 350 | Dispose(); 351 | } 352 | 353 | //------------------------------------------------------------------------ 354 | /// Destroy master instance 355 | public void Dispose() { 356 | m_sIp = "0.0.0.0"; 357 | m_usPort = 502; 358 | m_bConnected = false; 359 | m_bSyncConnected = false; 360 | m_bAsyncConnected = false; 361 | if(m_oAsyncTCPSocket != null) { 362 | if(m_oAsyncTCPSocket.Connected) { 363 | try { m_oAsyncTCPSocket.Shutdown(SocketShutdown.Both); } catch { } 364 | m_oAsyncTCPSocket.Close(); 365 | } 366 | m_oAsyncTCPSocket = null; 367 | } 368 | if(m_oSyncTCPSocket != null) { 369 | if(m_oSyncTCPSocket.Connected) { 370 | try { m_oSyncTCPSocket.Shutdown(SocketShutdown.Both); } catch { } 371 | m_oSyncTCPSocket.Close(); 372 | } 373 | m_oSyncTCPSocket = null; 374 | } 375 | } 376 | 377 | internal void CallException(ushort _usId, byte _bUnit, byte _bFunction, byte _bException) { 378 | if((m_oAsyncTCPSocket == null) || (m_oSyncTCPSocket == null)) { 379 | return; 380 | } 381 | if(_bException == EXCEPTION_CONNECTION_LOST) { 382 | m_oSyncTCPSocket = null; 383 | m_oAsyncTCPSocket = null; 384 | } 385 | if(OnException != null) { 386 | OnException(_usId, _bUnit, _bFunction, _bException); 387 | } 388 | } 389 | 390 | internal static UInt16 SwapUInt16(UInt16 inValue) { 391 | return (UInt16)(((inValue & 0xff00) >> 8) | 392 | ((inValue & 0x00ff) << 8)); 393 | } 394 | 395 | //------------------------------------------------------------------------ 396 | /// Read coils from slave asynchronous. The result is given in the response function. 397 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 398 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 399 | /// Address from where the data read begins. 400 | /// Length of data. 401 | public void ReadCoils(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumInputs) { 402 | WriteAsyncData(CreateReadHeader(_usId, _bUnit, _usStartAddress, _usNumInputs, FUNCTION_READ_COIL), _usId); 403 | } 404 | 405 | //------------------------------------------------------------------------ 406 | /// Read coils from slave synchronous. 407 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 408 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 409 | /// Address from where the data read begins. 410 | /// Length of data. 411 | /// Contains the result of function. 412 | public void ReadCoils(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumInputs, ref byte[] _bValues) { 413 | _bValues = WriteSyncData(CreateReadHeader(_usId, _bUnit, _usStartAddress, _usNumInputs, FUNCTION_READ_COIL), _usId); 414 | } 415 | 416 | //------------------------------------------------------------------------ 417 | /// Read discrete inputs from slave asynchronous. The result is given in the response function. 418 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 419 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 420 | /// Address from where the data read begins. 421 | /// Length of data. 422 | public void ReadDiscreteInputs(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumInputs) { 423 | WriteAsyncData(CreateReadHeader(_usId, _bUnit, _usStartAddress, _usNumInputs, FUNCTION_READ_DISCRETE_INPUTS), _usId); 424 | } 425 | 426 | //------------------------------------------------------------------------ 427 | /// Read discrete inputs from slave synchronous. 428 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 429 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 430 | /// Address from where the data read begins. 431 | /// Length of data. 432 | /// Contains the result of function. 433 | public void ReadDiscreteInputs(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumInputs, ref byte[] _bValues) { 434 | _bValues = WriteSyncData(CreateReadHeader(_usId, _bUnit, _usStartAddress, _usNumInputs, FUNCTION_READ_DISCRETE_INPUTS), _usId); 435 | } 436 | 437 | //------------------------------------------------------------------------ 438 | /// Read holding registers from slave asynchronous. The result is given in the response function. 439 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 440 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 441 | /// Address from where the data read begins. 442 | /// Length of data. 443 | public byte[] ReadHoldingRegister(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumInputs) { 444 | byte[] bHeader = CreateReadHeader(_usId, _bUnit, _usStartAddress, _usNumInputs, FUNCTION_READ_HOLDING_REGISTER); 445 | WriteAsyncData(bHeader, _usId); 446 | return bHeader; 447 | } 448 | 449 | //------------------------------------------------------------------------ 450 | /// Read holding registers from slave synchronous. 451 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 452 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 453 | /// Address from where the data read begins. 454 | /// Length of data. 455 | /// Contains the result of function. 456 | public void ReadHoldingRegister(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumInputs, ref byte[] _bValues) { 457 | _bValues = WriteSyncData(CreateReadHeader(_usId, _bUnit, _usStartAddress, _usNumInputs, FUNCTION_READ_HOLDING_REGISTER), _usId); 458 | } 459 | 460 | //------------------------------------------------------------------------ 461 | /// Read input registers from slave asynchronous. The result is given in the response function. 462 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 463 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 464 | /// Address from where the data read begins. 465 | /// Length of data. 466 | public void ReadInputRegister(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumInputs) { 467 | WriteAsyncData(CreateReadHeader(_usId, _bUnit, _usStartAddress, _usNumInputs, FUNCTION_READ_INPUT_REGISTER), _usId); 468 | } 469 | 470 | //------------------------------------------------------------------------ 471 | /// Read input registers from slave synchronous. 472 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 473 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 474 | /// Address from where the data read begins. 475 | /// Length of data. 476 | /// Contains the result of function. 477 | public void ReadInputRegister(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumInputs, ref byte[] _bValues) { 478 | _bValues = WriteSyncData(CreateReadHeader(_usId, _bUnit, _usStartAddress, _usNumInputs, FUNCTION_READ_INPUT_REGISTER), _usId); 479 | } 480 | 481 | //------------------------------------------------------------------------ 482 | /// Write single coil in slave asynchronous. The result is given in the response function. 483 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 484 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 485 | /// Address from where the data read begins. 486 | /// Specifys if the coil should be switched on or off. 487 | public byte[] WriteSingleCoils(ushort _usId, byte _bUnit, ushort _usStartAddress, bool _bOnOff) { 488 | byte[] bData; 489 | bData = CreateWriteHeader(_usId, _bUnit, _usStartAddress, 1, 1, FUNCTION_WRITE_SINGLE_COIL); 490 | if(_bOnOff == true) { 491 | bData[10] = 255; 492 | } else { 493 | bData[10] = 0; 494 | } 495 | WriteAsyncData(bData, _usId); 496 | return bData; 497 | } 498 | 499 | //------------------------------------------------------------------------ 500 | /// Write single coil in slave synchronous. 501 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 502 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 503 | /// Address from where the data read begins. 504 | /// Specifys if the coil should be switched on or off. 505 | /// Contains the result of the synchronous write. 506 | public void WriteSingleCoils(ushort _usId, byte _bUnit, ushort _usStartAddress, bool _bOnOff, ref byte[] _bResult) { 507 | byte[] bData; 508 | bData = CreateWriteHeader(_usId, _bUnit, _usStartAddress, 1, 1, FUNCTION_WRITE_SINGLE_COIL); 509 | if(_bOnOff == true) { 510 | bData[10] = 255; 511 | } else { 512 | bData[10] = 0; 513 | } 514 | _bResult = WriteSyncData(bData, _usId); 515 | } 516 | 517 | //------------------------------------------------------------------------ 518 | /// Write multiple coils in slave asynchronous. The result is given in the response function. 519 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 520 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 521 | /// Address from where the data read begins. 522 | /// Specifys number of bits. 523 | /// Contains the bit information in byte format. 524 | public void WriteMultipleCoils(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumBits, byte[] _bValues) { 525 | byte bNumBytes = Convert.ToByte(_bValues.Length); 526 | byte[] bData; 527 | bData = CreateWriteHeader(_usId, _bUnit, _usStartAddress, _usNumBits, (byte)(bNumBytes + 2), FUNCTION_WRITE_MULTIPLE_COILS); 528 | Array.Copy(_bValues, 0, bData, 13, bNumBytes); 529 | WriteAsyncData(bData, _usId); 530 | } 531 | 532 | //------------------------------------------------------------------------ 533 | /// Write multiple coils in slave synchronous. 534 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 535 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 536 | /// Address from where the data read begins. 537 | /// Specifys number of bits. 538 | /// Contains the bit information in byte format. 539 | /// Contains the result of the synchronous write. 540 | public void WriteMultipleCoils(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort numBits, byte[] _bValues, ref byte[] _bResult) { 541 | byte bNumBytes = Convert.ToByte(_bValues.Length); 542 | byte[] bData; 543 | bData = CreateWriteHeader(_usId, _bUnit, _usStartAddress, numBits, (byte)(bNumBytes + 2), FUNCTION_WRITE_MULTIPLE_COILS); 544 | Array.Copy(_bValues, 0, bData, 13, bNumBytes); 545 | _bResult = WriteSyncData(bData, _usId); 546 | } 547 | 548 | //------------------------------------------------------------------------ 549 | /// Write single register in slave asynchronous. The result is given in the response function. 550 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 551 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 552 | /// Address to where the data is written. 553 | /// Contains the register information. 554 | public byte[] WriteSingleRegister(ushort _usId, byte _bUnit, ushort _usStartAddress, byte[] _bValues) { 555 | byte[] bData; 556 | bData = CreateWriteHeader(_usId, _bUnit, _usStartAddress, 1, 1, FUNCTION_WRITE_SINGLE_REGISTER); 557 | bData[10] = _bValues[0]; 558 | bData[11] = _bValues[1]; 559 | WriteAsyncData(bData, _usId); 560 | return bData; 561 | } 562 | 563 | //------------------------------------------------------------------------ 564 | /// Write single register in slave synchronous. 565 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 566 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 567 | /// Address to where the data is written. 568 | /// Contains the register information. 569 | /// Contains the result of the synchronous write. 570 | public byte[] WriteSingleRegister(ushort _usId, byte _bUnit, ushort _usStartAddress, byte[] _bValues, ref byte[] _bResult) { 571 | byte[] bData; 572 | bData = CreateWriteHeader(_usId, _bUnit, _usStartAddress, 1, 1, FUNCTION_WRITE_SINGLE_REGISTER); 573 | bData[10] = _bValues[0]; 574 | bData[11] = _bValues[1]; 575 | _bResult = WriteSyncData(bData, _usId); 576 | return bData; 577 | } 578 | 579 | //------------------------------------------------------------------------ 580 | /// Write multiple registers in slave asynchronous. The result is given in the response function. 581 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 582 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 583 | /// Address to where the data is written. 584 | /// Contains the register information. 585 | public byte[] WriteMultipleRegister(ushort _usId, byte _bUnit, ushort _usStartAddress, byte[] _bValues) { 586 | ushort usNumBytes = Convert.ToUInt16(_bValues.Length); 587 | if(usNumBytes % 2 > 0) { 588 | usNumBytes++; 589 | } 590 | byte[] bData; 591 | 592 | bData = CreateWriteHeader(_usId, _bUnit, _usStartAddress, Convert.ToUInt16(usNumBytes / 2), Convert.ToUInt16(usNumBytes + 2), FUNCTION_WRITE_MULTIPLE_REGISTER); 593 | Array.Copy(_bValues, 0, bData, 13, _bValues.Length); 594 | WriteAsyncData(bData, _usId); 595 | return bData; 596 | } 597 | 598 | //------------------------------------------------------------------------ 599 | /// Write multiple registers in slave synchronous. 600 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 601 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 602 | /// Address to where the data is written. 603 | /// Contains the register information. 604 | /// Contains the result of the synchronous write. 605 | public byte[] WriteMultipleRegister(ushort _usId, byte _bUnit, ushort _usStartAddress, byte[] _bValues, ref byte[] _bResult) { 606 | ushort usNumBytes = Convert.ToUInt16(_bValues.Length); 607 | if(usNumBytes % 2 > 0) { 608 | usNumBytes++; 609 | } 610 | byte[] bData; 611 | 612 | bData = CreateWriteHeader(_usId, _bUnit, _usStartAddress, Convert.ToUInt16(usNumBytes / 2), Convert.ToUInt16(usNumBytes + 2), FUNCTION_WRITE_MULTIPLE_REGISTER); 613 | Array.Copy(_bValues, 0, bData, 13, _bValues.Length); 614 | _bResult = WriteSyncData(bData, _usId); 615 | return bData; 616 | } 617 | 618 | //------------------------------------------------------------------------ 619 | /// Read/Write multiple registers in slave asynchronous. The result is given in the response function. 620 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 621 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 622 | /// Address from where the data read begins. 623 | /// Length of data. 624 | /// Address to where the data is written. 625 | /// Contains the register information. 626 | public byte[] ReadWriteMultipleRegister(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumInputs, ushort _usStartWriteAddress, byte[] _bValues) { 627 | ushort usNumBytes = Convert.ToUInt16(_bValues.Length); 628 | if(usNumBytes % 2 > 0) { 629 | usNumBytes++; 630 | } 631 | byte[] bData; 632 | 633 | bData = CreateReadWriteHeader(_usId, _bUnit, _usStartAddress, _usNumInputs, _usStartWriteAddress, Convert.ToUInt16(usNumBytes / 2)); 634 | Array.Copy(_bValues, 0, bData, 17, _bValues.Length); 635 | WriteAsyncData(bData, _usId); 636 | return bData; 637 | } 638 | 639 | //------------------------------------------------------------------------ 640 | /// Read/Write multiple registers in slave synchronous. The result is given in the response function. 641 | /// Unique id that marks the transaction. In asynchonous mode this id is given to the callback function. 642 | /// Unit identifier (previously slave address). In asynchonous mode this unit is given to the callback function. 643 | /// Address from where the data read begins. 644 | /// Length of data. 645 | /// Address to where the data is written. 646 | /// Contains the register information. 647 | /// Contains the result of the synchronous command. 648 | public byte[] ReadWriteMultipleRegister(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumInputs, ushort _usStartWriteAddress, byte[] _bValues, ref byte[] _bResult) { 649 | ushort usNumBytes = Convert.ToUInt16(_bValues.Length); 650 | if(usNumBytes % 2 > 0) { 651 | usNumBytes++; 652 | } 653 | byte[] bData; 654 | 655 | bData = CreateReadWriteHeader(_usId, _bUnit, _usStartAddress, _usNumInputs, _usStartWriteAddress, Convert.ToUInt16(usNumBytes / 2)); 656 | Array.Copy(_bValues, 0, bData, 17, _bValues.Length); 657 | _bResult = WriteSyncData(bData, _usId); 658 | return bData; 659 | } 660 | 661 | //------------------------------------------------------------------------ 662 | //Create modbus header for read action 663 | private byte[] CreateReadHeader(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usLength, byte _bFunction) { 664 | byte[] bData = new byte[12]; 665 | 666 | byte[] bId = BitConverter.GetBytes((short)_usId); 667 | bData[0] = bId[1]; // Slave id high byte 668 | bData[1] = bId[0]; // Slave id low byte 669 | bData[5] = 6; // Message size 670 | bData[6] = _bUnit; // Slave address 671 | bData[7] = _bFunction; // Function code 672 | byte[] bAddress = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)_usStartAddress)); 673 | bData[8] = bAddress[0]; // Start address 674 | bData[9] = bAddress[1]; // Start address 675 | byte[] bLength = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)_usLength)); 676 | bData[10] = bLength[0]; // Number of data to read 677 | bData[11] = bLength[1]; // Number of data to read 678 | 679 | return bData; 680 | } 681 | 682 | //------------------------------------------------------------------------ 683 | //Create modbus header for write action 684 | private byte[] CreateWriteHeader(ushort _usId, byte _bUnit, ushort _usStartAddress, ushort _usNumData, ushort _usNumBytes, byte _bFunction) { 685 | byte[] bData = new byte[_usNumBytes + 11]; 686 | 687 | byte[] bId = BitConverter.GetBytes((short)_usId); 688 | bData[0] = bId[1]; // Slave id high byte 689 | bData[1] = bId[0]; // Slave id low byte 690 | byte[] bSize = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)(5 + _usNumBytes))); 691 | bData[4] = bSize[0]; // Complete message size in bytes 692 | bData[5] = bSize[1]; // Complete message size in bytes 693 | bData[6] = _bUnit; // Slave address 694 | bData[7] = _bFunction; // Function code 695 | byte[] bAddress = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)_usStartAddress)); 696 | bData[8] = bAddress[0]; // Start address 697 | bData[9] = bAddress[1]; // Start address 698 | if(_bFunction >= FUNCTION_WRITE_MULTIPLE_COILS) { 699 | byte[] bCnt = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)_usNumData)); 700 | bData[10] = bCnt[0]; // Number of bytes 701 | bData[11] = bCnt[1]; // Number of bytes 702 | bData[12] = (byte)(_usNumBytes - 2); 703 | } 704 | 705 | return bData; 706 | } 707 | 708 | //------------------------------------------------------------------------ 709 | //Create modbus header for read/write action 710 | private byte[] CreateReadWriteHeader(ushort _usId, byte _bUnit, ushort _usStartReadAddress, ushort _usNumRead, ushort _usStartWriteAddress, ushort _usNumWrite) { 711 | byte[] bData = new byte[_usNumWrite * 2 + 17]; 712 | 713 | byte[] bId = BitConverter.GetBytes((short)_usId); 714 | bData[0] = bId[1]; // Slave id high byte 715 | bData[1] = bId[0]; // Slave id low byte 716 | byte[] bSize = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)(11 + _usNumWrite * 2))); 717 | bData[4] = bSize[0]; // Complete message size in bytes 718 | bData[5] = bSize[1]; // Complete message size in bytes 719 | bData[6] = _bUnit; // Slave address 720 | bData[7] = FUNCTION_READ_WRITE_MULTIPLE_REGISTER; // Function code 721 | byte[] bAddressRead = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)_usStartReadAddress)); 722 | bData[8] = bAddressRead[0]; // Start read address 723 | bData[9] = bAddressRead[1]; // Start read address 724 | byte[] bCntRead = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)_usNumRead)); 725 | bData[10] = bCntRead[0]; // Number of bytes to read 726 | bData[11] = bCntRead[1]; // Number of bytes to read 727 | byte[] bAddressWrite = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)_usStartWriteAddress)); 728 | bData[12] = bAddressWrite[0]; // Start write address 729 | bData[13] = bAddressWrite[1]; // Start write address 730 | byte[] bCntWrite = BitConverter.GetBytes((short)IPAddress.HostToNetworkOrder((short)_usNumWrite)); 731 | bData[14] = bCntWrite[0]; // Number of bytes to write 732 | bData[15] = bCntWrite[1]; // Number of bytes to write 733 | bData[16] = (byte)(_usNumWrite * 2); 734 | 735 | return bData; 736 | } 737 | 738 | //------------------------------------------------------------------------ 739 | //Write asynchronous data 740 | private void WriteAsyncData(byte[] _bWriteData, ushort _usId) { 741 | if((m_oAsyncTCPSocket != null) && (m_oAsyncTCPSocket.Connected)) { 742 | try { 743 | m_oAsyncTCPSocket.BeginSend(_bWriteData, 0, _bWriteData.Length, SocketFlags.None, new AsyncCallback(OnSend), null); 744 | m_oAsyncTCPSocket.BeginReceive(m_bAsyncTCPSocketBuffer, 0, m_bAsyncTCPSocketBuffer.Length, SocketFlags.None, new AsyncCallback(OnReceive), m_oAsyncTCPSocket); 745 | } catch(SystemException) { 746 | CallException(_usId, _bWriteData[6], _bWriteData[7], EXCEPTION_CONNECTION_LOST); 747 | } 748 | } else { 749 | CallException(_usId, _bWriteData[6], _bWriteData[7], EXCEPTION_CONNECTION_LOST); 750 | } 751 | } 752 | 753 | //------------------------------------------------------------------------ 754 | //Write asynchronous data acknowledge 755 | private void OnSend(System.IAsyncResult _oResult) { 756 | if(_oResult.IsCompleted == false) { 757 | CallException(0xFFFF, 0xFF, 0xFF, EXCEPTION_SEND_FAIL); 758 | } 759 | } 760 | 761 | //------------------------------------------------------------------------ 762 | //Write asynchronous data response 763 | private void OnReceive(System.IAsyncResult _oResult) { 764 | if(_oResult.IsCompleted == false) { 765 | CallException(0xFF, 0xFF, 0xFF, EXCEPTION_CONNECTION_LOST); 766 | } 767 | ushort usId = SwapUInt16(BitConverter.ToUInt16(m_bAsyncTCPSocketBuffer, 0)); 768 | byte bUnit = m_bAsyncTCPSocketBuffer[6]; 769 | byte bFunction = m_bAsyncTCPSocketBuffer[7]; 770 | byte[] bData; 771 | 772 | //------------------------------------------------------------ 773 | //Write response data 774 | if((bFunction >= FUNCTION_WRITE_SINGLE_COIL) && (bFunction != FUNCTION_READ_WRITE_MULTIPLE_REGISTER)) { 775 | bData = new byte[2]; 776 | Array.Copy(m_bAsyncTCPSocketBuffer, 10, bData, 0, 2); 777 | } else { 778 | //------------------------------------------------------------ 779 | //Read response data 780 | bData = new byte[m_bAsyncTCPSocketBuffer[8]]; 781 | Array.Copy(m_bAsyncTCPSocketBuffer, 9, bData, 0, m_bAsyncTCPSocketBuffer[8]); 782 | } 783 | //------------------------------------------------------------ 784 | //Response data is slave exception 785 | if(bFunction > EXCEPTION_WRONG_OFFSET) { 786 | bFunction -= EXCEPTION_WRONG_OFFSET; 787 | CallException(usId, bUnit, bFunction, m_bAsyncTCPSocketBuffer[8]); 788 | } else if(OnResponseData != null) { 789 | //------------------------------------------------------------ 790 | //Response data is regular data 791 | OnResponseData(usId, bUnit, bFunction, m_bAsyncTCPSocketBuffer); 792 | } 793 | } 794 | 795 | //------------------------------------------------------------------------ 796 | //Write data and and wait for response 797 | private byte[] WriteSyncData(byte[] _bWriteData, ushort _uId) { 798 | if(m_oSyncTCPSocket.Connected) { 799 | try { 800 | m_oSyncTCPSocket.Send(_bWriteData, 0, _bWriteData.Length, SocketFlags.None); 801 | int iResult = m_oSyncTCPSocket.Receive(m_bSyncTCPSocketBuffer, 0, m_bSyncTCPSocketBuffer.Length, SocketFlags.None); 802 | 803 | byte bUnit = m_bSyncTCPSocketBuffer[6]; 804 | byte bFunction = m_bSyncTCPSocketBuffer[7]; 805 | byte[] bData; 806 | 807 | if(iResult == 0) { 808 | CallException(_uId, bUnit, _bWriteData[7], EXCEPTION_CONNECTION_LOST); 809 | } 810 | 811 | //------------------------------------------------------------ 812 | //Response data is slave exception 813 | if(bFunction > EXCEPTION_WRONG_OFFSET) { 814 | bFunction -= EXCEPTION_WRONG_OFFSET; 815 | CallException(_uId, bUnit, bFunction, m_bSyncTCPSocketBuffer[8]); 816 | return null; 817 | } else if((bFunction >= FUNCTION_WRITE_SINGLE_COIL) && (bFunction != FUNCTION_READ_WRITE_MULTIPLE_REGISTER)) { 818 | //------------------------------------------------------------ 819 | //Write response data 820 | bData = new byte[2]; 821 | Array.Copy(m_bSyncTCPSocketBuffer, 10, bData, 0, 2); 822 | } else { 823 | //------------------------------------------------------------ 824 | //Read response data 825 | bData = new byte[m_bSyncTCPSocketBuffer[8]]; 826 | Array.Copy(m_bSyncTCPSocketBuffer, 9, bData, 0, m_bSyncTCPSocketBuffer[8]); 827 | } 828 | return bData; 829 | } catch(SystemException) { 830 | CallException(_uId, _bWriteData[6], _bWriteData[7], EXCEPTION_CONNECTION_LOST); 831 | } 832 | } else { 833 | CallException(_uId, _bWriteData[6], _bWriteData[7], EXCEPTION_CONNECTION_LOST); 834 | } 835 | return null; 836 | } 837 | } 838 | 839 | --------------------------------------------------------------------------------