diff --git a/Sharp7.Tests/ConnectionTest.cs b/Sharp7.Tests/ConnectionTest.cs new file mode 100644 index 0000000..5b40c2f --- /dev/null +++ b/Sharp7.Tests/ConnectionTest.cs @@ -0,0 +1,23 @@ +using System; +using Shouldly; +using Xunit; + +namespace Sharp7.Tests +{ + public class ConnectionTest : ServerClientTestBase + { + + [Fact] + public void ClientIsNotNull() + { + Client.ShouldNotBeNull(); + } + + [Fact] + public void ServerIsNotNull() + { + Server.ShouldNotBeNull(); + } + + } +} diff --git a/Sharp7.Tests/S7Server.cs b/Sharp7.Tests/S7Server.cs new file mode 100644 index 0000000..8f23450 --- /dev/null +++ b/Sharp7.Tests/S7Server.cs @@ -0,0 +1,445 @@ +using System; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Text; + +namespace Sharp7.Tests +{ + internal class S7Consts + { + public const string S7LibName = "snap7.dll"; + } + public class S7Server + { + #region [Constants, private vars and TypeDefs] + + private const int MsgTextLen = 1024; + private const int MkEvent = 0; + private const int MkLog = 1; + + // Server Area ID (use with Register/unregister - Lock/unlock Area) + public static readonly int SrvAreaDB = 5; + + // S7 Server Event Code + public static readonly uint EvcPdUincoming = 0x00010000; + public static readonly uint EvcDataRead = 0x00020000; + public static readonly uint EvcDataWrite = 0x00040000; + public static readonly uint EvcNegotiatePdu = 0x00080000; + public static readonly uint EvcReadSzl = 0x00100000; + public static readonly uint EvcClock = 0x00200000; + public static readonly uint EvcUpload = 0x00400000; + public static readonly uint EvcDownload = 0x00800000; + public static readonly uint EvcDirectory = 0x01000000; + public static readonly uint EvcSecurity = 0x02000000; + public static readonly uint EvcControl = 0x04000000; + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct USrvEvent + { + public IntPtr EvtTime; // It's platform dependent (32 or 64 bit) + public Int32 EvtSender; + public UInt32 EvtCode; + public ushort EvtRetCode; + public ushort EvtParam1; + public ushort EvtParam2; + public ushort EvtParam3; + public ushort EvtParam4; + } + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct S7Tag + { + public Int32 Area; + public Int32 DBNumber; + public Int32 Start; + public Int32 Elements; + public Int32 WordLen; + } + + private Dictionary hArea; + + private IntPtr server; + + #endregion + + #region [Class Control] + + [DllImport(S7Consts.S7LibName)] + private static extern IntPtr Srv_Create(); + /// + /// Create an instace of S7Server + /// + public S7Server() + { + server = Srv_Create(); + hArea = new Dictionary(); + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_Destroy(ref IntPtr server); + /// + /// Destroy the S7Server and free the memory + /// + ~S7Server() + { + foreach (var item in hArea) + { + GCHandle handle = item.Value; + if (handle != null) + handle.Free(); + } + Srv_Destroy(ref server); + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_StartTo(IntPtr server, [MarshalAs(UnmanagedType.LPStr)] string address); + /// + /// Start the server to a specific Address + /// + /// Address for adapter selection + /// 0: No errors. Otherwise see errorcodes + public int StartTo(string address) + { + return Srv_StartTo(server, address); + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_Start(IntPtr server); + /// + /// start the server + /// + /// 0: No errors. Otherwise see errorcodes + public int Start() + { + return Srv_Start(server); + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_Stop(IntPtr server); + /// + /// Stop the server + /// + /// 0: No errors. Otherwise see errorcodes + public int Stop() + { + return Srv_Stop(server); + } + + #endregion + + #region [Data Areas functions] + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_RegisterArea(IntPtr server, Int32 areaCode, Int32 index, IntPtr pUsrData, Int32 size); + /// + /// Register a PLC Area + /// + /// + /// Code for area identification (e.g. S7Server.SrvAreaDB) + /// Area index + /// Content of the area by reference + /// Allocation size + /// 0: No errors. Otherwise see errorcodes + public int RegisterArea(Int32 areaCode, Int32 index, ref T pUsrData, Int32 size) + { + Int32 areaUid = (areaCode << 16) + index; + GCHandle handle = GCHandle.Alloc(pUsrData, GCHandleType.Pinned); + int result = Srv_RegisterArea(server, areaCode, index, handle.AddrOfPinnedObject(), size); + if (result == 0) + hArea.Add(areaUid, handle); + else + handle.Free(); + return result; + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_UnregisterArea(IntPtr server, Int32 areaCode, Int32 index); + /// + /// Unregister a PLC area + /// + /// Code for area identification (e.g. S7Server.SrvAreaDB) + /// Area index + /// 0: No errors. Otherwise see errorcodes + public int UnregisterArea(Int32 areaCode, Int32 index) + { + int result = Srv_UnregisterArea(server, areaCode, index); + if (result == 0) + { + Int32 areaUid = (areaCode << 16) + index; + if (hArea.ContainsKey(areaUid)) // should be always true + { + GCHandle handle = hArea[areaUid]; + if (handle != null) // should be always true + handle.Free(); + hArea.Remove(areaUid); + } + } + return result; + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_LockArea(IntPtr server, Int32 areaCode, Int32 index); + /// + /// Lock a memory area + /// + /// Code for area identification (e.g. S7Server.SrvAreaDB) + /// Area index + /// 0: No errors. Otherwise see errorcodes + public int LockArea(Int32 areaCode, Int32 index) + { + return Srv_LockArea(server, areaCode, index); + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_UnlockArea(IntPtr server, Int32 areaCode, Int32 index); + /// + /// Unlock a memory area + /// + /// Code for area identification (e.g. S7Server.SrvAreaDB) + /// Area index + /// 0: No errors. Otherwise see errorcodes + public int UnlockArea(Int32 areaCode, Int32 index) + { + return Srv_UnlockArea(server, areaCode, index); + } + + #endregion + + #region [Notification functions (Events)] + + [StructLayout(LayoutKind.Sequential, Pack = 1)] + public struct RwBuffer + { + [MarshalAs(UnmanagedType.ByValArray, SizeConst = 1024)] // A telegram cannot exceed PDU size (960 bytes) + public byte[] Data; + } + /// + /// Callback delegate + /// + /// User pointer passed back + /// Event information structure + /// Size + public delegate void SrvCallback(IntPtr usrPtr, ref USrvEvent Event, int size); + /// + /// Callback delegate for RW operation + /// + /// User pointer passed back + /// Sender + /// Operation type + /// Operation Tag + /// RW Buffer + /// 0: No errors. Otherwise see errorcodes + public delegate int SrvRwAreaCallback(IntPtr usrPtr, int sender, int operation, ref S7Tag tag, ref RwBuffer buffer); + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_SetEventsCallback(IntPtr server, SrvCallback callback, IntPtr usrPtr); + /// + /// Set a function callback + /// + /// Callback delegate + /// User pointer passed back + /// 0: No errors. Otherwise see errorcodes + public int SetEventsCallBack(SrvCallback callback, IntPtr usrPtr) + { + return Srv_SetEventsCallback(server, callback, usrPtr); + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_SetReadEventsCallback(IntPtr server, SrvCallback callback, IntPtr usrPtr); + /// + /// Set a function callback for read events + /// + /// Callback delegate + /// User pointer passed back + /// 0: No errors. Otherwise see errorcodes + public int SetReadEventsCallBack(SrvCallback callback, IntPtr usrPtr) + { + return Srv_SetReadEventsCallback(server, callback, usrPtr); + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_SetRWAreaCallback(IntPtr server, SrvRwAreaCallback callback, IntPtr usrPtr); + /// + /// Set a function callback for read-write events + /// + /// Callback delegate + /// User pointer passed back + /// 0: No errors. Otherwise see errorcodes + public int SetRwAreaCallBack(SrvRwAreaCallback callback, IntPtr usrPtr) + { + return Srv_SetRWAreaCallback(server, callback, usrPtr); + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_PickEvent(IntPtr server, ref USrvEvent Event, ref Int32 evtReady); + /// + /// Extracts an event (if available) from the Events queue. + /// + /// Reference of User event + /// 0: No errors. Otherwise see errorcodes + public bool PickEvent(ref USrvEvent Event) + { + Int32 evtReady = new Int32(); + if (Srv_PickEvent(server, ref Event, ref evtReady) == 0) + return evtReady != 0; + else + return false; + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_ClearEvents(IntPtr server); + /// + /// clear the event queue + /// + /// 0: No errors. Otherwise see errorcodes + public int ClearEvents() + { + return Srv_ClearEvents(server); + } + + [DllImport(S7Consts.S7LibName, CharSet = CharSet.Ansi)] + private static extern int Srv_EventText(ref USrvEvent Event, StringBuilder evtMsg, int textSize); + /// + /// retrieve a message from an event + /// + /// Reference to Event + /// The message for an event + public string EventText(ref USrvEvent Event) + { + StringBuilder message = new StringBuilder(MsgTextLen); + Srv_EventText(ref Event, message, MsgTextLen); + return message.ToString(); + } + /// + /// Convet an the event time to datetime object + /// + /// Event Time pointer + /// the datetime for the timestamp + public DateTime EvtTimeToDateTime(IntPtr timeStamp) + { + DateTime unixStartEpoch = new DateTime(1970, 1, 1, 0, 0, 0, 0); + return unixStartEpoch.AddSeconds(Convert.ToDouble(timeStamp)); + } + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_GetMask(IntPtr server, Int32 maskKind, ref UInt32 mask); + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_SetMask(IntPtr server, Int32 maskKind, UInt32 mask); + + // Property LogMask R/W + /// + /// Activate o deactivate the LogMask + /// + public UInt32 LogMask + { + get + { + UInt32 mask = new UInt32(); + if (Srv_GetMask(server, S7Server.MkLog, ref mask) == 0) + return mask; + else + return 0; + } + set + { + Srv_SetMask(server, S7Server.MkLog, value); + } + } + + // Property EventMask R/W + /// + /// Activate o deactivate the EventMask + /// + public UInt32 EventMask + { + get + { + UInt32 mask = new UInt32(); + if (Srv_GetMask(server, S7Server.MkEvent, ref mask) == 0) + return mask; + else + return 0; + } + set + { + Srv_SetMask(server, S7Server.MkEvent, value); + } + } + + + #endregion + + #region [Info functions] + + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_GetStatus(IntPtr server, ref Int32 serverStatus, ref Int32 cpuStatus, ref Int32 clientsCount); + [DllImport(S7Consts.S7LibName)] + private static extern int Srv_SetCpuStatus(IntPtr server, Int32 cpuStatus); + + // Property Virtual CPU status R/W + public int CpuStatus + { + get + { + Int32 cStatus = new Int32(); + Int32 sStatus = new Int32(); + Int32 cCount = new Int32(); + + if (Srv_GetStatus(server, ref sStatus, ref cStatus, ref cCount) == 0) + return cStatus; + else + return -1; + } + set + { + Srv_SetCpuStatus(server, value); + } + } + + // Property Server Status Read Only + public int ServerStatus + { + get + { + Int32 cStatus = new Int32(); + Int32 sStatus = new Int32(); + Int32 cCount = new Int32(); + if (Srv_GetStatus(server, ref sStatus, ref cStatus, ref cCount) == 0) + return sStatus; + else + return -1; + } + } + + // Property Clients Count Read Only + public int ClientsCount + { + get + { + Int32 cStatus = new Int32(); + Int32 sStatus = new Int32(); + Int32 cCount = new Int32(); + if (Srv_GetStatus(server, ref cStatus, ref sStatus, ref cCount) == 0) + return cCount; + else + return -1; + } + } + + [DllImport(S7Consts.S7LibName, CharSet = CharSet.Ansi)] + private static extern int Srv_ErrorText(int error, StringBuilder errMsg, int textSize); + /// + /// Retrieve the error message for an error code + /// + /// Error code + /// Message for the error code + public string ErrorText(int error) + { + StringBuilder message = new StringBuilder(MsgTextLen); + Srv_ErrorText(error, message, MsgTextLen); + return message.ToString(); + } + + #endregion + } +} \ No newline at end of file diff --git a/Sharp7.Tests/ServerClientTestBase.cs b/Sharp7.Tests/ServerClientTestBase.cs new file mode 100644 index 0000000..8ff85ec --- /dev/null +++ b/Sharp7.Tests/ServerClientTestBase.cs @@ -0,0 +1,26 @@ +using System; +using System.Linq; +using Shouldly; + +namespace Sharp7.Tests +{ + public class ServerClientTestBase : ServerTestBase, IDisposable + { + private S7Client client; + public S7Client Client => client; + + public ServerClientTestBase() : base() + { + client = new S7Client(); + var rc = client.ConnectTo(Localhost, 0, 2); + rc.ShouldBe(0); + } + + + public new void Dispose() + { + client.Disconnect(); + base.Dispose(); + } + } +} \ No newline at end of file diff --git a/Sharp7.Tests/ServerTestBase.cs b/Sharp7.Tests/ServerTestBase.cs new file mode 100644 index 0000000..5bd6b40 --- /dev/null +++ b/Sharp7.Tests/ServerTestBase.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using Shouldly; + +namespace Sharp7.Tests +{ + public class ServerTestBase : IDisposable + { + private readonly S7Server server; + protected readonly string Localhost = "127.0.0.1"; + public ServerTestBase() + { + server = new S7Server(); + var rc = server.StartTo(Localhost); + rc.ShouldBe(0); + } + + public S7Server Server => server; + + public void Dispose() + { + server.Stop(); + } + } +} \ No newline at end of file diff --git a/Sharp7.Tests/Sharp7.Tests.csproj b/Sharp7.Tests/Sharp7.Tests.csproj new file mode 100644 index 0000000..21e3898 --- /dev/null +++ b/Sharp7.Tests/Sharp7.Tests.csproj @@ -0,0 +1,33 @@ + + + + net45 + AnyCPU;x86 + + + + x86 + + + + x86 + + + + + + + + + + + + + + + + PreserveNewest + + + + diff --git a/Sharp7.Tests/snap7.dll b/Sharp7.Tests/snap7.dll new file mode 100644 index 0000000..091ed77 Binary files /dev/null and b/Sharp7.Tests/snap7.dll differ diff --git a/Sharp7.sln b/Sharp7.sln index d04a0f4..3d14355 100644 --- a/Sharp7.sln +++ b/Sharp7.sln @@ -1,9 +1,11 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27004.2009 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29306.81 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharp7", "Sharp7\Sharp7.csproj", "{4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sharp7", "Sharp7\Sharp7.csproj", "{4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Sharp7.Tests", "Sharp7.Tests\Sharp7.Tests.csproj", "{A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,6 +29,18 @@ Global {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Release|x64.Build.0 = Release|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Release|x86.ActiveCfg = Release|Any CPU {4FE194AF-7FFE-48BA-9DFC-425E9D5B9F46}.Release|x86.Build.0 = Release|Any CPU + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|Any CPU.ActiveCfg = Debug|x86 + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|Any CPU.Build.0 = Debug|x86 + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|x64.ActiveCfg = Debug|Any CPU + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|x64.Build.0 = Debug|Any CPU + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|x86.ActiveCfg = Debug|Any CPU + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Debug|x86.Build.0 = Debug|Any CPU + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|Any CPU.ActiveCfg = Release|x86 + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|Any CPU.Build.0 = Release|x86 + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|x64.ActiveCfg = Release|Any CPU + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|x64.Build.0 = Release|Any CPU + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|x86.ActiveCfg = Release|Any CPU + {A770A4B3-8BD7-49A8-BF7B-8CE1FA2E577A}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE