diff --git a/ControlLogixNET.sln b/ControlLogixNET.sln
new file mode 100644
index 0000000..2bfd114
--- /dev/null
+++ b/ControlLogixNET.sln
@@ -0,0 +1,163 @@
+
+Microsoft Visual Studio Solution File, Format Version 11.00
+# Visual Studio 2010
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlLogixNET", "ControlLogixNET\ControlLogixNET.csproj", "{9A6CFFE2-0C70-45A7-92FC-88DA9000E591}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ICommon", "ICommon\ICommon.csproj", "{1E726BE8-F9B8-4A9B-B052-E0D9154F3896}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{3BBB1E40-64FC-46BD-92AA-2D7704C5095E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Processor", "Examples\Processor\Processor.csproj", "{54A5D090-5322-4087-8E92-4A5C5E8E194E}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SimpleOperations", "Examples\SimpleOperations\SimpleOperations.csproj", "{E653D839-E72D-4B45-B6B3-754A8EF98AEE}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TagGroups", "Examples\TagGroups\TagGroups.csproj", "{B8B61369-057F-4893-8C52-366BC8C09EBC}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ArrayTags", "Examples\ArrayTags\ArrayTags.csproj", "{FA46ECC1-6E51-47FA-B025-A38BF75515B8}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UserDefinedType", "Examples\UserDefinedType\UserDefinedType.csproj", "{C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StructureTags", "Examples\StructureTags\StructureTags.csproj", "{F9FF55D3-526E-46E7-B703-45E980FABA33}"
+EndProject
+Global
+ GlobalSection(TeamFoundationVersionControl) = preSolution
+ SccNumberOfProjects = 9
+ SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C}
+ SccTeamFoundationServer = https://tfs.codeplex.com/tfs/tfs12
+ SccLocalPath0 = .
+ SccProjectUniqueName1 = ControlLogixNET\\ControlLogixNET.csproj
+ SccProjectName1 = ControlLogixNET
+ SccLocalPath1 = ControlLogixNET
+ SccProjectUniqueName2 = Examples\\ArrayTags\\ArrayTags.csproj
+ SccProjectTopLevelParentUniqueName2 = ControlLogixNET.sln
+ SccProjectName2 = Examples/ArrayTags
+ SccLocalPath2 = Examples\\ArrayTags
+ SccProjectUniqueName3 = Examples\\Processor\\Processor.csproj
+ SccProjectTopLevelParentUniqueName3 = ControlLogixNET.sln
+ SccProjectName3 = Examples/Processor
+ SccLocalPath3 = Examples\\Processor
+ SccProjectUniqueName4 = Examples\\SimpleOperations\\SimpleOperations.csproj
+ SccProjectTopLevelParentUniqueName4 = ControlLogixNET.sln
+ SccProjectName4 = Examples/SimpleOperations
+ SccLocalPath4 = Examples\\SimpleOperations
+ SccProjectUniqueName5 = Examples\\StructureTags\\StructureTags.csproj
+ SccProjectTopLevelParentUniqueName5 = ControlLogixNET.sln
+ SccProjectName5 = Examples/StructureTags
+ SccLocalPath5 = Examples\\StructureTags
+ SccProjectUniqueName6 = Examples\\TagGroups\\TagGroups.csproj
+ SccProjectTopLevelParentUniqueName6 = ControlLogixNET.sln
+ SccProjectName6 = Examples/TagGroups
+ SccLocalPath6 = Examples\\TagGroups
+ SccProjectUniqueName7 = Examples\\UserDefinedType\\UserDefinedType.csproj
+ SccProjectTopLevelParentUniqueName7 = ControlLogixNET.sln
+ SccProjectName7 = Examples/UserDefinedType
+ SccLocalPath7 = Examples\\UserDefinedType
+ SccProjectUniqueName8 = ICommon\\ICommon.csproj
+ SccProjectName8 = ICommon
+ SccLocalPath8 = ICommon
+ EndGlobalSection
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Debug|Mixed Platforms = Debug|Mixed Platforms
+ Debug|x86 = Debug|x86
+ Release|Any CPU = Release|Any CPU
+ Release|Mixed Platforms = Release|Mixed Platforms
+ Release|x86 = Release|x86
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}.Release|Any CPU.Build.0 = Release|Any CPU
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}.Release|x86.ActiveCfg = Release|Any CPU
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}.Release|Mixed Platforms.Build.0 = Release|Any CPU
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}.Release|x86.ActiveCfg = Release|Any CPU
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}.Debug|x86.ActiveCfg = Debug|x86
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}.Debug|x86.Build.0 = Debug|x86
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}.Release|Any CPU.ActiveCfg = Release|x86
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}.Release|Mixed Platforms.Build.0 = Release|x86
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}.Release|x86.ActiveCfg = Release|x86
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}.Release|x86.Build.0 = Release|x86
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}.Debug|x86.ActiveCfg = Debug|x86
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}.Debug|x86.Build.0 = Debug|x86
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}.Release|Any CPU.ActiveCfg = Release|x86
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}.Release|Mixed Platforms.Build.0 = Release|x86
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}.Release|x86.ActiveCfg = Release|x86
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}.Release|x86.Build.0 = Release|x86
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}.Debug|x86.ActiveCfg = Debug|x86
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}.Debug|x86.Build.0 = Debug|x86
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}.Release|Any CPU.ActiveCfg = Release|x86
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}.Release|Mixed Platforms.Build.0 = Release|x86
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}.Release|x86.ActiveCfg = Release|x86
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}.Release|x86.Build.0 = Release|x86
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}.Debug|x86.ActiveCfg = Debug|x86
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}.Debug|x86.Build.0 = Debug|x86
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}.Release|Any CPU.ActiveCfg = Release|x86
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}.Release|Mixed Platforms.Build.0 = Release|x86
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}.Release|x86.ActiveCfg = Release|x86
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}.Release|x86.Build.0 = Release|x86
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}.Debug|x86.ActiveCfg = Debug|x86
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}.Debug|x86.Build.0 = Debug|x86
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}.Release|Any CPU.ActiveCfg = Release|x86
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}.Release|Mixed Platforms.Build.0 = Release|x86
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}.Release|x86.ActiveCfg = Release|x86
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}.Release|x86.Build.0 = Release|x86
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}.Debug|Any CPU.ActiveCfg = Debug|x86
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}.Debug|Mixed Platforms.Build.0 = Debug|x86
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}.Debug|x86.ActiveCfg = Debug|x86
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}.Debug|x86.Build.0 = Debug|x86
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}.Release|Any CPU.ActiveCfg = Release|x86
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}.Release|Mixed Platforms.ActiveCfg = Release|x86
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}.Release|Mixed Platforms.Build.0 = Release|x86
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}.Release|x86.ActiveCfg = Release|x86
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}.Release|x86.Build.0 = Release|x86
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E} = {3BBB1E40-64FC-46BD-92AA-2D7704C5095E}
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE} = {3BBB1E40-64FC-46BD-92AA-2D7704C5095E}
+ {B8B61369-057F-4893-8C52-366BC8C09EBC} = {3BBB1E40-64FC-46BD-92AA-2D7704C5095E}
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8} = {3BBB1E40-64FC-46BD-92AA-2D7704C5095E}
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD} = {3BBB1E40-64FC-46BD-92AA-2D7704C5095E}
+ {F9FF55D3-526E-46E7-B703-45E980FABA33} = {3BBB1E40-64FC-46BD-92AA-2D7704C5095E}
+ EndGlobalSection
+ GlobalSection(MonoDevelopProperties) = preSolution
+ StartupItem = EIPNET\EIPNET.csproj
+ EndGlobalSection
+EndGlobal
diff --git a/ControlLogixNET.vssscc b/ControlLogixNET.vssscc
new file mode 100644
index 0000000..6cb031b
--- /dev/null
+++ b/ControlLogixNET.vssscc
@@ -0,0 +1,10 @@
+""
+{
+"FILE_VERSION" = "9237"
+"ENLISTMENT_CHOICE" = "NEVER"
+"PROJECT_FILE_RELATIVE_PATH" = ""
+"NUMBER_OF_EXCLUDED_FILES" = "0"
+"ORIGINAL_PROJECT_FILE_PATH" = ""
+"NUMBER_OF_NESTED_PROJECTS" = "0"
+"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROJECT"
+}
diff --git a/ControlLogixNET/ChangeLog.txt b/ControlLogixNET/ChangeLog.txt
new file mode 100644
index 0000000..5045e30
--- /dev/null
+++ b/ControlLogixNET/ChangeLog.txt
@@ -0,0 +1,9 @@
+1.1
+Modified LogixTagGroup so that it sends out tag update notifications as soon as the packet is processed instead of waiting for all packets to be processed.
+Added a short version of the IOI string to more quickly update tags.
+Added methods on LogixTagGroup to add multiple tags.
+Added methods on LogixTagGroup to add tags by LogixTagInfo
+Added a LogixTagInfo property on the LogixTag
+
+1.0
+Initial Release
\ No newline at end of file
diff --git a/ControlLogixNET/CommonDataServiceReply.cs b/ControlLogixNET/CommonDataServiceReply.cs
new file mode 100644
index 0000000..d8fe2b1
--- /dev/null
+++ b/ControlLogixNET/CommonDataServiceReply.cs
@@ -0,0 +1,14 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ internal class CommonDataServiceReply
+ {
+ public byte Service { get; set; }
+ public byte Reserved { get; set; }
+ public ushort Status { get; set; }
+ }
+}
diff --git a/ControlLogixNET/ControlLogixNET.csproj b/ControlLogixNET/ControlLogixNET.csproj
new file mode 100644
index 0000000..e8cb4b3
--- /dev/null
+++ b/ControlLogixNET/ControlLogixNET.csproj
@@ -0,0 +1,122 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}
+ Library
+ Properties
+ ControlLogixNET
+ ControlLogixNET
+ v4.0
+ 512
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+ bin\Debug\ControlLogixNETDocumentation.XML
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ False
+ ..\EIPNET\bin\Debug\EIPNET.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ True
+ True
+ ErrorStrings.resx
+
+
+
+
+
+
+
+
+
+
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}
+ ICommon
+
+
+
+
+ ResXFileCodeGenerator
+ ErrorStrings.Designer.cs
+
+
+
+
+ Always
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ControlLogixNET/ControlLogixNET.csproj.vspscc b/ControlLogixNET/ControlLogixNET.csproj.vspscc
new file mode 100644
index 0000000..b6d3289
--- /dev/null
+++ b/ControlLogixNET/ControlLogixNET.csproj.vspscc
@@ -0,0 +1,10 @@
+""
+{
+"FILE_VERSION" = "9237"
+"ENLISTMENT_CHOICE" = "NEVER"
+"PROJECT_FILE_RELATIVE_PATH" = ""
+"NUMBER_OF_EXCLUDED_FILES" = "0"
+"ORIGINAL_PROJECT_FILE_PATH" = ""
+"NUMBER_OF_NESTED_PROJECTS" = "0"
+"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
+}
diff --git a/ControlLogixNET/ConversionHelper.cs b/ControlLogixNET/ConversionHelper.cs
new file mode 100644
index 0000000..3d3c5b1
--- /dev/null
+++ b/ControlLogixNET/ConversionHelper.cs
@@ -0,0 +1,25 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using ControlLogixNET.LogixType;
+
+namespace ControlLogixNET
+{
+ ///
+ /// Set of utility functions to help convert objects or strings to
+ /// the correct LogixDataType.
+ ///
+ internal class ConversionHelper
+ {
+ public object ConvertTo(LogixTypes LogixDataType, object InputValue)
+ {
+ switch (LogixDataType)
+ {
+ default:
+ break;
+ }
+ return null;
+ }
+ }
+}
diff --git a/ControlLogixNET/Exceptions.cs b/ControlLogixNET/Exceptions.cs
new file mode 100644
index 0000000..ffe7b91
--- /dev/null
+++ b/ControlLogixNET/Exceptions.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ ///
+ /// Raised when an invalid response was detected from the processor
+ ///
+ public class InvalidProcessorResponseException : Exception
+ {
+ ///
+ /// Creates a new InvalidProcessorResponseException
+ ///
+ /// Description of the exception
+ internal InvalidProcessorResponseException(string Description)
+ : base(Description)
+ {
+
+ }
+ }
+
+ ///
+ /// Raised when the dimensions have not been set before accessing a
+ /// multi-dimensional tag.
+ ///
+ public class DimensionsNotSetException : Exception
+ {
+ ///
+ /// Creates a new DimensionsNotSetException
+ ///
+ /// Description of the exception
+ internal DimensionsNotSetException(string Description)
+ : base(Description)
+ {
+
+ }
+ }
+
+ ///
+ /// Raised when trying to add a group to a that
+ /// already exists.
+ ///
+ public class GroupAlreadyExistsException : Exception
+ {
+ internal GroupAlreadyExistsException(string Description)
+ : base(Description)
+ {
+
+ }
+ }
+
+ ///
+ /// Raised when trying to retrieve a group that doesn't exist on the processor
+ ///
+ public class GroupNotFoundException : Exception
+ {
+ internal GroupNotFoundException(string Description)
+ : base(Description)
+ {
+
+ }
+ }
+
+ ///
+ /// Raised when trying to address a field in a UDT that does not exist.
+ ///
+ public class UDTMemberNotFoundException : Exception
+ {
+ internal UDTMemberNotFoundException(string Description)
+ : base(Description)
+ {
+
+ }
+ }
+
+ ///
+ /// Raised when the specified type cannot be converted to the correct type.
+ ///
+ public class TypeConversionException : Exception
+ {
+ internal TypeConversionException(string Description)
+ : base(Description)
+ {
+
+ }
+ }
+
+ ///
+ /// Raised when an array length is not of the required size
+ ///
+ public class ArrayLengthException : Exception
+ {
+ internal ArrayLengthException(string Description)
+ : base(Description) { }
+ }
+}
diff --git a/ControlLogixNET/GetStructAttribsRequest.cs b/ControlLogixNET/GetStructAttribsRequest.cs
new file mode 100644
index 0000000..d555b54
--- /dev/null
+++ b/ControlLogixNET/GetStructAttribsRequest.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ internal class GetStructAttribsRequest
+ {
+ //Bytes 6 and 7 are the instance number
+ byte[] request = new byte[]
+ { 0x03, 0x03, 0x20, 0x6C, 0x25, 0x00, 0x00, 0x00, 0x04, 0x00, 0x04, 0x00, 0x03, 0x00,
+ 0x02, 0x00, 0x01, 0x00 };
+
+ public byte RequestSize { get { return (byte)request.Length; } }
+
+ public GetStructAttribsRequest(ushort StructureId)
+ {
+ request[7] = (byte)(StructureId & 0x00FF);
+ request[6] = (byte)((StructureId & 0xFF00) >> 8);
+ }
+
+ public byte[] Pack()
+ {
+ return request;
+ }
+ }
+
+ internal class GetStructAttribsReply
+ {
+ public uint TemplateSize { get; internal set; }
+ public ushort MemorySize { get; internal set; }
+ public ushort MemberCount { get; internal set; }
+ public ushort Handle { get; internal set; }
+
+ public GetStructAttribsReply(byte[] data)
+ {
+ TemplateSize = BitConverter.ToUInt32(data, 6);
+ MemorySize = BitConverter.ToUInt16(data, 14);
+ MemberCount = BitConverter.ToUInt16(data, 20);
+ Handle = BitConverter.ToUInt16(data, 26);
+ }
+ }
+}
diff --git a/ControlLogixNET/Known Issues.txt b/ControlLogixNET/Known Issues.txt
new file mode 100644
index 0000000..0cd5ef6
--- /dev/null
+++ b/ControlLogixNET/Known Issues.txt
@@ -0,0 +1,4 @@
+This file contains a list of known issues. These issues will be fixed in an order of priority.
+
+1. The "SenderContext" is not being persisted through the requests. (Fixed 9-16-2011 RAB)
+2. LogixProcessor.WriteTag does not support more data than will fit in one packet. Solution may be to create a new WriteOnly group to write the tag.
\ No newline at end of file
diff --git a/ControlLogixNET/LicenseCheck.cs b/ControlLogixNET/LicenseCheck.cs
new file mode 100644
index 0000000..6f8d29f
--- /dev/null
+++ b/ControlLogixNET/LicenseCheck.cs
@@ -0,0 +1,16 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ internal static class LicenseCheck
+ {
+
+ static LicenseCheck()
+ {
+
+ }
+ }
+}
diff --git a/ControlLogixNET/LogixErrors.cs b/ControlLogixNET/LogixErrors.cs
new file mode 100644
index 0000000..e698264
--- /dev/null
+++ b/ControlLogixNET/LogixErrors.cs
@@ -0,0 +1,106 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ ///
+ /// Various Logix Errors
+ ///
+ public enum LogixErrors
+ {
+ ///
+ /// Session could not be established with the communications link
+ ///
+ SessionNotEstablished = -1,
+ ///
+ /// Session could not be registered with the communications link
+ ///
+ SessionNotRegistered = -2,
+ ///
+ /// CIP is not supported on the target device
+ ///
+ CIPNotSupported = -3,
+ ///
+ /// Processor could not be connected
+ ///
+ ProcessorNotConnected = -4,
+ ///
+ /// Incorrect Argument Tag Type
+ ///
+ IncorrectArgTagType = -5,
+ ///
+ /// Tag was not found on the processor
+ ///
+ TagNotFound = -6,
+ ///
+ /// The data returned by the processor does not match the tag type
+ ///
+ TypeMismatch = -7,
+ ///
+ /// The group that you are trying to add to the processor already exists
+ ///
+ GroupExists = -8,
+ ///
+ /// The specified group was not found
+ ///
+ GroupNotFound = -9,
+ ///
+ /// The processor received an invalid response size
+ ///
+ InvalidResponseSize = -10,
+ ///
+ /// The specified value could not be converted to the required type
+ ///
+ TypeConversionError = -11,
+ ///
+ /// The specified User Defined Type member was not found in the structure
+ ///
+ UDTMemberNotFound = -12,
+ ///
+ /// The IOI path could not be deciphered or the matching item was not found.
+ ///
+ MalformedIOI = -13,
+ ///
+ /// The item referenced could not be found on the processor
+ ///
+ ItemNotFound = -14,
+ ///
+ /// An error occurred trying to process one of the attributes
+ ///
+ AttributeError = -15,
+ ///
+ /// Not enough data was sent to the processor to execute the command
+ ///
+ NotEnoughData = -16,
+ ///
+ /// An insufficient number of attributes were supplied compared to the attribute count.
+ ///
+ InsufficientAttributes = -17,
+ ///
+ /// The IOI word length did not match the amount of IOI which was processed
+ ///
+ InvalidIOILength = -18,
+ ///
+ /// An attempt was made to access data beyond the end of the object
+ ///
+ AccessBeyondObject = -19,
+ ///
+ /// The abbreviated type does not match the data type of the object
+ ///
+ AbbreviatedTypeError = -20,
+ ///
+ /// The beginning offset was beyond the end of the template
+ ///
+ BeginOffsetError = -21,
+ ///
+ /// A socket exception has occurred
+ ///
+ SocketError = -22,
+ ///
+ /// An unknown error has occurred
+ ///
+ Unknown = -100
+ }
+}
diff --git a/ControlLogixNET/LogixProcessor.cs b/ControlLogixNET/LogixProcessor.cs
new file mode 100644
index 0000000..add8087
--- /dev/null
+++ b/ControlLogixNET/LogixProcessor.cs
@@ -0,0 +1,1243 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using ICommon;
+using EIPNET.EIP;
+using System.Diagnostics;
+using EIPNET;
+using EIPNET.CIP;
+using System.Threading;
+using System.Net.Sockets;
+
+namespace ControlLogixNET
+{
+ ///
+ /// Interface to a ControlLogix Processor
+ ///
+ public sealed class LogixProcessor : IDevice
+ {
+
+ #region Fields
+
+ private object _userData;
+ private int _errorCode;
+ private string _errorString;
+
+ private byte[] _path;
+ private string _ipAddress;
+ private SessionInfo _session;
+
+ private SortedDictionary _tags;
+
+ private ProcessorState _state = ProcessorState.SolidRed;
+ private ProcessorKeySwitch _keySw = ProcessorKeySwitch.Unknown;
+ private ProcessorFaultState _faultState = ProcessorFaultState.None;
+
+ private ushort _vendorId;
+ private ushort _deviceType;
+ private ushort _productCode;
+ private byte _majorRevision;
+ private byte _minorRevision;
+ private uint _serialNumber;
+ private string _productName;
+
+ private object _lockObject = new object();
+
+ private Timer _stateTimer;
+ private Thread _stateThread;
+ private bool _stateUpdateEnabled = true;
+ private bool _stateEventsEnabled = false;
+
+ private Timer _autoUpdateTimer;
+ private Thread _autoUpdateThread;
+ private bool _autoUpdateEnabled = false;
+ private int _autoUpdateTime = 0;
+
+ private Dictionary _tagGroups;
+
+ private DateTime _lastOperationTime = DateTime.Now;
+ private bool _initializing = false;
+
+ private List _tagInfo;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets the most recent error code, see
+ ///
+ public int ErrorCode
+ {
+ get { return _errorCode; }
+ }
+
+ ///
+ /// Gets a text description of the most recent error in a localized language if available
+ ///
+ public string ErrorString
+ {
+ get { return _errorString; }
+ }
+
+ ///
+ /// Gets or Sets any data available to the user to store with the LogixProcessor
+ ///
+ public object UserData
+ {
+ get
+ {
+ return _userData;
+ }
+ set
+ {
+ _userData = value;
+ }
+ }
+
+ ///
+ /// Gets the version information
+ ///
+ public Version Version
+ {
+ get
+ {
+ return System.Reflection.Assembly.GetAssembly(typeof(LogixProcessor)).GetName().Version;
+ }
+ }
+
+ ///
+ /// Gets the ProcessorState, see
+ ///
+ public ProcessorState ProcessorState { get { return _state; } }
+
+ ///
+ /// Gets the Key Switch Position, see
+ ///
+ public ProcessorKeySwitch KeySwitchPosition { get { return _keySw; } }
+
+ ///
+ /// Gets the Fault State, see
+ ///
+ public ProcessorFaultState FaultState { get { return _faultState; } }
+
+ ///
+ /// Gets the VendorId (should be 0x01 for Allen-Bradley)
+ ///
+ public ushort VendorId { get { return _vendorId; } }
+
+ ///
+ /// Gets the device type (should be 0x0E for PLC)
+ ///
+ public ushort DeviceType { get { return _deviceType; } }
+
+ ///
+ /// Gets the Product Code
+ ///
+ public ushort ProductCode { get { return _productCode; } }
+
+ ///
+ /// Gets the Major Revision of the Processor
+ ///
+ public byte MajorRevision { get { return _majorRevision; } }
+
+ ///
+ /// Gets the Minor Revision of the Processor
+ ///
+ public byte MinorRevision { get { return _minorRevision; } }
+
+ ///
+ /// Gets the Serial Number of the Processor
+ ///
+ public uint SerialNumber { get { return _serialNumber; } }
+
+ ///
+ /// Gets the Product Name
+ ///
+ public string ProductName { get { return _productName; } }
+
+ ///
+ /// Gets the Host Name or IP Address associated with this processor
+ ///
+ public string HostNameOrIp { get { return _ipAddress; } }
+
+ ///
+ /// Gets a indicating if the AutoUpdate feature is enabled
+ ///
+ ///
+ /// To enable the AutoUpdate feature, use . To
+ /// disable the AutoUpdate feature, use .
+ ///
+ public bool AutoUpdateEnabled
+ {
+ get { return _autoUpdateEnabled; }
+ }
+
+ ///
+ /// Gets the AutoUpdate interval time in Milliseconds
+ ///
+ ///
+ /// To change the time between updates, call
+ /// with the new interval time. You can call this function without disabling
+ /// the auto update feature first.
+ ///
+ public int AutoUpdateTimeMs
+ {
+ get { return _autoUpdateTime; }
+ }
+
+ ///
+ /// Gets the SessionInfo object (for internal use only)
+ ///
+ internal SessionInfo SessionInfo { get { return _session; } }
+
+ ///
+ /// Gets the path to the processor
+ ///
+ internal byte[] Path { get { return _path; } }
+
+ internal object SyncRoot { get { return _lockObject; } }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Raised when the processor state changes
+ ///
+ public event LogixProcessorStateChangedEvent ProcessorStateChanged;
+ ///
+ /// Raised when the fault state changes
+ ///
+ public event LogixFaultStateChangedEvent FaultStateChanged;
+ ///
+ /// Raised when the key switch position changes
+ ///
+ public event LogixKeyPositionChangedEvent KeySwitchChanged;
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new LogixProcessor
+ ///
+ /// Host Name or IP Address
+ /// Path to the processor
+ public LogixProcessor(string HostNameOrIP, byte[] ProcessorPath)
+ {
+ _tagGroups = new Dictionary();
+
+ _ipAddress = HostNameOrIP;
+ _path = new byte[ProcessorPath.Length + 1];
+ _path[0] = 1;
+ Buffer.BlockCopy(ProcessorPath, 0, _path, 1, ProcessorPath.Length);
+
+ _tags = new SortedDictionary();
+ }
+
+ ///
+ /// Disposes the object, closing the connection if necessary
+ ///
+ public void Dispose()
+ {
+ Disconnect();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Connects to the PLC
+ ///
+ /// True if the connection was successfully made
+ public bool Connect()
+ {
+ try
+ {
+ _initializing = true;
+ _session = SessionManager.CreateAndRegister(_ipAddress, 0xAF12, System.Text.ASCIIEncoding.ASCII.GetBytes("6DSYSTEM"));
+
+ if (!_session.Connected)
+ {
+ Trace.WriteLine(Resources.ErrorStrings.SessionNotEstablished);
+ _errorCode = (int)LogixErrors.SessionNotEstablished;
+ _errorString = Resources.ErrorStrings.SessionNotEstablished;
+ _initializing = false;
+ return false;
+ }
+
+ if (!_session.Registered)
+ {
+ Trace.WriteLine(Resources.ErrorStrings.SessionNotRegistered + " : " + _session.LastSessionErrorString);
+ _errorCode = (int)LogixErrors.SessionNotRegistered;
+ _errorString = Resources.ErrorStrings.SessionNotRegistered + " : " + _session.LastSessionErrorString;
+ _initializing = false;
+ return false;
+ }
+
+ Trace.WriteLine(Resources.ErrorStrings.SessionRegistered);
+
+ if (!SessionManager.VerifyCIP(_session))
+ {
+ Trace.WriteLine(Resources.ErrorStrings.CIPNotSupported);
+ _errorCode = (int)LogixErrors.CIPNotSupported;
+ _errorString = Resources.ErrorStrings.CIPNotSupported;
+ SessionManager.UnRegister(_session);
+ _initializing = false;
+ return false;
+ }
+
+ Random rnd = new Random();
+ _session.SetConnectionParameters((ushort)rnd.Next(0, ushort.MaxValue),
+ 1800000, (uint)rnd.Next(0, int.MaxValue), 0xFACE, 0xEFFEC);
+ _session.MillisecondTimeout = 2000;
+
+ if (!ConnectionManager.ConnectOverControlNet(_session, _path, 0))//0x43F4))
+ {
+ _errorCode = (int)LogixErrors.ProcessorNotConnected;
+ _errorString = Resources.ErrorStrings.ProcessorNotConnected;
+ Trace.WriteLine(Resources.ErrorStrings.ProcessorNotConnected);
+ SessionManager.UnRegister(_session);
+ _initializing = false;
+ return false;
+ }
+
+ //The connection times out after some time, so we have to make sure to
+ //get an event when that happens so we can reconnect...
+ _lastOperationTime = DateTime.Now;
+
+ _session.OnSocketError = new EIPNET.EIP.SocketErrorCallback(SocketErrorCallback);
+ _session.OnPreOperation = new EIPNET.EIP.PreOperationCallback(PreOperationCallback);
+ _session.OnPostOperation = new EIPNET.EIP.PostOperationCallback(PostOperationCallback);
+
+ _stateEventsEnabled = false;
+ UpdateState();
+ _stateEventsEnabled = true;
+
+ //_stateTimer = new Timer(new TimerCallback(UpdateStateTimer), null, 1000, 1000);
+ _stateThread = new Thread(new ThreadStart(StateUpdateThread));
+ _stateThread.IsBackground = true;
+ _stateThread.Name = _ipAddress + " State Update Thread";
+ _stateThread.Start();
+
+ _initializing = false;
+ return true;
+ }
+ catch (Exception e)
+ {
+ _initializing = false;
+ return false;
+ }
+ }
+
+ ///
+ /// Disconnects from the PLC
+ ///
+ /// True if the connection was successfully closed
+ public bool Disconnect()
+ {
+ //lock (_lockObject)
+ {
+ //Stop the state timer...
+ if (_stateUpdateEnabled)
+ {
+ _stateUpdateEnabled = false;
+ try
+ {
+ _stateThread.Abort();
+ _stateThread = null;
+ }
+ catch { }
+ }
+
+ DisableAutoUpdate();
+
+ if (_session != null && _session.Connected)
+ {
+ ConnectionManager.ForwardClose(_session, _path);
+ SessionManager.UnRegister(_session);
+ }
+
+ }
+
+ return true;
+ }
+
+ ///
+ /// Reads a single tag
+ ///
+ /// Tag to read
+ /// True if the read was successful
+ /// This is not the preferred method of updating tag information. If you have
+ /// multiple tags that you want to update, use the method.
+ public bool ReadTag(ITag tag)
+ {
+ lock (_lockObject)
+ {
+ LogixTag lgxTag = tag as LogixTag;
+
+ if (lgxTag == null)
+ throw new ArgumentException(Resources.ErrorStrings.IncorrectArgTagType, "tag");
+
+ ReadDataServiceReply lgxRead = LogixServices.ReadLogixData(_session, lgxTag.Address, (ushort)lgxTag.Elements);
+
+ if (lgxRead == null || lgxRead.Data == null)
+ {
+ if (lgxRead != null)
+ lgxTag.SetTagError(lgxRead.ByteStatus);
+
+ lgxTag.LastError = Resources.ErrorStrings.TagNotFound + _ipAddress;
+ lgxTag.LastErrorNumber = (int)LogixErrors.TagNotFound;
+ return false;
+ }
+
+ lgxTag.SetTagError(lgxRead.ByteStatus);
+ CIPType tagType = (CIPType)lgxRead.DataType;
+ byte[] temp = new byte[lgxRead.Data.Length + 2];
+ Buffer.BlockCopy(BitConverter.GetBytes(lgxRead.DataType), 0, temp, 0, 2);
+ Buffer.BlockCopy(lgxRead.Data, 0, temp, 2, lgxRead.Data.Length);
+
+ lgxTag.UpdateValue(temp);
+
+ uint offset = (uint)lgxRead.Data.Length;
+
+ if (lgxRead.Status == 0x06)
+ {
+ //We are going to have to request more data...
+ while (lgxRead.Status == 0x06)
+ {
+ lgxRead = LogixServices.ReadLogixDataFragmented(_session, lgxTag.Address, (ushort)lgxTag.Elements, offset);
+ lgxTag.SetTagError(lgxRead.ByteStatus);
+
+ tagType = (CIPType)lgxRead.DataType;
+ temp = new byte[lgxRead.Data.Length + 2];
+ Buffer.BlockCopy(BitConverter.GetBytes(lgxRead.DataType), 0, temp, 0, 2);
+ Buffer.BlockCopy(lgxRead.Data, 0, temp, 2, lgxRead.Data.Length);
+
+ lgxTag.UpdateValue(temp, offset);
+
+ offset += (uint)lgxRead.Data.Length;
+ }
+ }
+ } //End Lock
+
+ return true;
+ }
+
+ ///
+ /// Writes a single tag
+ ///
+ /// Tag to write
+ /// True if the write was successful
+ /// This is not the preferred method of updating tag information. If you have
+ /// multiple tags that you want to update, use the method.
+ public bool WriteTag(ITag tag)
+ {
+ lock (_lockObject)
+ {
+ LogixTag lgxTag = tag as LogixTag;
+
+ if (lgxTag == null)
+ throw new ArgumentException(Resources.ErrorStrings.IncorrectArgTagType, "tag");
+
+ WriteDataServiceReply lgxWrite = LogixServices.WriteLogixData(_session, lgxTag.Address, lgxTag.DataType, (ushort)lgxTag.Elements, lgxTag.GetWriteData(), lgxTag.StructHandle);
+
+ if (lgxWrite == null)
+ return false;
+
+ if (lgxWrite.Status == 0x00)
+ {
+ lgxTag.ClearPendingWrite();
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Writes a tag to the PLC then reads it back
+ ///
+ /// Tag to write
+ /// True if both the read and write were successful
+ public bool WriteRead(ITag tag)
+ {
+ bool retVal = true;
+ retVal &= WriteTag(tag);
+ retVal &= ReadTag(tag);
+ return retVal;
+ }
+
+ ///
+ /// Updates (reads/writes) all the tags in the active groups
+ ///
+ public void UpdateGroups()
+ {
+ lock (_lockObject)
+ {
+ foreach (LogixTagGroup group in _tagGroups.Values)
+ {
+ if (group.Enabled)
+ group.UpdateGroup();
+ }
+ }
+ }
+
+ ///
+ /// Creates a LogixTagGroup on this processor
+ ///
+ /// Name of the group to create
+ /// with the specified group name
+ /// Raised when trying to add a group that already exists
+ public LogixTagGroup CreateTagGroup(string GroupName)
+ {
+ //First verify that the group doesn't exist
+ if (_tagGroups.ContainsKey(GroupName))
+ throw new GroupAlreadyExistsException(Resources.ErrorStrings.GroupExists);
+
+ LogixTagGroup newGroup = new LogixTagGroup(this, GroupName);
+ _tagGroups.Add(GroupName, newGroup);
+
+ return newGroup;
+ }
+
+ ///
+ /// Sets the processor to be in the RUN mode
+ ///
+ /// The keyswitch must be in the REM position for this to work.
+ public void SetRunMode()
+ {
+ //lock (_lockObject)
+ {
+ CommonPacket cpf = new CommonPacket();
+ cpf.AddressItem = CommonPacketItem.GetConnectedAddressItem(_session.ConnectionParameters.O2T_CID);
+
+ byte[] request = new byte[] { 0x06, 0x02, 0x20, 0x8E, 0x24, 0x01 };
+
+ cpf.DataItem = CommonPacketItem.GetConnectedDataItem(request, SequenceNumberGenerator.SequenceNumber);
+
+ _session.SendUnitData(cpf.AddressItem, cpf.DataItem);
+ }
+ }
+
+ ///
+ /// Sets the processor to be in the PROGRAM mode
+ ///
+ /// The keyswitch must be in the REM position for this to work.
+ public void SetProgramMode()
+ {
+ //lock (_lockObject)
+ {
+ CommonPacket cpf = new CommonPacket();
+ cpf.AddressItem = CommonPacketItem.GetConnectedAddressItem(_session.ConnectionParameters.O2T_CID);
+
+ byte[] request = new byte[] { 0x07, 0x02, 0x20, 0x8E, 0x24, 0x01 };
+
+ cpf.DataItem = CommonPacketItem.GetConnectedDataItem(request, SequenceNumberGenerator.SequenceNumber);
+
+ _session.SendUnitData(cpf.AddressItem, cpf.DataItem);
+ }
+ }
+
+ ///
+ /// Gets the tag group with the specified name
+ ///
+ /// Name of the group to get. This is case sensitive.
+ /// LogixTagGroup reference
+ /// Thrown when the requested group is not found.
+ public LogixTagGroup GetTagGroup(string GroupName)
+ {
+ LogixTagGroup retGroup = null;
+
+ if (!_tagGroups.TryGetValue(GroupName, out retGroup))
+ throw new GroupNotFoundException(Resources.ErrorStrings.GroupNotFound);
+
+ return retGroup;
+ }
+
+ ///
+ /// Gets a list of all the tags on the processor
+ ///
+ ///
+ public List EnumerateTags()
+ {
+ return GetTagInfo();
+ }
+
+ ///
+ /// Get information about a tag
+ ///
+ /// Address of the tag
+ /// LogixTagInfo object or null if the tag was not found
+ public LogixTagInfo GetTagInformation(string Address)
+ {
+ List tagInfo = EnumerateTags();
+
+ if (tagInfo != null && tagInfo.Count > 0)
+ {
+ LogixTagInfo retTag = tagInfo.Find(t => (string.Compare(t.TagName, Address, true) == 0));
+ if (retTag == null)
+ return null;
+ if (retTag.TagName != Address)
+ return null;
+ return retTag;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Returns a string representation of the object
+ ///
+ /// String of the object
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine("ControlLogix Processor");
+ sb.AppendLine("\tIP Address : " + _ipAddress.ToString());
+ sb.AppendLine("\tConnected : " + (_session.Connected ? "Yes" : "No"));
+ sb.AppendLine("\tProcessor : " + _productName);
+ sb.AppendLine("\tFault State : " + _faultState.ToString());
+ sb.AppendLine("\tKey Position : " + _keySw.ToString());
+ sb.AppendLine("\tProcessor State: " + _state.ToString());
+
+ return sb.ToString();
+ }
+
+ ///
+ /// Enables or changes the AutoUpdate feature
+ ///
+ ///
+ /// The AutoUpdate feature will automatically write/read all active tag
+ /// groups on the processor. Calling this function with the AutoUpdate feature
+ /// already active will change the time between updates. The actual time that
+ /// it takes to update the groups is dependant upon how many tags are in the
+ /// group and how many writes are active.
+ /// To disable the AutoUpdate feature, call the
+ /// method.
+ ///
+ /// Milliseconds between updates
+ public void EnableAutoUpdate(int UpdateRateMs)
+ {
+ _autoUpdateEnabled = true;
+ _autoUpdateTime = UpdateRateMs;
+
+ if (_autoUpdateThread == null)
+ {
+ _autoUpdateThread = new Thread(new ThreadStart(AutoUpdateThread));
+ _autoUpdateThread.Name = _ipAddress + " Group Update Thread";
+ _autoUpdateThread.IsBackground = true;
+ _autoUpdateThread.Start();
+ }
+ }
+
+ ///
+ /// Disables the AutoUpdate feature
+ ///
+ public void DisableAutoUpdate()
+ {
+ if (!_autoUpdateEnabled)
+ return;
+
+ try
+ {
+ _autoUpdateEnabled = false;
+ _autoUpdateThread.Abort();
+ _autoUpdateThread = null;
+ }
+ catch { }
+
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+ ///
+ /// Adds a tag to the processor
+ ///
+ /// Tag to add (must be a LogixTag)
+ /// True if the tag was successfully added
+ /// This is used by the tag service to verify the
+ /// tag actually exists.
+ internal bool AddTag(LogixTag tag)
+ {
+ //Here is the procedure for adding a tag:
+ //1. Run some verification tests on the tag first
+ //2. First create a read request for a single tag
+ //3. Send the request to the PLC
+ //4. Verify the response and data type
+ //5. Store it in the tag
+
+ LogixTag lgxTag = tag as LogixTag;
+
+ if (lgxTag == null)
+ throw new ArgumentException(Resources.ErrorStrings.IncorrectArgTagType, "tag");
+
+ if (!ReadTag(lgxTag))
+ return false;
+
+ return true;
+ }
+
+ ///
+ /// Returns the structure handle for a particular tag
+ ///
+ /// Name of the tag
+ /// Structure handle or 0 if the tag wasn't found...
+ internal ushort GetStructureHandle(string tagName)
+ {
+ bool refreshed = false;
+
+ //lock (_lockObject)
+ {
+ //First we find the tag information..
+ if (_tagInfo == null)
+ {
+ RefreshInternalInfo();
+ refreshed = true;
+ }
+
+ LogixTagInfo ti = GetTagInfo(tagName);
+
+ if (ti == null && !refreshed)
+ {
+ RefreshInternalInfo();
+ ti = GetTagInfo(tagName);
+ }
+
+ if (ti == null)
+ return 0;
+ else
+ return (ushort)(ti.FullTypeInfo & 0x7FFF);
+ }
+
+ }
+
+ ///
+ /// Gets information for the specified tag
+ ///
+ /// Name of the tag
+ /// LogixTagInfo or null if not found
+ internal LogixTagInfo GetInfoForTag(string tagName)
+ {
+ bool refreshed = false;
+
+ //lock (_lockObject)
+ {
+ //First we find the tag information..
+ if (_tagInfo == null)
+ {
+ RefreshInternalInfo();
+ refreshed = true;
+ }
+
+ LogixTagInfo ti = GetTagInfo(tagName);
+
+ if (ti == null && !refreshed)
+ {
+ RefreshInternalInfo();
+ ti = GetTagInfo(tagName);
+ }
+
+ return ti;
+ }
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private LogixTagInfo GetTagInfo(string tagName)
+ {
+ for (int i = 0; i < _tagInfo.Count; i++)
+ {
+ if (_tagInfo[i].TagName.ToLower() == tagName.ToLower())
+ return _tagInfo[i];
+ }
+
+ return null;
+ }
+
+ private void RefreshInternalInfo()
+ {
+ _tagInfo = GetTagInfo();
+ }
+
+ private void PreOperationCallback()
+ {
+ if (_initializing)
+ return;
+
+ //lock (_lockObject)
+ {
+ if (_lastOperationTime.AddMilliseconds(60000) < DateTime.Now)
+ {
+ //We have to forward open again...
+ _initializing = true;
+ ConnectionManager.ConnectOverControlNet(_session, _path, 0);//0x43F4);
+ LogixTag fakeTag = new ControlLogixNET.LogixType.LogixDINT("Program:MainProgram", this);
+ _initializing = false;
+ }
+ }
+
+
+ _lastOperationTime = DateTime.Now;
+ }
+
+ private void PostOperationCallback()
+ {
+
+ }
+
+ private void SocketErrorCallback(SocketException se)
+ {
+ _errorCode = se.NativeErrorCode;
+ _errorString = Resources.ErrorStrings.SocketError;
+ }
+
+ private void UpdateStateTimer(object state)
+ {
+ //lock (_lockObject)
+ {
+ UpdateState();
+ }
+ }
+
+ private void UpdateState()
+ {
+ EncapsRRData rrData = new EncapsRRData();
+ rrData.CPF = new CommonPacket();
+ rrData.CPF.AddressItem = CommonPacketItem.GetNullAddressItem();
+
+ UnconnectedSend ucmm = new UnconnectedSend();
+ ucmm.RequestPath = CommonPaths.ConnectionManager;
+ ucmm.RoutePath = _path;
+ ucmm.Priority_TimeTick = _session.ConnectionParameters.PriorityAndTick;
+ ucmm.Timeout_Ticks = _session.ConnectionParameters.ConnectionTimeoutTicks;
+ ucmm.MessageRequest = new MR_Request();
+ ucmm.MessageRequest.Service = 0x03;
+ ucmm.MessageRequest.Request_Path = CommonPaths.IdentityObject;
+ ucmm.MessageRequest.Request_Data = new byte[] { 0x07, 0x00, 0x01, 0x00, 0x02, 0x00, 0x03, 0x00, 0x04, 0x00, 0x05, 0x00, 0x06, 0x00, 0x07, 0x00 };
+ rrData.CPF.DataItem = CommonPacketItem.GetUnconnectedDataItem(ucmm.Pack());
+
+ EncapsReply reply = _session.SendRRData(rrData.CPF.AddressItem, rrData.CPF.DataItem);
+
+ if (reply == null)
+ return;
+
+ if (reply.Status == 0)
+ {
+ UnconnectedSendReply ucReply = reply;
+
+ if (ucReply.GeneralStatus == 0x00)
+ {
+ UnconnectedSendReply_Success succReply = ucReply as UnconnectedSendReply_Success;
+ DecodeAttributeReply(succReply.ServiceResponse);
+ }
+ else
+ {
+ //Failed, for some reason...
+ //TODO: Decipher the code and add it to the error property
+ }
+ }
+
+ }
+
+ private List GetTagInfo()
+ {
+ CommonPacketItem addressItem = CommonPacketItem.GetNullAddressItem();
+
+ UnconnectedSend ucmm = new UnconnectedSend();
+ ucmm.RequestPath = CommonPaths.ConnectionManager;
+ ucmm.RoutePath = _path;
+ ucmm.Priority_TimeTick = _session.ConnectionParameters.PriorityAndTick;
+ ucmm.Timeout_Ticks = _session.ConnectionParameters.ConnectionTimeoutTicks;
+
+ ucmm.MessageRequest = new MR_Request();
+ ucmm.MessageRequest.Service = 0x55;
+ ucmm.MessageRequest.Request_Path = new byte[] { 0x20, 0x6B, 0x25, 0x00, 0x00, 0x00 }; //Last 2 bytes are the offset...
+ ucmm.MessageRequest.Request_Data = new byte[] { 0x04, 0x00, 0x02, 0x00, 0x07, 0x00, 0x08, 0x00, 0x01, 0x00 };
+
+ CommonPacketItem dataItem = CommonPacketItem.GetUnconnectedDataItem(ucmm.Pack());
+
+ EncapsReply reply = _session.SendRRData(addressItem, dataItem);
+
+ List retVal = new List();
+
+ if (reply.Status == 0)
+ {
+ UnconnectedSendReply ucReply = reply;
+
+ if (ucReply.GeneralStatus == 0x00 || ucReply.GeneralStatus == 0x06)
+ {
+ UnconnectedSendReply_Success succReply = ucReply as UnconnectedSendReply_Success;
+ List replyBytes = new List();
+ uint lastOffset = 0x0000;
+
+ retVal.AddRange(DecodeTagInfo(succReply.ServiceResponse, out lastOffset));
+
+ while (ucReply.GeneralStatus == 0x06)
+ {
+ lastOffset += 1;
+ if (lastOffset <= 0xFFFF)
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes((ushort)lastOffset), 0, ucmm.MessageRequest.Request_Path, 4, 2);
+ }
+ else
+ {
+ byte[] tempPath = new byte[8];
+ Buffer.BlockCopy(ucmm.MessageRequest.Request_Path, 0, tempPath, 0, 6);
+ Buffer.BlockCopy(BitConverter.GetBytes(lastOffset), 0, tempPath, 4, 4);
+ ucmm.MessageRequest.Request_Path = tempPath;
+ }
+
+ dataItem = CommonPacketItem.GetUnconnectedDataItem(ucmm.Pack());
+
+ reply = _session.SendRRData(addressItem, dataItem);
+
+ if (reply.Status == 0x00)
+ {
+ ucReply = reply;
+
+ if (ucReply.GeneralStatus == 0x00 || ucReply.GeneralStatus == 0x06)
+ {
+ succReply = ucReply as UnconnectedSendReply_Success;
+ retVal.AddRange(DecodeTagInfo(succReply.ServiceResponse, out lastOffset));
+ }
+ }
+ }
+ }
+ }
+
+ return retVal;
+ }
+
+ private List DecodeTagInfo(byte[] data, out uint lastOffset)
+ {
+ List retVal = new List();
+ lastOffset = 0;
+ for (int i = 0; i < data.Length; i++)
+ {
+ LogixTagInfo info = new LogixTagInfo();
+ lastOffset = BitConverter.ToUInt32(data, i);
+ i += 4;
+ info.MemoryAddress = lastOffset;
+ //Next two bytes are type code and dimension information
+ ushort typeInfo = BitConverter.ToUInt16(data, i);
+ i += 2;
+ info.FullTypeInfo = typeInfo;
+ //Need to decode the type info...
+ info.DataType = (ushort)(typeInfo & 0x00FF);
+ if ((ushort)(typeInfo & 0x8000) == 0x8000)
+ info.DataType = (ushort)(info.DataType | 0x8000);
+ int dimInfo = (typeInfo & 0x6000) >> 13;
+ info.Dimensions = (ushort)dimInfo;
+
+ //Skip 2 bytes
+ i += 2;
+ if (i > data.Length)
+ continue;
+
+ //Next 3 sets of 4 bytes are the dimension sizes
+ info.Dimension1Size = BitConverter.ToUInt32(data, i);
+ i += 4;
+ if (i > data.Length)
+ continue;
+ info.Dimension2Size = BitConverter.ToUInt32(data, i);
+ i += 4;
+ if (i > data.Length)
+ continue;
+ info.Dimension3Size = BitConverter.ToUInt32(data, i);
+ i += 4;
+ if (i > data.Length)
+ continue;
+
+ //Next 2 bytes are the length of the name...
+ int strSize = BitConverter.ToUInt16(data, i);
+ i += 2;
+ if (i > data.Length)
+ continue;
+ if (i + strSize > data.Length)
+ continue;
+ info.TagName = System.Text.ASCIIEncoding.ASCII.GetString(data, i, strSize);
+ i += strSize - 1;
+ retVal.Add(info);
+ }
+
+ return retVal;
+ }
+
+ private void DecodeAttributeReply(byte[] data)
+ {
+ //Ok, first is the number of attributes...
+ ushort numAttrs = BitConverter.ToUInt16(data, 0);
+
+ if (numAttrs != 0x07)
+ {
+ return; //Error, not enough attributes returned
+ }
+ int offset = 2;
+
+ //The format of the reply seems to be:
+ //[2:AttributeId][Variable Data]
+
+ for (int i = 0; i < 7; i++)
+ {
+ //Read 2 bytes for the Attribute Id:
+ ushort attribId = BitConverter.ToUInt16(data, offset);
+ offset += 2;
+
+ switch (attribId)
+ {
+ case 0x01: //VendorId
+ //Returns 2 words for some reason, data is in the lower
+ //word...
+ offset += 2;
+ _vendorId = BitConverter.ToUInt16(data, offset);
+ offset += 2;
+ break;
+ case 0x02: //Device Type
+ //Also 2 word reply...
+ offset += 2;
+ _deviceType = BitConverter.ToUInt16(data, offset);
+ offset += 2;
+ break;
+ case 0x03: //Product Code
+ //Again 2 words...
+ offset += 2;
+ _productCode = BitConverter.ToUInt16(data, offset);
+ offset += 2;
+ break;
+ case 0x04: //Revision
+ //Another 2 words
+ offset += 2;
+ _majorRevision = data[offset];
+ _minorRevision = data[offset + 1];
+ offset += 2;
+ break;
+ case 0x05: //Status
+ //2 words
+ offset += 2;
+ //This gets a little complicated here...
+ //State is stored in the upper nibble of the byte...
+ ProcessorState oldState = _state;
+ _state = (ProcessorState)(byte)((data[offset] & 0xF0) >> 4);
+ offset += 1;
+ //Fault state is in the lower nibble
+ ProcessorFaultState oldFault = _faultState;
+ _faultState = (ProcessorFaultState)(byte)((data[offset] & 0x0F));
+ //Keyswitch position is in the upper nibble
+ ProcessorKeySwitch oldKey = _keySw;
+ _keySw = (ProcessorKeySwitch)(byte)((data[offset] & 0xF0) >> 4);
+ offset += 1;
+
+ //Take care of events
+ if (_stateEventsEnabled)
+ {
+ if (oldState != _state)
+ RaiseProcessorStateChange(oldState, _state);
+
+ if (oldFault != _faultState)
+ RaiseFaultStateChange(oldFault, _faultState);
+
+ if (oldKey != _keySw)
+ RaiseKeySwitchChange(oldKey, _keySw);
+ }
+
+ break;
+ case 0x06: //Serial Number
+ //This returns 3 words, data is in the lower 2...
+ offset += 2;
+ _serialNumber = BitConverter.ToUInt32(data, offset);
+ offset += 4;
+ break;
+ case 0x07: //Product Name
+ //Skip one word
+ offset += 2;
+ //Get the size of the string...
+ byte sSize = data[offset];
+ offset += 1;
+ _productName = System.Text.ASCIIEncoding.ASCII.GetString(data, offset, sSize);
+ offset += sSize;
+ break;
+ default:
+ break; //This is an error
+ }
+ }
+ }
+
+ private void RaiseProcessorStateChange(ProcessorState oldState, ProcessorState newState)
+ {
+ if (ProcessorStateChanged != null)
+ ProcessorStateChanged(this, new LogixProcessorStateChangedEventArgs(oldState, newState));
+ }
+
+ private void RaiseFaultStateChange(ProcessorFaultState oldState, ProcessorFaultState newState)
+ {
+ if (FaultStateChanged != null)
+ FaultStateChanged(this, new LogixFaultStateChangedEventArgs(oldState, newState));
+ }
+
+ private void RaiseKeySwitchChange(ProcessorKeySwitch oldState, ProcessorKeySwitch newState)
+ {
+ if (KeySwitchChanged != null)
+ KeySwitchChanged(this, new LogixKeyChangedEventArgs(oldState, newState));
+ }
+
+ private void AutoUpdateTimer(object state)
+ {
+ _autoUpdateTimer.Change(0, _autoUpdateTime); //Disable the timer so we can't pile up on long operations
+ UpdateGroups();
+ _autoUpdateTimer.Change(_autoUpdateTime, _autoUpdateTime);
+ }
+
+ private void AutoUpdateThread()
+ {
+ bool quitFlag = false;
+
+ while (!quitFlag)
+ {
+ quitFlag = Thread.CurrentThread.ThreadState == System.Threading.ThreadState.AbortRequested ||
+ Thread.CurrentThread.ThreadState == System.Threading.ThreadState.StopRequested ||
+ !_autoUpdateEnabled;
+
+ if (quitFlag)
+ continue;
+
+ //lock (_lockObject)
+ {
+ foreach (LogixTagGroup group in _tagGroups.Values)
+ {
+ if (group.Enabled)
+ group.UpdateGroup();
+ }
+ }
+
+ Thread.Sleep(_autoUpdateTime);
+ }
+ }
+
+ private void StateUpdateThread()
+ {
+ bool quitFlag = false;
+
+ while (!quitFlag)
+ {
+ quitFlag = Thread.CurrentThread.ThreadState == System.Threading.ThreadState.AbortRequested ||
+ Thread.CurrentThread.ThreadState == System.Threading.ThreadState.StopRequested ||
+ !_stateUpdateEnabled;
+
+ if (quitFlag)
+ continue;
+
+ //lock (_lockObject)
+ {
+ //UpdateState();
+ }
+
+ Thread.Sleep(1000);
+ }
+ }
+
+ #endregion
+
+ }
+
+ #region Events
+
+ ///
+ /// LogixKeyPositionChangedEvent
+ ///
+ /// Processor raising the event
+ /// Event Arguments
+ public delegate void LogixKeyPositionChangedEvent(LogixProcessor sender, LogixKeyChangedEventArgs e);
+ ///
+ /// Event Arguments for the
+ ///
+ public class LogixKeyChangedEventArgs : EventArgs
+ {
+ ///
+ /// Gets the previous key position
+ ///
+ public ProcessorKeySwitch OldPosition { get; private set; }
+ ///
+ /// Gets the current key position
+ ///
+ public ProcessorKeySwitch NewPosition { get; private set; }
+
+ ///
+ /// Creates a new LogixKeyChangedEventArgs object
+ ///
+ /// Old Key Position
+ /// New Key Position
+ internal LogixKeyChangedEventArgs(ProcessorKeySwitch oldPos, ProcessorKeySwitch newPos)
+ {
+ OldPosition = oldPos;
+ NewPosition = newPos;
+ }
+ }
+
+ ///
+ /// LogixFaultStateChangedEvent
+ ///
+ /// Processor raising the event
+ /// Event Arguments
+ public delegate void LogixFaultStateChangedEvent(LogixProcessor sender, LogixFaultStateChangedEventArgs e);
+ ///
+ /// Event Arguments for the
+ ///
+ public class LogixFaultStateChangedEventArgs : EventArgs
+ {
+ ///
+ /// Gets the old fault state
+ ///
+ public ProcessorFaultState OldState { get; private set; }
+ ///
+ /// Gets the new fault state
+ ///
+ public ProcessorFaultState NewState { get; private set; }
+
+ ///
+ /// Creates a new LogixFaultStateChangedEventArgs object
+ ///
+ /// Old Fault State
+ /// New Fault State
+ internal LogixFaultStateChangedEventArgs(ProcessorFaultState oldFault, ProcessorFaultState newFault)
+ {
+ OldState = oldFault;
+ NewState = newFault;
+ }
+ }
+
+ ///
+ /// LogixProcessorStateChangedEvent
+ ///
+ /// Processor raising the event
+ /// Event Arguments
+ public delegate void LogixProcessorStateChangedEvent(LogixProcessor sender, LogixProcessorStateChangedEventArgs e);
+ ///
+ /// Event Arguments for the
+ ///
+ public class LogixProcessorStateChangedEventArgs : EventArgs
+ {
+ ///
+ /// Gets the previous processor state
+ ///
+ public ProcessorState OldState { get; private set; }
+ ///
+ /// Gets the current processor state
+ ///
+ public ProcessorState NewState { get; private set; }
+
+ internal LogixProcessorStateChangedEventArgs(ProcessorState oldState, ProcessorState newState)
+ {
+ OldState = oldState;
+ NewState = newState;
+ }
+ }
+
+ #endregion
+}
diff --git a/ControlLogixNET/LogixRead.cs b/ControlLogixNET/LogixRead.cs
new file mode 100644
index 0000000..68fddf7
--- /dev/null
+++ b/ControlLogixNET/LogixRead.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using EIPNET.CIP;
+
+namespace ControlLogixNET
+{
+ internal class LogixRead
+ {
+ public CIPType DataType { get; set; }
+ public int VarCount { get; set; }
+ public int TotalSize { get; set; }
+ public int ElementSize { get; set; }
+ public uint Mask { get; set; }
+ }
+}
diff --git a/ControlLogixNET/LogixServices.cs b/ControlLogixNET/LogixServices.cs
new file mode 100644
index 0000000..007cadb
--- /dev/null
+++ b/ControlLogixNET/LogixServices.cs
@@ -0,0 +1,255 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using EIPNET.CIP;
+using EIPNET;
+using EIPNET.EIP;
+
+namespace ControlLogixNET
+{
+ internal class LogixServices
+ {
+ public static ReadDataServiceRequest BuildLogixReadDataRequest(LogixTag tag, ushort number, out int requestSize)
+ {
+ if (tag.TagInfo != null && tag.TagInfo.IsStructure == false && tag.TagInfo.Dimensions == 0)
+ {
+ //Short version
+ ushort size = 0;
+ switch ((CIPType)tag.TagInfo.DataType)
+ {
+ case CIPType.BITS:
+ case CIPType.BOOL:
+ size = 1; break;
+ case CIPType.DINT:
+ size = 4; break;
+ case CIPType.INT:
+ size = 2; break;
+ case CIPType.LINT:
+ size = 8; break;
+ case CIPType.REAL:
+ size = 4; break;
+ case CIPType.SINT:
+ size = 1; break;
+ default:
+ return BuildLogixReadDataRequest(tag.Address, number, out requestSize);
+ }
+
+ return BuildShortReadDataRequest(tag.TagInfo.MemoryAddress, number, size, out requestSize);
+ }
+ else
+ {
+ //Long version
+ return BuildLogixReadDataRequest(tag.Address, number, out requestSize);
+ }
+ }
+
+ public static ReadDataServiceRequest BuildLogixReadDataRequest(string tagAddress, ushort number, out int requestSize)
+ {
+ byte[] newIOI = IOI.BuildIOI(tagAddress);
+
+ int pathLen = newIOI.Length;// IOI.BuildIOI(null, tagAddress);
+ ReadDataServiceRequest request = new ReadDataServiceRequest();
+ request.Service = (byte)ControlNetService.CIP_ReadData;
+ request.PathSize = (byte)(pathLen / 2);
+ byte[] path = new byte[pathLen];
+ Buffer.BlockCopy(newIOI, 0, path, 0, newIOI.Length);
+ //IOI.BuildIOI(path, tagAddress);
+ request.Path = path;
+ request.Elements = number;
+
+ requestSize = request.Size;
+
+ return request;
+ }
+
+ public static ReadDataServiceRequest BuildShortReadDataRequest(uint tagInstance, ushort number, ushort size, out int requestSize)
+ {
+ byte[] newIOI = IOI.BuildIOI(tagInstance);
+
+ int pathLen = newIOI.Length;
+ ReadDataServiceRequest request = new ReadDataServiceRequest();
+ request.Service = 0x4E;// (byte)ControlNetService.CIP_ReadData;
+ request.PathSize = 0x03;
+ byte[] path = new byte[] { 0x20, 0xB2, 0x25, 0x00, 0x21, 0x00 };
+ request.Path = path;
+ request.Elements = number;
+
+ byte[] addData = new byte[10 + newIOI.Length];
+ addData[0] = 0x02; addData[1] = 0x00; addData[2] = 0x01; addData[3] = 0x01; addData[4] = 0x01;
+ addData[5] = (byte)(size & 0xFF); addData[6] = (byte)((size & 0xFF00) >> 8);
+ addData[7] = 0x03; addData[8] = 0x20; addData[9] = 0x6B;
+ Buffer.BlockCopy(newIOI, 0, addData, 10, newIOI.Length);
+ request.AddtlData = addData;
+
+ requestSize = request.Size;
+
+ return request;
+ }
+
+ public static ReadDataServiceRequest BuildFragmentedReadDataRequest(string tagAddress, ushort number, uint offset, out int requestSize)
+ {
+ byte[] newIOI = IOI.BuildIOI(tagAddress);
+
+ int pathLen = newIOI.Length;// IOI.BuildIOI(null, tagAddress);
+ ReadDataServiceRequest request = new ReadDataServiceRequest();
+ request.Service = (byte)ControlNetService.CIP_ReadDataFragmented;
+ request.PathSize = (byte)(pathLen / 2);
+ request.IsFragmented = true;
+ request.DataOffset = offset;
+ byte[] path = new byte[pathLen];
+ Buffer.BlockCopy(newIOI, 0, path, 0, newIOI.Length);
+ //IOI.BuildIOI(path, tagAddress);
+ request.Path = path;
+ request.Elements = number;
+
+ requestSize = request.Size;
+
+ return request;
+ }
+
+ public static ReadDataServiceReply ReadLogixDataFragmented(SessionInfo si, string tagAddress, ushort elementCount, uint byteOffset)
+ {
+ int requestSize = 0;
+ ReadDataServiceRequest request = BuildFragmentedReadDataRequest(tagAddress, elementCount, byteOffset, out requestSize);
+
+ EncapsRRData rrData = new EncapsRRData();
+ rrData.CPF = new CommonPacket();
+ rrData.CPF.AddressItem = CommonPacketItem.GetConnectedAddressItem(si.ConnectionParameters.O2T_CID);
+ rrData.CPF.DataItem = CommonPacketItem.GetConnectedDataItem(request.Pack(), SequenceNumberGenerator.SequenceNumber);
+ rrData.Timeout = 2000;
+
+ EncapsReply reply = si.SendUnitData(rrData.CPF.AddressItem, rrData.CPF.DataItem);
+
+ if (reply == null)
+ return null;
+
+ if (reply.Status != 0 && reply.Status != 0x06)
+ {
+ //si.LastSessionError = (int)reply.Status;
+ return null;
+ }
+
+ return new ReadDataServiceReply(reply);
+ }
+
+ public static ReadDataServiceReply ReadLogixData(SessionInfo si, string tagAddress, ushort elementCount)
+ {
+ int requestSize = 0;
+ ReadDataServiceRequest request = BuildLogixReadDataRequest(tagAddress, elementCount, out requestSize);
+
+ EncapsRRData rrData = new EncapsRRData();
+ rrData.CPF = new CommonPacket();
+ rrData.CPF.AddressItem = CommonPacketItem.GetConnectedAddressItem(si.ConnectionParameters.O2T_CID);
+ rrData.CPF.DataItem = CommonPacketItem.GetConnectedDataItem(request.Pack(), SequenceNumberGenerator.SequenceNumber);
+ rrData.Timeout = 2000;
+
+ EncapsReply reply = si.SendUnitData(rrData.CPF.AddressItem, rrData.CPF.DataItem);
+
+ if (reply == null)
+ return null;
+
+ if (reply.Status != 0 && reply.Status != 0x06)
+ {
+ //si.LastSessionError = (int)reply.Status;
+ return null;
+ }
+
+ return new ReadDataServiceReply(reply);
+ }
+
+#if MONO
+ public static WriteDataServiceRequest BuildLogixWriteDataRequest(string tagAddress, ushort dataType, ushort elementCount, byte[] data)
+ {
+ return BuildLogixWriteDataRequest(tagAddress, dataType, elementCount, data, 0x0000);
+ }
+#endif
+#if MONO
+ public static WriteDataServiceRequest BuildLogixWriteDataRequest(string tagAddress, ushort dataType, ushort elementCount, byte[] data, ushort structHandle)
+#else
+ public static WriteDataServiceRequest BuildLogixWriteDataRequest(string tagAddress, ushort dataType, ushort elementCount, byte[] data, ushort structHandle = 0x0000)
+#endif
+ {
+ byte[] newIOI = IOI.BuildIOI(tagAddress);
+
+ int pathLen = newIOI.Length;
+ WriteDataServiceRequest request = new WriteDataServiceRequest();
+ request.Service = (byte)ControlNetService.CIP_WriteData;
+ request.PathSize = (byte)(pathLen / 2);
+ byte[] path = new byte[pathLen];
+ Buffer.BlockCopy(newIOI, 0, path, 0, newIOI.Length);
+ request.Path = path;
+ request.Elements = elementCount;
+ request.DataType = dataType;
+ request.StructHandle = structHandle;
+ request.Data = data;
+
+ return request;
+ }
+
+#if MONO
+ public static WriteDataServiceRequest BuildFragmentedWriteRequest(string tagAddress, ushort dataType, ushort elementCount, uint offset, byte[] data)
+ {
+ return BuildFragmentedWriteRequest(tagAddress, dataType, elementCount, offset, data, 0x0000);
+ }
+#endif
+#if MONO
+ public static WriteDataServiceRequest BuildFragmentedWriteRequest(string tagAddress, ushort dataType, ushort elementCount, uint offset, byte[] data, ushort structHandle)
+#else
+ public static WriteDataServiceRequest BuildFragmentedWriteRequest(string tagAddress, ushort dataType, ushort elementCount, uint offset, byte[] data, ushort structHandle = 0x0000)
+#endif
+ {
+ byte[] newIOI = IOI.BuildIOI(tagAddress);
+
+ int pathLen = newIOI.Length;
+ WriteDataServiceRequest request = new WriteDataServiceRequest();
+ request.Service = (byte)ControlNetService.CIP_WriteDataFragmented;
+ request.PathSize = (byte)(pathLen / 2);
+ byte[] path = new byte[pathLen];
+ Buffer.BlockCopy(newIOI, 0, path, 0, newIOI.Length);
+ request.Path = path;
+ request.Elements = elementCount;
+ request.DataType = dataType;
+ request.StructHandle = structHandle;
+ request.IsFragmented = true;
+ request.Offset = offset;
+ request.Data = data;
+
+ return request;
+ }
+
+#if MONO
+ public static WriteDataServiceReply WriteLogixData(SessionInfo si, string tagAddress, ushort dataType, ushort elementCount, byte[] data)
+ {
+ return WriteLogixData(si, tagAddress, dataType, elementCount, data, 0x0000);
+ }
+#endif
+#if MONO
+ public static WriteDataServiceReply WriteLogixData(SessionInfo si, string tagAddress, ushort dataType, ushort elementCount, byte[] data, ushort structHandle)
+#else
+ public static WriteDataServiceReply WriteLogixData(SessionInfo si, string tagAddress, ushort dataType, ushort elementCount, byte[] data, ushort structHandle = 0x0000)
+#endif
+ {
+ WriteDataServiceRequest request = BuildLogixWriteDataRequest(tagAddress, dataType, elementCount, data, structHandle);
+
+ EncapsRRData rrData = new EncapsRRData();
+ rrData.CPF = new CommonPacket();
+ rrData.CPF.AddressItem = CommonPacketItem.GetConnectedAddressItem(si.ConnectionParameters.O2T_CID);
+ rrData.CPF.DataItem = CommonPacketItem.GetConnectedDataItem(request.Pack(), SequenceNumberGenerator.SequenceNumber);
+ rrData.Timeout = 2000;
+
+ EncapsReply reply = si.SendUnitData(rrData.CPF.AddressItem, rrData.CPF.DataItem);
+
+ if (reply == null)
+ return null;
+
+ if (reply.Status != 0)
+ {
+ return null;
+ }
+
+ return new WriteDataServiceReply(reply);
+ }
+
+ }
+}
diff --git a/ControlLogixNET/LogixTag.cs b/ControlLogixNET/LogixTag.cs
new file mode 100644
index 0000000..0520d6b
--- /dev/null
+++ b/ControlLogixNET/LogixTag.cs
@@ -0,0 +1,683 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using ICommon;
+
+namespace ControlLogixNET
+{
+ ///
+ /// Represents a ControlLogix Tag
+ ///
+ public abstract class LogixTag : ITag
+ {
+
+ #region Fields
+
+ private bool _enabled;
+ private IDevice _parent;
+ private ushort _dataType;
+ private object _userData;
+ private string _address;
+ //private object _value;
+ private string _lastError;
+ private int _lastErrorNumber;
+ private TagQuality _tagQuality;
+ private DateTime _timeStamp;
+ private ushort _elements = 1;
+
+ //private object _pendingWriteValue;
+
+ private ReadDataServiceRequest _readRequest;
+ private WriteDataServiceRequest _writeRequest;
+ private ushort _dim1 = 0;
+ private ushort _dim2 = 0;
+ private ushort _dim3 = 0;
+
+ #endregion
+
+ #region Properties
+
+ ///The Enabled property turns on/off any updates to the tag.
+ ///
+ ///Setting the Enabled property to falls will disable any updates to the tag. When the property is disabled,
+ ///the tag will no longer recieve updates from the controller. This does not mean that the tag will be removed from
+ ///the controller update routine, it just means any updates will be ignored.
+ ///When this property is false, it will not raise any events and any writes to the Value property will not have
+ ///any effect (will not be cached).
+ ///
+ ///
+ ///This example shows how to disable the tag from receiving/sending any updates.
+ ///
+ ///public static class Program
+ ///{
+ /// public static void Main(string[] args)
+ /// {
+ /// LogixTag myTag = new LogixTag("sample");
+ /// myTag.Enabled = false;
+ /// }
+ ///}
+ ///
+ ///
+ public bool Enabled
+ {
+ get
+ {
+ return _enabled;
+ }
+ set
+ {
+ _enabled = false;
+ }
+ }
+
+ ///Gets the that this belongs to.
+ ///
+ ///This provides a reference from the to the that the tag belongs to. This
+ ///property is automatically set when a tag is added to a processor. By design, any can only belong to a single
+ /// at any time. Adding a to another will result in the
+ ///tag being removed from one before being added to the other.
+ ///
+ ///
+ ///This example shows how to get the processor from a .
+ ///
+ ///public static class Program
+ ///{
+ /// public static void Main(string[] args)
+ /// {
+ /// LogixTag myTag = new LogixTag("sample");
+ /// LogixProcessor tagProcessor = myTag.Device as LogixProcessor;
+ /// }
+ ///}
+ ///
+ ///
+ public IDevice Device
+ {
+ get { return _parent; }
+ internal set { _parent = value; }
+ }
+
+ ///Gets the associated with the tag.
+ ///
+ ///The data type of the tag is automatically determined on the first read of the tag. For this reason, the
+ ///tag cannot be written until it has been read at least once.
+ ///
+ ///
+ ///This example shows how to obtain the data type code of the tag and convert it to a .
+ ///
+ ///public static class Program
+ ///{
+ /// public static void Main(string[] args)
+ /// {
+ /// LogixTag myTag = new LogixTag("sample");
+ ///
+ /// //Here the tag is added to the processor, the processor
+ /// //will automatically read the tag before adding it to
+ /// //the optimized packet collection to verify that the
+ /// //tag exists and to obtain the data type.
+ ///
+ /// EIPNET.CIP.CIPType tagType = (EIPNET.CIP.CIPType)myTag.DataType;
+ /// }
+ ///}
+ ///
+ ///
+ public ushort DataType
+ {
+ get { return _dataType; }
+ }
+
+ ///Gets or Sets any object to be associated with the tag.
+ ///This property is available to the user to store any data. The data stays with the tag
+ ///in the system and is not modified outside of user code.
+ ///
+ ///This example shows how to store and retrieve data from the .
+ ///
+ ///public static class Program
+ ///{
+ /// public static void Main(string[] args)
+ /// {
+ /// LogixTag myTag = new LogixTag("sample");
+ ///
+ /// myTag.UserData = "Stored string";
+ /// string storedString = myTag.UserData as string;
+ ///
+ /// }
+ ///}
+ ///
+ ///
+ public object UserData
+ {
+ get
+ {
+ return _userData;
+ }
+ set
+ {
+ _userData = value;
+ }
+ }
+
+ ///Gets the address (tag name) associated with the tag.
+ ///
+ ///
+ ///When the tag is added to a device, the tag is read to automatically check for
+ ///the existance of the tag and the tag's data type. If the tag does not exist on the
+ ///processor, it will raise an error and be removed from the optimized packets.
+ ///
+ ///
+ ///
+ ///This example shows how to obtain the address property after the tag has been created.
+ ///
+ ///public static class Program
+ ///{
+ /// public static void Main(string[] args)
+ /// {
+ /// LogixTag myTag = new LogixTag("sample");
+ ///
+ /// string tagName = myTag.Address; //tagName now contains "sample"
+ /// }
+ ///}
+ ///
+ ///
+ public string Address
+ {
+ get { return _address; }
+ }
+
+ ///Gets a description of the last error associated with this tag.
+ ///
+ ///
+ ///The error descriptions are obtained by analyzing the CIP error code and obtaining the
+ ///description from the EIPNET library. The error descriptions are localized to the
+ ///current UI culture on the computer. The currently supported languages are EN and FR.
+ ///
+ ///
+ ///
+ ///The following example shows how to read the of a tag.
+ ///
+ ///public static class Program
+ ///{
+ /// public static void Main(string[] args)
+ /// {
+ /// LogixProcessor myPLC = new LogixProcessor("192.168.1.10", new byte[] { 1 });
+ /// myPLC.Connect();
+ ///
+ /// LogixTag myTag = new LogixTag("sample", myPLC);
+ /// myTag.Value = 100;
+ ///
+ /// myPLC.WriteTag(myTag);
+ ///
+ /// if (myTag.LastErrorNumber != 0)
+ /// {
+ /// Console.WriteLine("Error Occurred: " + myTag.LastError);
+ /// }
+ ///
+ /// }
+ ///}
+ ///
+ ///
+ public string LastError
+ {
+ get { return _lastError; }
+ internal set { _lastError = value; }
+ }
+
+ ///Gets the last error number associated with this tag.
+ ///
+ ///
+ ///The last error number is the CIP general status associated with the last operation
+ ///on the tag. If this number is anything other than 0, it indicates an error has
+ ///occurred. A text description of the error can be viewed by using the
+ ///property. The description text is localized.
+ ///
+ ///
+ ///
+ ///The following example shows how to use the LastErrorNumber property.
+ ///
+ ///public static class Program
+ ///{
+ /// public static void Main(string[] args)
+ /// {
+ /// LogixProcessor myPLC = new LogixProcessor("192.168.1.10", new byte[] { 1 });
+ /// myPLC.Connect();
+ ///
+ /// LogixTag myTag = new LogixTag("sample", myPLC);
+ /// myTag.Value = 100;
+ ///
+ /// myPLC.WriteTag(myTag);
+ ///
+ /// if (myTag.LastErrorNumber != 0)
+ /// {
+ /// Console.WriteLine("Error Occurred: " + myTag.LastError);
+ /// }
+ ///
+ /// }
+ ///}
+ ///
+ ///
+ public int LastErrorNumber
+ {
+ get { return _lastErrorNumber; }
+ internal set { _lastErrorNumber = value; }
+ }
+
+ ///Gets the of the tag.
+ ///
+ ///
+ ///The tag quality is initially until the tag
+ ///is read the first time (when it is added to a processor). If the tag can be successfully read,
+ ///the quality becomes , otherwise the quality is
+ ///.
+ ///
+ ///
+ public TagQuality Quality
+ {
+ get { return _tagQuality; }
+ }
+
+ ///Gets the timestamp of the most recent operation on the tag.
+ ///
+ ///
+ ///The timestamp is initially set to . The timestamp is the
+ ///most recent operation (successful or otherwise) that happened on the tag.
+ ///
+ ///
+ public DateTime TimeStamp
+ {
+ get { return _timeStamp; }
+ }
+
+ /// Gets the number of elements read for this tag
+ /// The number of elements is typically the size of the array to retrieve.
+ public ushort Elements
+ {
+ get { return _elements; }
+ }
+
+ ///
+ /// Gets the Logix type of the tag
+ ///
+ public abstract LogixType.LogixTypes LogixType { get; }
+
+ ///
+ /// Gets or Sets the value of the tag as an object
+ ///
+ public abstract object ValueAsObject { get; set; }
+
+ #endregion
+
+ #region Internal Properties
+
+ internal ushort Dim1 { get { return _dim1; } }
+ internal ushort Dim2 { get { return _dim2; } }
+ internal ushort Dim3 { get { return _dim3; } }
+ internal ushort StructHandle { get; set; }
+ internal LogixProcessor Processor { get { return (LogixProcessor)_parent; } }
+ internal LogixTagInfo TagInfo { get; set; }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Event raised when the tag value is updated
+ ///
+ public event TagValueUpdateEventHandler TagValueUpdated;
+
+ ///
+ /// Event raised when the tag quality changes
+ ///
+ public event TagQualityChangedEventHandler TagQualityChanged;
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new with the specified TagAddress.
+ ///
+ /// Address (Tag Name) of the item on the Processor.
+ internal LogixTag(string TagAddress)
+ {
+ _enabled = true;
+ _parent = null;
+ _dataType = 0x00;
+ _address = TagAddress;
+ _lastError = string.Empty;
+ _lastErrorNumber = 0;
+ _tagQuality = TagQuality.Unknown;
+ _timeStamp = DateTime.MinValue;
+ }
+
+ ///
+ /// Creates a new with the specified TagAddress and adds it to the .
+ ///
+ /// Address (Tag Name) of the item on the Processor.
+ /// Processor to add this LogixTag to.
+ internal LogixTag(string TagAddress, LogixProcessor Processor)
+ : this(TagAddress)
+ {
+ _parent = Processor;
+ //Processor.AddTag(this);
+
+ //Everything else is automatically set by the processor
+ }
+
+#if MONO
+
+ internal LogixTag(string TagAddress, LogixProcessor Processor, ushort ElementCount)
+ : this(TagAddress, Processor, ElementCount, null)
+ {
+ }
+
+#endif
+
+#if MONO
+ internal LogixTag(string TagAddress, LogixProcessor Processor, ushort ElementCount, object InitData)
+#else
+ ///
+ /// Creates a new with the specified TagAddress and adds it to the .
+ ///
+ /// Address (Tag Name) of the item on the Processor.
+ /// Processor to add this LogixTag to.
+ ///
+ internal LogixTag(string TagAddress, LogixProcessor Processor, ushort ElementCount, object InitData = null)
+#endif
+ {
+ _enabled = true;
+ _parent = Processor;
+ _dataType = 0x00;
+ _address = TagAddress;
+ _lastError = string.Empty;
+ _lastErrorNumber = 0;
+ _tagQuality = TagQuality.Unknown;
+ _timeStamp = DateTime.MinValue;
+ _elements = ElementCount;
+ Initialize(InitData);
+ //Processor.AddTag(this);
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Sets the dimensions of a multiple dimension array
+ ///
+ /// First Dimension
+ /// Second Dimension
+ public void SetMultipleDimensions(ushort Dimension1, ushort Dimension2)
+ {
+ _dim1 = Dimension1;
+ _dim2 = Dimension2;
+ }
+
+ ///
+ /// Sets the dimensions of a multiple dimension array
+ ///
+ /// First Dimension
+ /// Second Dimension
+ /// Third Dimension
+ public void SetMultipleDimensions(ushort Dimension1, ushort Dimension2, ushort Dimension3)
+ {
+ _dim1 = Dimension1;
+ _dim2 = Dimension2;
+ _dim3 = Dimension3;
+ }
+
+ #endregion
+
+ #region Internal Abstract Methods
+
+#if MONO
+ internal void OnDataUpdated(byte[] data)
+ {
+ OnDataUpdated(data, 0);
+ }
+#endif
+#if MONO
+ internal abstract void OnDataUpdated(byte[] data, uint byteOffset);
+#else
+ ///
+ /// Called when new data is obtained through a tag that is not an atomic type
+ ///
+ /// This is used to create custom data types in the processor. To
+ /// create a custom type, first inherit the ControlLogixNET.LogixTag class
+ /// and add your custom data type fields. Then override the OnDataUpdated
+ /// function to decode the data as required. The data returned in the array
+ /// is presented exactly as it is read from the processor. The arrangement
+ /// of data in the class depends on how the type is defined in the custom
+ /// data type.
+ /// Array of data returned by the processor.
+ /// Offset that this byte array starts in the entire object
+ internal abstract bool OnDataUpdated(byte[] data, uint byteOffset = 0);
+#endif
+ ///
+ /// Called when the tag needs to be written to the processor.
+ ///
+ /// This is used to create custom data types in the processor. To
+ /// create a custom type, first inherit the ControlLogixNET.Logix class
+ /// and add your custom data type fields. Then override the OnDataUpdated
+ /// and OnDataWrite functions. The data returned by the array is the packed
+ /// data to be written to the PLC. The format of the data depends on how
+ /// the data type is set up in the processor.
+ /// Custom data type packed into an array.
+ internal abstract byte[] OnDataWrite();
+
+ ///
+ /// Returns True if there is a cached pending write
+ ///
+ /// True if there is a pending write
+ internal abstract bool HasPendingWrite();
+
+ ///
+ /// Clears the pending write, this happens after the
+ /// request has been successfully processed by the
+ /// PLC.
+ ///
+ internal abstract void ClearPendingWrite();
+
+#if MONO
+ internal void Initialize()
+ {
+ Initialize(null);
+ }
+#endif
+#if MONO
+ internal abstract void Initialize(object InitData);
+#else
+ ///
+ /// Initializes internal data structures before the first read
+ ///
+ internal abstract void Initialize(object InitData = null);
+#endif
+
+ ///
+ /// Returns the write data and the header for the tag
+ ///
+ /// [Out] Header to be sent with each packet
+ /// [Out] Alignment that the bytes must be set on
+ ///
+ internal abstract byte[] GetWriteData(out int byteAlignment);
+
+ #endregion
+
+ #region Internal Methods
+
+ internal void SetTagError(byte[] value)
+ {
+ TagQuality oldQuality = _tagQuality;
+
+ switch (value[0])
+ {
+ case 0x04:
+ _lastError = Resources.ErrorStrings.MalformedIOI;
+ _lastErrorNumber = (int)LogixErrors.MalformedIOI;
+ _tagQuality = TagQuality.Bad;
+ break;
+ case 0x05:
+ _lastError = Resources.ErrorStrings.ItemNotFound;
+ _lastErrorNumber = (int)LogixErrors.ItemNotFound;
+ _tagQuality = TagQuality.Bad;
+ break;
+ case 0x0A:
+ _lastError = Resources.ErrorStrings.AttributeError;
+ _lastErrorNumber = (int)LogixErrors.AttributeError;
+ _tagQuality = TagQuality.Bad;
+ break;
+ case 0x13:
+ _lastError = Resources.ErrorStrings.NotEnoughData;
+ _lastErrorNumber = (int)LogixErrors.NotEnoughData;
+ _tagQuality = TagQuality.Bad;
+ break;
+ case 0x1C:
+ _lastError = Resources.ErrorStrings.InsufficientAttributes;
+ _lastErrorNumber = (int)LogixErrors.InsufficientAttributes;
+ _tagQuality = TagQuality.Bad;
+ break;
+ case 0x26:
+ _lastError = Resources.ErrorStrings.InvalidIOILength;
+ _lastErrorNumber = (int)LogixErrors.InvalidIOILength;
+ _tagQuality = TagQuality.Bad;
+ break;
+ case 0xFF:
+ if (value.Length != 4)
+ {
+ _lastError = Resources.ErrorStrings.Unknown;
+ _lastErrorNumber = (int)LogixErrors.Unknown;
+ _tagQuality = TagQuality.Bad;
+ }
+ else
+ {
+ switch (value[2])
+ {
+ case 0x05:
+ _lastError = Resources.ErrorStrings.AccessBeyondObject;
+ _lastErrorNumber = (int)LogixErrors.AccessBeyondObject;
+ _tagQuality = TagQuality.Bad;
+ break;
+ case 0x07:
+ _lastError = Resources.ErrorStrings.AbbreviatedTypeError;
+ _lastErrorNumber = (int)LogixErrors.AbbreviatedTypeError;
+ _tagQuality = TagQuality.Bad;
+ break;
+ case 0x04:
+ _lastError = Resources.ErrorStrings.BeginOffsetError;
+ _lastErrorNumber = (int)LogixErrors.BeginOffsetError;
+ _tagQuality = TagQuality.Bad;
+ break;
+ default:
+ _lastError = Resources.ErrorStrings.Unknown;
+ _lastErrorNumber = (int)LogixErrors.Unknown;
+ _tagQuality = TagQuality.Bad;
+ break;
+ }
+ }
+ break;
+ case 0x06:
+ case 0x00:
+ _lastErrorNumber = 0x00;
+ _lastError = string.Empty;
+ _tagQuality = TagQuality.Good;
+ break;
+ default:
+ _lastError = Resources.ErrorStrings.Unknown;
+ _lastErrorNumber = (int)LogixErrors.Unknown;
+ _tagQuality = TagQuality.Bad;
+ break;
+ }
+
+ if (_tagQuality != oldQuality)
+ RaiseTagQualityChanged(this);
+ }
+
+#if MONO
+ internal void UpdateValue(byte[] value)
+ {
+ UpdateValue(value, 0);
+ }
+#endif
+#if MONO
+ internal bool UpdateValue(byte[] value, uint byteOffset)
+#else
+ internal bool UpdateValue(byte[] value, uint byteOffset = 0)
+#endif
+ {
+ if (!Enabled)
+ return false;
+
+ //The structure is always a 2 byte type code followed by the
+ //actual data...
+ ushort typeCode = BitConverter.ToUInt16(value, 0);
+
+ if (_dataType == 0)
+ {
+ //Setting the type code for the first time...
+ _dataType = typeCode;
+ }
+ else
+ {
+ if (_dataType != typeCode)
+ {
+ _tagQuality = TagQuality.Bad;
+ _lastError = Resources.ErrorStrings.TypeMismatch + "0x" + typeCode.ToString("X2");
+ _lastErrorNumber = (int)LogixErrors.TypeMismatch;
+ throw new Exception(Resources.ErrorStrings.TypeMismatch + "0x" + typeCode.ToString("X2"));
+ }
+ }
+ byte[] temp = new byte[value.Length - 2];
+
+ Buffer.BlockCopy(value, 2, temp, 0, temp.Length);
+
+ _timeStamp = DateTime.Now;
+
+ return OnDataUpdated(temp, byteOffset);
+ }
+
+ internal void GenerateRequests()
+ {
+ int rSize = 0;
+ _readRequest = LogixServices.BuildLogixReadDataRequest(_address, _elements, out rSize);
+ _writeRequest = LogixServices.BuildLogixWriteDataRequest(_address, _dataType, _elements, new byte[] { });
+
+ }
+
+ internal WriteDataServiceRequest GetWriteRequest()
+ {
+ _writeRequest.Data = OnDataWrite();
+ return _writeRequest;
+ }
+
+ internal byte[] GetWriteData()
+ {
+ int align = 0;
+ return GetWriteData(out align);
+ }
+
+ internal void ClearWrite()
+ {
+ ClearPendingWrite();
+ }
+
+ internal void RaiseTagDataChanged(LogixTag sender)
+ {
+ if (TagValueUpdated != null)
+ TagValueUpdated(sender, new TagValueUpdateEventArgs(sender));
+ }
+
+ internal void RaiseTagQualityChanged(LogixTag sender)
+ {
+ if (TagQualityChanged != null)
+ TagQualityChanged(sender, new TagQualityChangedEventArgs(sender));
+ }
+
+ #endregion
+
+ #region Private Methods
+
+
+
+ #endregion
+
+ }
+}
diff --git a/ControlLogixNET/LogixTagGroup.cs b/ControlLogixNET/LogixTagGroup.cs
new file mode 100644
index 0000000..aae46d5
--- /dev/null
+++ b/ControlLogixNET/LogixTagGroup.cs
@@ -0,0 +1,645 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Diagnostics;
+using EIPNET.EIP;
+
+namespace ControlLogixNET
+{
+ ///
+ /// Represents a group of tags that can be manipulated as a group
+ ///
+ public sealed class LogixTagGroup
+ {
+
+ #region Internal Types
+
+ private class PacketMap
+ {
+ public int TagIndex { get; set; }
+ public List PacketIndex { get; set; }
+ public List ServiceIndex { get; set; }
+ public List Offsets { get; set; }
+ public int NumReplies { get; set; }
+ }
+
+ #endregion
+
+ #region Fields
+
+ private const int MAX_MSR_SIZE = 480;
+
+ private LogixProcessor _parent;
+ private string _groupName;
+ private bool _enabled;
+
+ private List _tags;
+
+ private List _readPackets;
+ private List _writePackets;
+ private List _msrPackets;
+ private List _writeMsrPackets;
+ private Dictionary> _byteReplies;
+
+ private object _lockObject = new object();
+
+ private Stopwatch _writeSw = new Stopwatch();
+ private Stopwatch _readSw = new Stopwatch();
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets the processor that this LogixTagGroup belongs to
+ ///
+ public LogixProcessor Processor
+ {
+ get { return _parent; }
+ }
+
+ ///
+ /// Gets the name of the LogixTagGroup
+ ///
+ public string GroupName
+ {
+ get { return _groupName; }
+ }
+
+ ///
+ /// Gets or Sets the Enabled property
+ ///
+ /// When this property is set to false, it will not update
+ /// any of the tags that belong to the group. If the tag belongs to
+ /// multiple groups, those groups may still update the tag unless the
+ /// property is set to false.
+ public bool Enabled
+ {
+ get { return _enabled; }
+ set { _enabled = value; }
+ }
+
+ ///
+ /// Gets the number of tags in this LogixTagGroup
+ ///
+ public int Count
+ {
+ get { return _tags.Count; }
+ }
+
+ ///
+ /// Gets the tag at the specified index
+ ///
+ /// Index of the tag
+ /// LogixTag at the specified index
+ /// Thrown when the index >= Count or the index < 0
+ public LogixTag this[int idx]
+ {
+ get
+ {
+ if (idx >= _tags.Count || idx < 0)
+ throw new IndexOutOfRangeException();
+
+ return _tags[idx];
+ }
+ }
+
+ ///
+ /// Returns the amount of time it took to perform the last read, in Milliseconds
+ ///
+ public double ReadTime
+ {
+ get { return _readSw.Elapsed.TotalMilliseconds; }
+ }
+
+ ///
+ /// Returns the amount of time it took to perform the last write, in Milliseconds
+ ///
+ public double WriteTime
+ {
+ get { return _writeSw.Elapsed.TotalMilliseconds; }
+ }
+
+ #endregion
+
+ #region Events
+
+ ///
+ /// Event is raised just before the group is written/read
+ ///
+ public event EventHandler UpdateStart;
+ ///
+ /// Event is raised just after the group is written/read
+ ///
+ public event EventHandler UpdateFinished;
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new LogixTagGroup on the specified processor. It is
+ /// recommended that you use
+ /// to create tag groups instead of this constructor so the processor
+ /// can manage the group.
+ ///
+ /// Processor that the group belongs to
+ /// Name of the group, must be unique on the processor
+ public LogixTagGroup(LogixProcessor Processor, string GroupName)
+ {
+ _parent = Processor;
+ _groupName = GroupName;
+
+ _enabled = true;
+ _tags = new List();
+ _readPackets = new List();
+ _msrPackets = new List();
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Adds a tag to this tag group
+ ///
+ /// Tag to be added
+ ///
+ /// It is recommended that you create your groups once, then
+ /// enable or disable them as you need them. Don't
+ /// frequently, as this may degrade the performance of your application.
+ ///
+ public void AddTag(LogixTag tag)
+ {
+ //lock (_lockObject)
+ {
+ int idx = _tags.Count;
+ _tags.Add(tag);
+ AddOrCreateReadPacket(_tags[idx], idx);
+ }
+ }
+
+ ///
+ /// Adds tags from an enumerable collection
+ ///
+ /// Collection that contains the tags
+ public void AddTags(IEnumerable tags)
+ {
+ foreach (LogixTag t in tags)
+ AddTag(t);
+ }
+
+ ///
+ /// Removes a tag from this tag group
+ ///
+ /// Tag to be removed
+ ///
+ /// Removing a tag triggers the tag group to re-optimize all
+ /// of the tag read requests. This can be a long process for groups
+ /// with large numbers of tags or tags that require a lot of data to
+ /// be transferred.
+ /// It is recommended that if you only need to remove a couple
+ /// tags from a group that you disable them instead. Disabled tags
+ /// will no longer update the value or send out update events.
+ ///
+ public void RemoveTag(LogixTag tag)
+ {
+ //lock (_lockObject)
+ {
+ _tags.Remove(tag);
+ RebuildReadRequests();
+ }
+ }
+
+ ///
+ /// Removes a collection of tags from the group
+ ///
+ /// Tags to be removed
+ ///
+ /// Removing tags triggers the group to re-optimize all
+ /// of the tag read requests. This can be a long process for groups
+ /// with large numbers of tags or tags that require a lot of data to
+ /// be transferred.
+ /// It is recommended that if you want to remove a large number
+ /// of tags, you use the method, which waits
+ /// until after all the tags are removed to reoptimize. The
+ /// function will reoptimize the read requests
+ /// after each tag is removed, which will degrade performance with
+ /// large tag groups.
+ ///
+ public void RemoveTags(IEnumerable tags)
+ {
+ foreach (LogixTag t in tags)
+ _tags.Remove(t);
+
+ RebuildReadRequests();
+ }
+
+ ///
+ /// Forces this tag group to write all the tags with pending update
+ /// values then read all the tags.
+ ///
+ public void UpdateGroup()
+ {
+ lock (_lockObject)
+ {
+ if (UpdateStart != null)
+ UpdateStart(this, EventArgs.Empty);
+
+ _writeSw.Reset();
+ _writeSw.Start();
+ WriteAll();
+ _writeSw.Stop();
+ _readSw.Reset();
+ _readSw.Start();
+ ReadAll();
+ _readSw.Stop();
+ }
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+
+
+ #endregion
+
+ #region Private Methods
+
+ private void ReadAll()
+ {
+ //The lock object should already be held...
+
+ List replies = new List();
+ List updatedTags = new List();
+
+ _byteReplies = new Dictionary>();
+
+ CommonPacketItem addressItem = CommonPacketItem.GetConnectedAddressItem(_parent.SessionInfo.ConnectionParameters.O2T_CID);
+
+ for (int i = 0; i < _msrPackets.Count; i++)
+ {
+ CommonPacketItem dataItem = CommonPacketItem.GetConnectedDataItem(_msrPackets[i].Pack(), SequenceNumberGenerator.SequenceNumber);
+
+ EncapsReply reply = _parent.SessionInfo.SendUnitData(addressItem, dataItem);
+
+ if (reply != null)
+ {
+ //We need to suck all the replies out of the packet, and request more data if there is more
+ MultiServiceReply msReply = new MultiServiceReply(reply);
+
+ DecodeReadPacket(i, msReply);
+
+ foreach (KeyValuePair> kvp in _byteReplies)
+ {
+ uint offset = 0;
+ for (int i2 = 0; i2 < kvp.Value.Count; i2++)
+ {
+ if (_tags[kvp.Key].UpdateValue(kvp.Value[i2], offset))
+ updatedTags.Add(_tags[kvp.Key]);
+ offset += (uint)(kvp.Value[i2].Length + 6);
+ }
+ }
+
+ if (UpdateFinished != null)
+ UpdateFinished(this, new UpdateFinishedEventArgs(updatedTags));
+
+ updatedTags.Clear();
+ _byteReplies.Clear();
+ }
+
+ }
+ }
+
+ private void DecodeReadPacket(int packetIdx, MultiServiceReply reply)
+ {
+ //Basically we need to find the tag(s) that this belongs to and add it to
+ //a list of reply bytes at the specified offset.
+
+ List packetTags = GetTagsForPacket(packetIdx);
+
+ for (int i = 0; i < packetTags.Count; i++)
+ {
+ PacketMap currentTag = packetTags[i];
+
+ //We have to figure out what service this tag is on
+ int idx = currentTag.PacketIndex.IndexOf(packetIdx);
+ int serviceNum = currentTag.ServiceIndex[idx];
+
+ //Now get the service data out and add it to the replies
+ if (reply.ServiceReplies.Count > serviceNum)
+ {
+ _tags[currentTag.TagIndex].SetTagError(reply.ServiceReplies[serviceNum].FullStatus);
+ if (!_byteReplies.ContainsKey(currentTag.TagIndex))
+ _byteReplies[currentTag.TagIndex] = new List();
+ _byteReplies[currentTag.TagIndex].Add(reply.ServiceReplies[serviceNum].ServiceData);
+ }
+ }
+ }
+
+ private List GetTagsForPacket(int packetIdx)
+ {
+ var packets =
+ from p in _readPackets
+ where p.PacketIndex.Contains(packetIdx)
+ select p;
+
+ if (packets == null)
+ return null;
+
+ return new List(packets);
+ }
+
+ private void WriteAll()
+ {
+ //First we are going to have to build the write requests...
+ _writeMsrPackets = new List();
+ BuildWriteRequests();
+
+ //Now we have to send them out...
+ for (int i = 0; i < _writeMsrPackets.Count; i++)
+ {
+ CommonPacketItem addressItem = CommonPacketItem.GetConnectedAddressItem(_parent.SessionInfo.ConnectionParameters.O2T_CID);
+ CommonPacketItem dataItem = CommonPacketItem.GetConnectedDataItem(_writeMsrPackets[i].Pack(), SequenceNumberGenerator.SequenceNumber);
+
+ EncapsReply reply = _parent.SessionInfo.SendUnitData(addressItem, dataItem);
+
+ if (reply != null)
+ {
+ MultiServiceReply msReply = new MultiServiceReply(reply);
+
+ DecodeWriteReply(i, msReply);
+ }
+ }
+ }
+
+ private void RebuildReadRequests()
+ {
+ _readPackets.Clear();
+ _msrPackets.Clear();
+
+ for (int i = 0; i < _tags.Count; i++)
+ {
+ AddOrCreateReadPacket(_tags[i], i);
+ }
+ }
+
+ private void AddOrCreateReadPacket(LogixTag tag, int idx)
+ {
+ //First we create the request...
+ int temp = 0;
+ ReadDataServiceRequest request = LogixServices.BuildLogixReadDataRequest(
+ tag.Address, tag.Elements, out temp);
+
+ //Now we read it from the PLC to find out if it's fragmented...
+ CommonPacketItem addressItem = CommonPacketItem.GetConnectedAddressItem(_parent.SessionInfo.ConnectionParameters.O2T_CID);
+ CommonPacketItem dataItem = CommonPacketItem.GetConnectedDataItem(request.Pack(), SequenceNumberGenerator.SequenceNumber);
+
+ EncapsReply reply = _parent.SessionInfo.SendUnitData(addressItem, dataItem);
+
+ if (reply != null)
+ {
+ //It's a good tag, let's figure out if it's fragmented...
+ ReadDataServiceReply rdReply = new ReadDataServiceReply(reply);
+
+ PacketMap pm = new PacketMap() { TagIndex = idx };
+ pm.PacketIndex = new List();
+ pm.ServiceIndex = new List();
+ pm.Offsets = new List();
+ pm.NumReplies = 1;
+
+ if (rdReply.Status == 0x06)
+ {
+ //Partial read... We'll have to request more data, but first let's make this packet
+ request = LogixServices.BuildFragmentedReadDataRequest(tag.Address, tag.Elements,
+ 0, out temp);
+ int[] status = FindPacketOrCreate(request.Pack(), (ushort)(rdReply.Data.Length + 2));
+ uint offset = (uint)rdReply.Data.Length;
+ pm.PacketIndex.Add(status[0]);
+ pm.ServiceIndex.Add(status[1]);
+ pm.Offsets.Add(0);
+
+ while (rdReply.Status == 0x06)
+ {
+ rdReply = LogixServices.ReadLogixDataFragmented(_parent.SessionInfo, tag.Address, tag.Elements,
+ offset);
+ request = LogixServices.BuildFragmentedReadDataRequest(tag.Address, tag.Elements,
+ offset, out temp);
+ status = FindPacketOrCreate(request.Pack(), (ushort)(rdReply.Data.Length + 2));
+ pm.PacketIndex.Add(status[0]);
+ pm.ServiceIndex.Add(status[1]);
+ offset += (uint)rdReply.Data.Length;
+ pm.Offsets.Add(offset);
+ pm.NumReplies++;
+ }
+ }
+ else if (rdReply.Status == 0x00 && rdReply.Data != null)
+ {
+ //Full read, create the packet...
+ int[] status = FindPacketOrCreate(request.Pack(), (ushort)(rdReply.Data.Length + 2));
+ pm.PacketIndex.Add(status[0]);
+ pm.ServiceIndex.Add(status[1]);
+ pm.Offsets.Add(0);
+ }
+
+ _readPackets.Add(pm);
+ }
+
+ }
+
+ private int[] FindPacketOrCreate(byte[] request, ushort replySize)
+ {
+ //We'll go through all the packets and find one that can fit both the
+ //request size and the reply size...
+ int[] retVal = new int[2]; //Packet Number, Service Number
+
+ for (int i = 0; i < _msrPackets.Count; i++)
+ {
+ if (_msrPackets[i].ReplySize + replySize <= MAX_MSR_SIZE)
+ {
+ //Possible candidate, let's see if we can put the request in it...
+ if (_msrPackets[i].Size + request.Length <= MAX_MSR_SIZE)
+ {
+ //Got one!
+ retVal[0] = i;
+ retVal[1] = _msrPackets[i].AddService(request);
+ _msrPackets[i].ReplySize += replySize;
+ return retVal;
+ }
+ }
+ }
+
+ //If we got here, we need to create one...
+ MultiServiceRequest newPacket = new MultiServiceRequest();
+ retVal[0] = _msrPackets.Count;
+ _msrPackets.Add(newPacket);
+ retVal[1] = _msrPackets[retVal[0]].AddService(request);
+ _msrPackets[retVal[0]].ReplySize += replySize;
+
+ return retVal;
+ }
+
+ private void BuildWriteRequests()
+ {
+ List writeRequests = new List();
+ List writeTags = new List();
+ List alignments = new List();
+
+ for (int i = 0; i < _tags.Count; i++)
+ {
+ if (_tags[i].HasPendingWrite())
+ {
+ writeTags.Add(i);
+ int align = 0;
+ writeRequests.Add(_tags[i].GetWriteData(out align));
+ alignments.Add(align);
+ }
+ }
+
+ _writePackets = new List();
+
+ //Ok, now we have all the data for the write, but we have to break it up into packets...
+ for (int i = 0; i < writeTags.Count; i++)
+ {
+ PacketMap pm = new PacketMap() { TagIndex = writeTags[i] };
+ pm.PacketIndex = new List();
+ pm.ServiceIndex = new List();
+ DistributeWriteRequest(pm, writeRequests[i], alignments[i]);
+ _writePackets.Add(pm);
+ }
+
+ //And that's it...
+ }
+
+ private void DistributeWriteRequest(PacketMap pm, byte[] requestData, int alignment)
+ {
+ //Ok, the request may have to be broken up into multiple requests...
+ if (requestData.Length > MAX_MSR_SIZE)
+ {
+ //This will have to be broken up... the overhead of an MSR packet is 12 bytes, so
+ //that means we can only fit up to MAX_MSR_SIZE - 12 bytes into a single packet.
+ //We need to figure out how much data we can stuff into one packet, then make
+ //multiple ones based on that...
+ //The first packet should always be the maximum size we can fit into one request...
+ WriteDataServiceRequest fragReq = LogixServices.BuildFragmentedWriteRequest(
+ _tags[pm.TagIndex].Address, _tags[pm.TagIndex].DataType, _tags[pm.TagIndex].Elements,
+ 0, null, _tags[pm.TagIndex].StructHandle);
+ int maxSize = MAX_MSR_SIZE - 12 - fragReq.Size;
+ int alignedSize = maxSize - (maxSize % alignment);
+ int remainingSize = requestData.Length;
+ uint offset = 0;
+
+ while (remainingSize > 0)
+ {
+ //We can fit up to alignedSize bytes in the array...
+ byte[] temp;
+ if (remainingSize < alignedSize)
+ {
+ temp = new byte[remainingSize];
+ remainingSize = 0;
+ }
+ else
+ {
+ temp = new byte[alignedSize];
+ remainingSize -= alignedSize;
+ }
+
+ Buffer.BlockCopy(requestData, (int)offset, temp, 0, temp.Length);
+
+ fragReq = LogixServices.BuildFragmentedWriteRequest(_tags[pm.TagIndex].Address,
+ _tags[pm.TagIndex].DataType, _tags[pm.TagIndex].Elements, offset, temp, _tags[pm.TagIndex].StructHandle);
+
+ offset += (uint)temp.Length;
+
+ FindWritePacketOrCreate(pm, fragReq);
+ }
+ }
+ else
+ {
+ //We can fit it into a single packet, we just need to find
+ //one
+ WriteDataServiceRequest request = LogixServices.BuildLogixWriteDataRequest(
+ _tags[pm.TagIndex].Address, _tags[pm.TagIndex].DataType, _tags[pm.TagIndex].Elements,
+ requestData, _tags[pm.TagIndex].StructHandle);
+
+ FindWritePacketOrCreate(pm, request);
+ }
+ }
+
+ private void FindWritePacketOrCreate(PacketMap pm, WriteDataServiceRequest request)
+ {
+ //The data is already broken up into appropriately sized chunks, we just have
+ //to find a place to put it...
+
+ int rSize = request.Size;
+ for (int i = 0; i < _writeMsrPackets.Count; i++)
+ {
+ if (_writeMsrPackets[i].Size + rSize < MAX_MSR_SIZE)
+ {
+ //This one will fit the packet
+ int svc = _writeMsrPackets[i].AddService(request.Pack());
+ pm.PacketIndex.Add(i);
+ pm.ServiceIndex.Add(i);
+ return;
+ }
+ }
+
+ //If we got here, we have to create a new one...
+ MultiServiceRequest msr = new MultiServiceRequest();
+ pm.PacketIndex.Add(_writeMsrPackets.Count);
+ _writeMsrPackets.Add(msr);
+ pm.ServiceIndex.Add(msr.AddService(request.Pack()));
+ }
+
+ private void DecodeWriteReply(int packetIdx, MultiServiceReply reply)
+ {
+ List writeTags = GetTagsForWritePacket(packetIdx);
+
+ for (int i = 0; i < writeTags.Count; i++)
+ {
+ PacketMap currentTag = writeTags[i];
+
+ int idx = currentTag.PacketIndex.IndexOf(packetIdx);
+ int serviceNum = currentTag.ServiceIndex[idx];
+
+ //TODO: This should look at all the replies for a particular tag before
+ //telling the tag to clear the pending write...
+
+ if (reply.ServiceReplies.Count > serviceNum)
+ {
+ _tags[currentTag.TagIndex].SetTagError(reply.ServiceReplies[serviceNum].FullStatus);
+ if (reply.ServiceReplies[serviceNum].Status == 0x00)
+ _tags[currentTag.TagIndex].ClearPendingWrite();
+ }
+ }
+ }
+
+ private List GetTagsForWritePacket(int packetIdx)
+ {
+ var packets =
+ from p in _writePackets
+ where p.PacketIndex.Contains(packetIdx)
+ select p;
+
+ if (packets == null)
+ return null;
+
+ return new List(packets);
+ }
+
+ #endregion
+
+ }
+
+ public sealed class UpdateFinishedEventArgs : EventArgs
+ {
+ public List UpdatedTags { get; private set; }
+
+ internal UpdateFinishedEventArgs(List UpdatedTags)
+ {
+ this.UpdatedTags = UpdatedTags;
+ }
+ }
+}
diff --git a/ControlLogixNET/LogixTagInfo.cs b/ControlLogixNET/LogixTagInfo.cs
new file mode 100644
index 0000000..22afd20
--- /dev/null
+++ b/ControlLogixNET/LogixTagInfo.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ ///
+ /// Holds information about a LogixTag in the processor
+ ///
+ public class LogixTagInfo
+ {
+ ///
+ /// Gets the memory address of the tag in the LogixProcessor
+ ///
+ internal uint MemoryAddress { get; set; }
+ ///
+ /// Gets the type information
+ ///
+ public ushort FullTypeInfo { get; internal set; }
+ ///
+ /// Gets the CIP data type for the tag, or the structure handle
+ ///
+ public ushort DataType { get; internal set; }
+ ///
+ /// Gets the number of dimensions for array tags
+ ///
+ public ushort Dimensions { get; internal set; }
+ ///
+ /// Gets true if the tag is a structure, false if it is atomic
+ ///
+ public bool IsStructure { get; internal set; }
+ ///
+ /// Gets the name of the tag on the
+ ///
+ public string TagName { get; internal set; }
+ ///
+ /// Gets the first dimension size for array tags.
+ ///
+ public uint Dimension1Size { get; internal set; }
+ ///
+ /// Gets the second dimension size for array tags. In order for
+ /// this to be valid, must be greater
+ /// than zero.
+ ///
+ public uint Dimension2Size { get; internal set; }
+ ///
+ /// Gets the third dimension size for array tags. In order for
+ /// this to be valid, must be greater
+ /// than zero.
+ ///
+ public uint Dimension3Size { get; internal set; }
+
+ internal LogixTagInfo() { }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.Append(TagName);
+
+ if (Dimensions > 0)
+ {
+ sb.Append("[");
+ sb.Append(Dimension1Size);
+ if (Dimensions > 1)
+ {
+ sb.Append(",");
+ sb.Append(Dimension2Size);
+ }
+
+ if (Dimensions > 2)
+ {
+ sb.Append(",");
+ sb.Append(Dimension3Size);
+ }
+ sb.Append("]");
+ }
+
+ sb.Append((IsStructure ? " (Structure)" : "(Not a Structure)"));
+ sb.Append(" Memory Address: " + MemoryAddress.ToString("X8"));
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/ControlLogixNET/LogixTypes.cs b/ControlLogixNET/LogixTypes.cs
new file mode 100644
index 0000000..a67baa4
--- /dev/null
+++ b/ControlLogixNET/LogixTypes.cs
@@ -0,0 +1,4471 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using EIPNET.CIP;
+using EIPNET.EIP;
+using System.Runtime.CompilerServices;
+
+namespace ControlLogixNET.LogixType
+{
+
+ ///
+ /// This namespace contains information about specific tag types and utilities to manipulate them
+ ///
+ [CompilerGenerated]
+ class NamespaceDoc { }
+
+ ///
+ /// Built-In Logix Data Types
+ ///
+ ///
+ /// This is as of Revision 16
+ ///
+ public enum LogixTypes : ushort
+ {
+ ///
+ /// Unknown type
+ ///
+ Unknown = 0,
+ ///
+ /// Logix String
+ ///
+ /// [1:Len][(82):Char]
+ String,
+ /*
+ ///
+ /// Alarm Information
+ ///
+ Alarm,
+ ///
+ /// Analog Alarm
+ ///
+ Alarm_Analog,
+ ///
+ /// Digital Alarm
+ ///
+ Alarm_Digital,
+ ///
+ /// Consumed Axis
+ ///
+ Axis_Consumed,
+ ///
+ /// Generic Axis
+ ///
+ Axis_Generic,
+ ///
+ /// Generic Drive Axis
+ ///
+ Axis_Generic_Drive,
+ ///
+ /// Generic Servo
+ ///
+ Axis_Servo,
+ ///
+ /// Generic Servo Drive
+ ///
+ Axis_Servo_Drive,
+ ///
+ /// Virtual Axis
+ ///
+ Axis_Virtual,
+ */
+ ///
+ /// Boolean Value
+ ///
+ Bool,
+ /*
+ ///
+ /// Cam
+ ///
+ Cam,
+ ///
+ /// Cam Profile
+ ///
+ Cam_Profile,
+ ///
+ /// Connection Status
+ ///
+ Connection_Status,
+ */
+ ///
+ /// Control
+ ///
+ Control,
+ /*
+ ///
+ /// Coordinate System
+ ///
+ Coordinate_System,
+ */
+ ///
+ /// Counter
+ ///
+ Counter,
+ /*
+ ///
+ /// DeadTime
+ ///
+ Deadtime,
+ ///
+ /// Derivative
+ ///
+ Derivative,
+ */
+ ///
+ /// Double Integer
+ ///
+ DInt,
+ /*
+ ///
+ /// Discrete 2 State
+ ///
+ Discrete_2State,
+ ///
+ /// Discrete 3 State
+ ///
+ Discrete_3State,
+ ///
+ /// Discrete Input
+ ///
+ Diverse_Input,
+ ///
+ /// Dominant Reset
+ ///
+ Dominant_Reset,
+ ///
+ /// Dominant Set
+ ///
+ Dominant_Set,
+ ///
+ /// Emergency Stop
+ ///
+ Emergency_Stop,
+ ///
+ /// Enable Pendant
+ ///
+ Enable_Pendant,
+ ///
+ /// External Routine Control
+ ///
+ Ext_Routine_Control,
+ ///
+ /// External Routine Parameters
+ ///
+ Ext_Routine_Parameters,
+ ///
+ /// (Function Block) Bit Field Distribute
+ ///
+ FBD_Bit_Field_Distribute,
+ ///
+ /// (Function Block) Boolean And
+ ///
+ FBD_Boolean_And,
+ ///
+ /// (Function Block) Boolean Not
+ ///
+ FBD_Boolean_Not,
+ ///
+ /// (Function Block) Boolean Or
+ ///
+ FBD_Boolean_Or,
+ ///
+ /// (Function Block) Boolean XOr
+ ///
+ FBD_Boolean_XOr,
+ ///
+ /// (Function Block) Compare
+ ///
+ FBD_Compare,
+ ///
+ /// (Function Block) Convert
+ ///
+ FBD_Convert,
+ ///
+ /// (Function Block) Counter
+ ///
+ FBD_Counter,
+ ///
+ /// (Function Block) Limit
+ ///
+ FBD_Limit,
+ ///
+ /// (Function Block) Logical
+ ///
+ FBD_Logical,
+ ///
+ /// (Function Block) Masked Move
+ ///
+ FBD_Masked_Move,
+ ///
+ /// (Function Block) Mask Equal
+ ///
+ FBD_Mask_Equal,
+ ///
+ /// (Function Block) Math
+ ///
+ FBD_Math,
+ ///
+ /// (Function Block) Math (Advanced)
+ ///
+ FBD_Math_Advanced,
+ ///
+ /// (Function Block) One Shot
+ ///
+ FBD_OneShot,
+ ///
+ /// (Function Block) Timer
+ ///
+ FBD_Timer,
+ ///
+ /// (Function Block) Truncate
+ ///
+ FBD_Truncate,
+ ///
+ /// High Pass Filter
+ ///
+ Filter_High_Pass,
+ ///
+ /// Low Pass Filter
+ ///
+ Filter_Low_Pass,
+ ///
+ /// Notch Filter
+ ///
+ Filter_Notch,
+ ///
+ /// Five Position Mode Selector
+ ///
+ Five_Pos_Mode_Selector,
+ ///
+ /// Flip Flop (D)
+ ///
+ Flip_Flop_D,
+ ///
+ /// Flip Flop (JK)
+ ///
+ Flip_Flop_JK,
+ ///
+ /// Function Generator
+ ///
+ Function_Generator,
+ ///
+ /// High-Low Limit
+ ///
+ HL_Limit,
+ */
+ ///
+ /// Integer
+ ///
+ Int,
+ /*
+ ///
+ /// Integrator
+ ///
+ Integrator,
+ ///
+ /// Lead/Lag
+ ///
+ Lead_Lag,
+ ///
+ /// Lead/Lag Second Order
+ ///
+ Lead_Lag_Sec_Order,
+ ///
+ /// Light Curtain
+ ///
+ Light_Curtain,
+ */
+ ///
+ /// Long Integer
+ ///
+ LInt,
+ /*
+ ///
+ /// Maximum Capture
+ ///
+ Maximum_Capture,
+ ///
+ /// Message
+ ///
+ Message,
+ ///
+ /// Minimum Capture
+ ///
+ Minimum_Capture,
+ ///
+ /// Motion Group
+ ///
+ Motion_Group,
+ ///
+ /// Motion Instruction
+ ///
+ Motion_Instruction,
+ ///
+ /// Moving Average
+ ///
+ Moving_Average,
+ ///
+ /// Moving Standard Deviation
+ ///
+ Moving_Std_Dev,
+ ///
+ /// Multiplexor
+ ///
+ Multiplexer,
+ ///
+ /// Output Cam
+ ///
+ Output_Cam,
+ ///
+ /// Output Compensation
+ ///
+ Output_Compensation,
+ ///
+ /// Phase
+ ///
+ Phase,
+ ///
+ /// Phase Compensation
+ ///
+ Phase_Compensation,
+ ///
+ /// Proportional-Integral-Derivative
+ ///
+ PID,
+ ///
+ /// PID Auto-Tune
+ ///
+ PIDE_AutoTune,
+ ///
+ /// Proportional-Integral-Deriviative Enhanced
+ ///
+ PID_Enhanced,
+ ///
+ /// Position Property
+ ///
+ Position_Prop,
+ ///
+ /// Prop Int
+ ///
+ Prop_Int,
+ ///
+ /// Pulse Multiplier
+ ///
+ Pulse_Multiplier,
+ ///
+ /// Ramp/Soak
+ ///
+ Ramp_Soak,
+ ///
+ /// Rate Limiter
+ ///
+ Rate_Limiter,
+ */
+ ///
+ /// Real (Single Precision Float)
+ ///
+ Real,
+ /*
+ ///
+ /// Redundant Input
+ ///
+ Redundant_Input,
+ ///
+ /// Redundant Output
+ ///
+ Redundant_Output,
+ ///
+ /// Scale
+ ///
+ Scale,
+ ///
+ /// Second Order Controller
+ ///
+ Sec_Order_Controller,
+ ///
+ /// Select
+ ///
+ Select,
+ ///
+ /// Selectable Negate
+ ///
+ Selectable_Negate,
+ ///
+ /// Selected Summer
+ ///
+ Selected_Summer,
+ ///
+ /// Select Enhanced
+ ///
+ Select_Enhanced,
+ ///
+ /// Serial Port Control
+ ///
+ Serial_Port_Control,
+ ///
+ /// (Sequential Function) Action
+ ///
+ SFC_Action,
+ ///
+ /// (Sequential Function) Step
+ ///
+ SFC_Step,
+ ///
+ /// Sequential Function) Stop
+ ///
+ SFC_Stop,
+ */
+ ///
+ /// Single Integer
+ ///
+ SInt,
+ /*
+ ///
+ /// Split Range
+ ///
+ Split_Range,
+ ///
+ /// S-Curve
+ ///
+ S_Curve,
+ */
+ ///
+ /// Timer
+ ///
+ Timer,
+ /*
+ ///
+ /// Totalizer
+ ///
+ Totalizer,
+ ///
+ /// Two Hand Run Station
+ ///
+ Two_Hand_Run_Station,
+ ///
+ /// Up-Down Accumulator
+ ///
+ Up_Down_Accum,
+ */
+ ///
+ /// User Defined Type
+ ///
+ User_Defined
+ }
+
+ ///
+ /// Helper class to create tags without knowing the underlying LOGIX type
+ ///
+ public static class LogixTagFactory
+ {
+#if MONO
+ public static LogixTag CreateTag(string Address, LogixProcessor Processor)
+ {
+ return CreateTag(Address, Processor, 1);
+ }
+#endif
+#if MONO
+ public static LogixTag CreateTag(string Address, LogixProcessor Processor, ushort Elements)
+#else
+ ///
+ /// Creates a LogixTag with the correct type
+ ///
+ /// Address (Tag Name)
+ /// Processor the tag belongs to
+ /// Number of elements to read
+ /// LogixTag of the correct underlying type
+ public static LogixTag CreateTag(string Address, LogixProcessor Processor, ushort Elements = 1)
+#endif
+ {
+ //We'll do this by creating the tag first, then seeing what type it is
+ //and returning it to the user...
+
+ return CreateQuickTag(Address, Processor, Elements);
+ }
+
+ internal static LogixTag CreateLongTag(string Address, LogixProcessor Processor, ushort Elements)
+ {
+ //It doesn't exist in the list, maybe it was added later?
+ //lock (Processor.SyncRoot)
+ {
+ ReadDataServiceReply lgxRead = LogixServices.ReadLogixData(
+ Processor.SessionInfo, Address, Elements);
+
+ if (lgxRead == null)
+ return null;
+
+ if (lgxRead.Data == null)
+ return null;
+
+ if (lgxRead.Status != 0x00 && lgxRead.Status != 0x06)
+ {
+ return null;
+ }
+
+ //The first two bytes are the type...
+ CIPType tagType = (CIPType)lgxRead.DataType;
+
+ if (tagType == CIPType.STRUCT)
+ {
+ //We need to build an information object about this type
+ //The first two bytes are a handle to the structure...
+ ushort structureId = Processor.GetStructureHandle(Address);
+
+ //Now we can send a request to get the attributes...
+ TemplateInfo ti = ReadStructAttributes(structureId, Processor);
+
+ return CreateStructTag(Address, Processor, Elements, ti);
+ }
+ else
+ {
+ switch (tagType)
+ {
+ case CIPType.BITS:
+ case CIPType.BOOL:
+ return new LogixBOOL(Address, Processor, Elements);
+ case CIPType.SINT:
+ return new LogixSINT(Address, Processor, Elements);
+ case CIPType.INT:
+ return new LogixINT(Address, Processor, Elements);
+ case CIPType.DINT:
+ return new LogixDINT(Address, Processor, Elements);
+ case CIPType.LINT:
+ return new LogixLINT(Address, Processor, Elements);
+ case CIPType.REAL:
+ return new LogixREAL(Address, Processor, Elements);
+ default:
+ break; //Unknown type...
+ }
+ }
+
+ return null;
+ }
+ }
+
+ internal static LogixTag CreateQuickTag(string Address, LogixProcessor Processor, ushort Elements)
+ {
+ if (Address.Contains(".") || Address.Contains("["))
+ {
+ return CreateLongTag(Address, Processor, Elements);
+ }
+
+ LogixTagInfo ti = Processor.GetInfoForTag(Address);
+
+ if (ti == null)
+ return null;
+
+ if (ti.IsStructure)
+ {
+ //We need to build an information object about this type
+ //The first two bytes are a handle to the structure...
+ ushort structureId = Processor.GetStructureHandle(Address);
+
+ //Now we can send a request to get the attributes...
+ TemplateInfo tempInfo = ReadStructAttributes(structureId, Processor);
+
+ LogixTag st = CreateStructTag(Address, Processor, Elements, tempInfo);
+ st.TagInfo = ti;
+ }
+ else
+ {
+ LogixTag at = null;
+
+ switch ((CIPType)ti.DataType)
+ {
+ case CIPType.BITS:
+ case CIPType.BOOL:
+ at = new LogixBOOL(Address, Processor, Elements); break;
+ case CIPType.SINT:
+ at = new LogixSINT(Address, Processor, Elements); break;
+ case CIPType.INT:
+ at = new LogixINT(Address, Processor, Elements); break;
+ case CIPType.DINT:
+ at = new LogixDINT(Address, Processor, Elements); break;
+ case CIPType.LINT:
+ at = new LogixLINT(Address, Processor, Elements); break;
+ case CIPType.REAL:
+ at = new LogixREAL(Address, Processor, Elements); break;
+ default:
+ break; //Unknown type...
+ }
+
+ if (at != null)
+ {
+ at.TagInfo = ti;
+ return at;
+ }
+ }
+
+ return null;
+ }
+
+ internal static TemplateInfo GetTemplateInfo(string Address, LogixProcessor Processor, ushort Elements)
+ {
+ //lock (Processor.SyncRoot)
+ {
+ ReadDataServiceReply lgxRead = LogixServices.ReadLogixData(
+ Processor.SessionInfo, Address, Elements);
+
+ if (lgxRead == null)
+ return null;
+
+ if (lgxRead.Data == null)
+ return null;
+
+ if (lgxRead.Status != 0x00 && lgxRead.Status != 0x06)
+ {
+ return null;
+ }
+
+ //The first two bytes are the type...
+ CIPType tagType = (CIPType)lgxRead.DataType;
+
+ if (tagType == CIPType.STRUCT)
+ {
+ //We need to build an information object about this type
+ //The first two bytes are a handle to the structure...
+ ushort structureId = Processor.GetStructureHandle(Address);
+
+ //Now we can send a request to get the attributes...
+ TemplateInfo ti = ReadStructAttributes(structureId, Processor);
+
+ return ti;
+ }
+ }
+
+ return null;
+ }
+
+ private static TemplateInfo ReadStructAttributes(ushort structureId, LogixProcessor processor)
+ {
+ //First we have to get the template info...
+ GetStructAttribsRequest attribsReq = new GetStructAttribsRequest(structureId);
+ CommonPacketItem addressItem = CommonPacketItem.GetNullAddressItem();
+
+ UnconnectedSend ucmm = new UnconnectedSend();
+ ucmm.Priority_TimeTick = 0x07;
+ ucmm.Timeout_Ticks = 0x9B;
+ ucmm.RoutePath = processor.Path;
+ ucmm.MessageRequest = new MR_Request();
+ ucmm.MessageRequest.Request_Path = new byte[] { 0x20, 0x6C, 0x25, 0x00, (byte)((structureId & 0x00FF)), (byte)((structureId & 0xFF00) >> 8) };
+ ucmm.MessageRequest.Service = 0x03;
+ ucmm.MessageRequest.Request_Data = new byte[] { 0x04, 0x00, 0x04, 0x00, 0x03, 0x00, 0x02, 0x00, 0x01, 0x00 };
+ ucmm.RequestPath = CommonPaths.ConnectionManager;
+
+ CommonPacketItem dataItem = CommonPacketItem.GetUnconnectedDataItem(ucmm.Pack());
+
+ EncapsReply reply = processor.SessionInfo.SendRRData(addressItem, dataItem);
+
+ if (reply.Status != 0x00)
+ return null;
+
+ //We have to get the data out...
+ EncapsRRData rrData = new EncapsRRData();
+ CommonPacket cpf = new CommonPacket();
+ int temp = 0;
+ rrData.Expand(reply.EncapsData, 0, out temp);
+ cpf = rrData.CPF;
+
+ byte[] replyData = new byte[28];
+ Buffer.BlockCopy(cpf.DataItem.Data, 4, replyData, 0, 28);
+
+ GetStructAttribsReply structAttribs = new GetStructAttribsReply(replyData);
+
+ //Great... now we can request the structure template and be able to read it!
+ ucmm.MessageRequest.Service = 0x4C;
+
+ //We can only read about 480 bytes at a time, so we may have to break it up...
+ uint bytesRemaining = (structAttribs.TemplateSize - 5) * 4;
+ uint offset = 0;
+ List structInfoBytes = new List();
+
+ while (bytesRemaining > 0)
+ {
+ ushort bytesToRead;
+ if (bytesRemaining < 480)
+ {
+ bytesToRead = (ushort)bytesRemaining;
+ bytesRemaining = 0;
+ }
+ else
+ {
+ bytesToRead = 480;
+ bytesRemaining -= 480;
+ }
+
+ byte[] tempB = new byte[6];
+ Buffer.BlockCopy(BitConverter.GetBytes(offset), 0, tempB, 0, 4);
+ Buffer.BlockCopy(BitConverter.GetBytes(bytesToRead), 0, tempB, 4, 2);
+
+ ucmm.MessageRequest.Request_Data = tempB;
+
+ dataItem = CommonPacketItem.GetUnconnectedDataItem(ucmm.Pack());
+
+ reply = processor.SessionInfo.SendRRData(addressItem, dataItem);
+
+ if (reply.Status != 0x00)
+ continue;
+
+ rrData.Expand(reply.EncapsData, 0, out temp);
+ cpf = rrData.CPF;
+
+ //get the data out...
+ tempB = new byte[cpf.DataItem.Data.Length - 4];
+ Buffer.BlockCopy(cpf.DataItem.Data, 4, tempB, 0, cpf.DataItem.Data.Length - 4);
+ structInfoBytes.AddRange(tempB);
+ offset += bytesToRead;
+ }
+
+ //Now we have all the data!!!!
+
+ return new TemplateInfo(structInfoBytes.ToArray(), structAttribs);
+ }
+
+ private static LogixTag CreateStructTag(string Address, LogixProcessor Processor, ushort Elements, TemplateInfo Template)
+ {
+ if (Template.TemplateName == "COUNTER")
+ {
+ return new LogixCOUNTER(Address, Template, Processor, Elements);
+ }
+ else if (Template.TemplateName == "CONTROL")
+ {
+ return new LogixCONTROL(Address, Template, Processor, Elements);
+ }
+ else if (Template.TemplateName == "TIMER")
+ {
+ return new LogixTIMER(Address, Template, Processor, Elements);
+ }
+ else if (Template.TemplateName == "ASCIISTRING82")
+ {
+ return new LogixSTRING(Address, Template, Processor, Elements);
+ }
+ else
+ {
+ //Return as a UDT...
+ return new LogixUDT(Address, Template, Processor, Elements);
+ }
+ }
+ }
+
+ #region Atomic Types
+
+ ///
+ /// Represents a type of BOOL in a Logix Processor
+ ///
+ public sealed class LogixBOOL : LogixTag
+ {
+
+ #region Fields
+
+ private bool[] _state = new bool[] { false };
+ private bool[] _pendingWriteValue = new bool[] { false };
+ private bool[] _hasPendingWrite = new bool[] { false };
+ private bool _lastWriteChanged = false;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or Sets the state of the tag
+ ///
+ public bool Value
+ {
+ get { return _state[0]; }
+ set
+ {
+ _pendingWriteValue[0] = value;
+ _hasPendingWrite[0] = true;
+ }
+ }
+
+ ///
+ /// Gets or Sets the element of the tag at the specified index
+ ///
+ /// Index of the element to get or set
+ /// True or False
+ /// Thrown when index >= Elements * 32 or index < 0
+ public bool this[int index]
+ {
+ get
+ {
+ if (index >= Elements * 32)
+ throw new IndexOutOfRangeException();
+
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ return _state[index];
+ }
+ set
+ {
+ if (index >= Elements * 32)
+ throw new IndexOutOfRangeException();
+
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ _pendingWriteValue[index] = value;
+ _hasPendingWrite[index] = true;
+ }
+ }
+
+ ///
+ /// Gets or Sets the value at the specified row and column
+ ///
+ /// Row to set/get
+ /// Column to set/get
+ /// Value at the specified row and column
+ public bool this[int row, int col]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets or Sets the value at the specified row, column, and element
+ ///
+ /// Row to set/get
+ /// Column to set/get
+ /// Element to set/get
+ /// Value at the specified row, column, and element
+ public bool this[int row, int col, int element]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0 || Dim3 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * col * element;
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the Logix tag type
+ ///
+ public override LogixTypes LogixType
+ {
+ get { return LogixTypes.Bool; }
+ }
+
+ ///
+ /// Gets or Sets the LogixBOOL as an object
+ ///
+ ///
+ ///
+ /// When using this to set the array value, the passed in array must be a single
+ /// dimension array with the same number of elements as the LogixBOOL tag.
+ ///
+ ///
+ /// It is also good to note that this function makes use of the class to convert
+ /// from the passed in object to the appropriate data type. If the object is an array, this function is
+ /// called on every element of the array, not just the first element. This can be a performance burden if
+ /// setting large numbers of elements in this fashion. In that case it is recommended to cast the LogixTag
+ /// to the correct object and use the indexor to set the values.
+ ///
+ ///
+ /// Thrown when the value type cannot be converted to a boolean
+ /// Thrown when the number of elements in the value array does not equal
+ public override object ValueAsObject
+ {
+ get
+ {
+ if (Elements == 1)
+ return Value;
+ else
+ return _state;
+ }
+ set
+ {
+ if (Elements == 1)
+ {
+ try
+ {
+ Value = Convert.ToBoolean(value);
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ else
+ {
+ Array valueArray = value as Array;
+ if (valueArray != null)
+ {
+ if (valueArray.Length != Elements)
+ throw new ArrayLengthException(Resources.ErrorStrings.ArrayLengthException);
+ try
+ {
+ for (int i = 0; i < valueArray.Length; i++)
+ {
+ this[i] = Convert.ToBoolean(valueArray.GetValue(i));
+ }
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+
+ }
+ else
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new BOOL Logix Tag
+ ///
+ /// Address (Tag Name) in the processor.
+ public LogixBOOL(string TagAddress)
+ : base(TagAddress)
+ {
+
+ }
+
+ ///
+ /// Creates a new BOOL Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ public LogixBOOL(string TagAddress, LogixProcessor Processor)
+ : base(TagAddress, Processor)
+ {
+
+ }
+
+ ///
+ /// Creates a new BOOL Logix Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ /// Number of elements to read
+ public LogixBOOL(string TagAddress, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, Processor, ElementCount)
+ {
+
+ }
+
+ #endregion
+
+ #region Public Methods
+
+
+
+ #endregion
+
+ #region Internal Methods
+
+#if MONO
+ internal override void Initialize(object InitData)
+#else
+ internal override void Initialize(object InitData = null)
+#endif
+ {
+ _state = new bool[Elements * 32];
+ _pendingWriteValue = new bool[Elements * 32];
+ _hasPendingWrite = new bool[Elements * 32];
+ }
+
+#if MONO
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset)
+#else
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset = 0)
+#endif
+ {
+ bool canRaiseEvent = false;
+ //data should be at least as long as the state...
+ if (DataType == 0xD3)
+ {
+ for (int iNum = 0; iNum < data.Length / 4; iNum++)
+ {
+ int source = BitConverter.ToInt32(data, iNum * 4);
+ //The data returned is a 32 bit value representing up to
+ //32 boolean values starting at the index of 0...
+ int iStart = iNum * 32;
+ for (int i = iStart; i < (iStart + 32); i++)
+ {
+ bool newVal = ((source >> i) & 1) == 1;
+ _lastWriteChanged |= (newVal != _state[i]);
+ _state[i] = newVal;
+ if (i == _state.Length)
+ canRaiseEvent = true;
+ }
+ }
+
+ }
+ else if (DataType == 0xC1)
+ {
+ bool newState = (data[0] == 0xFF ? true : false);
+ _lastWriteChanged = _state[0] != newState;
+ _state[0] = newState;
+ canRaiseEvent = true;
+ }
+ else
+ {
+ LastErrorNumber = (int)LogixErrors.TypeMismatch;
+ LastError = Resources.ErrorStrings.TypeMismatch;
+ }
+
+ if (canRaiseEvent && _lastWriteChanged)
+ {
+ _lastWriteChanged = false;
+ RaiseTagDataChanged(this);
+ return true;
+ }
+
+ return false;
+ }
+
+ internal override byte[] OnDataWrite()
+ {
+ //This has to be packed into 32 bit values...
+ uint[] packed = new uint[Elements];
+
+ for (int iNum = 0; iNum < Elements; iNum++)
+ {
+ uint currentNum = 0;
+ int bitNum = 0;
+
+ for (int i = iNum * 32; i < (iNum * 32) + 32; i++)
+ {
+ if (_hasPendingWrite[i])
+ {
+ if (_pendingWriteValue[i])
+ currentNum |= ((uint)0x01 << bitNum);
+ }
+ else
+ {
+ if (_state[i])
+ currentNum |= ((uint)0x01 << bitNum);
+ }
+ bitNum++;
+ }
+
+ packed[iNum] = currentNum;
+ }
+
+ byte[] retVal = new byte[4 * Elements];
+
+ for (int i = 0; i < Elements; i++)
+ Buffer.BlockCopy(BitConverter.GetBytes(packed[i]), 0, retVal, i * 4, 4);
+
+ return retVal;
+ }
+
+ internal override void ClearPendingWrite()
+ {
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ _state[i] = _pendingWriteValue[i];
+ _hasPendingWrite[i] = false;
+ }
+ this.RaiseTagDataChanged(this);
+ }
+
+ internal override bool HasPendingWrite()
+ {
+ for (int i = 0; i < _hasPendingWrite.Length; i++)
+ {
+ if (_hasPendingWrite[i])
+ return true;
+ }
+
+ return false;
+ }
+
+ internal override byte[] GetWriteData(out int byteAlignment)
+ {
+ if (Elements > 0)
+ byteAlignment = 4;
+ else
+ byteAlignment = 1;
+
+ return OnDataWrite();
+ }
+
+ #endregion
+
+ #region Conversion Methods
+
+ public static implicit operator bool(LogixBOOL tag)
+ {
+ return tag[0];
+ }
+
+ public override string ToString()
+ {
+ if (Elements == 1)
+ {
+ if (Value)
+ return "True";
+ else
+ return "False";
+ }
+ else
+ {
+ StringBuilder sb = new StringBuilder();
+ if (Dim1 == 0)
+ {
+ //One dimensional, nothing set
+ sb.Append("[");
+ for (int i = 0; i < Elements; i++)
+ {
+ sb.Append(this[i] ? "True" : "False");
+ if (i < Elements - 1)
+ sb.Append(",");
+ }
+ sb.Append("]");
+ }
+ else if (Dim2 == 0)
+ {
+ //One dimensional, set dimension
+ sb.Append("[");
+ for (int i = 0; i < Elements; i++)
+ {
+ sb.Append(this[i] ? "True" : "False");
+ if (i < Elements - 1)
+ sb.Append(",");
+ }
+ sb.Append("]");
+ }
+ else if (Dim3 == 0)
+ {
+ //Two dimensional, set dimensions
+ sb.Append("[");
+ for (int row = 0; row < Dim1; row++)
+ {
+ sb.Append("[");
+ for (int col = 0; col < Dim2; col++)
+ {
+ sb.Append(this[row, col] ? "True" : "False");
+ if (col < Dim2 - 1)
+ sb.Append(",");
+ }
+ sb.AppendLine("]");
+ }
+ sb.Append("]");
+ }
+ else
+ {
+ //Three dimensional, set dimensions
+ sb.Append("[");
+ for (int row = 0; row < Dim1; row++)
+ {
+ sb.Append("[");
+ for (int col = 0; col < Dim2; col++)
+ {
+ sb.Append("[");
+ for (int el = 0; el < Dim3; el++)
+ {
+ sb.Append(this[row, col, el] ? "True" : "False");
+ if (el < Dim3 - 1)
+ sb.Append(",");
+ }
+ sb.AppendLine("]");
+ }
+ sb.AppendLine("]");
+ }
+ sb.Append("]");
+ }
+
+ return sb.ToString();
+ }
+ }
+
+ #endregion
+
+ }
+
+ ///
+ /// Represents a type of DINT in a Logix Processor
+ ///
+ public sealed class LogixDINT : LogixTag
+ {
+
+ #region Fields
+
+ private int[] _value = new int[] { 0 };
+ private int[] _pendingWriteValue = new int[] { 0 };
+ private bool[] _hasPendingWrite = new bool[] { false };
+ private bool _lastWriteChanged = false;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or Sets the Value of the tag
+ ///
+ public int Value
+ {
+ get
+ {
+ return _value[0];
+ }
+ set
+ {
+ _pendingWriteValue[0] = value;
+ _hasPendingWrite[0] = true;
+ }
+ }
+
+ ///
+ /// Gets or Sets the tag at the specified index
+ ///
+ /// Index to get or set
+ /// Value at the specified index
+ /// Thrown when index >= Elements or index < 0
+ public int this[int index]
+ {
+ get
+ {
+ if (index >= Elements)
+ throw new IndexOutOfRangeException();
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ return _value[index];
+ }
+ set
+ {
+ if (index >= Elements)
+ throw new IndexOutOfRangeException();
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ _pendingWriteValue[index] = value;
+ _hasPendingWrite[index] = true;
+ }
+ }
+
+ ///
+ /// Gets the specified value at the row and column index
+ ///
+ /// Row index
+ /// Column index
+ /// Value at the specified row and column
+ /// Thrown when the dimensions haven't been set before accessing this property
+ /// Thrown when row*col > Elements or either index < 0
+ public int this[int row, int col]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the specified value at the row, column, and element index
+ ///
+ /// Row index
+ /// Column Index
+ /// Element Index
+ /// Value at the specified row, column and element index
+ /// Thrown when the dimensions haven't been set before accessing this property
+ /// Thrown when row*col > Elements or either index < 0
+ public int this[int row, int col, int element]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0 || Dim3 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the Logix type of the tag
+ ///
+ public override LogixTypes LogixType
+ {
+ get { return LogixTypes.DInt; }
+ }
+
+ ///
+ /// Gets or Sets the LogixDINT as an object
+ ///
+ ///
+ ///
+ /// When using this to set the array value, the passed in array must be a single
+ /// dimension array with the same number of elements as the LogixDINT tag.
+ ///
+ ///
+ /// It is also good to note that this function makes use of the class to convert
+ /// from the passed in object to the appropriate data type. If the object is an array, this function is
+ /// called on every element of the array, not just the first element. This can be a performance burden if
+ /// setting large numbers of elements in this fashion. In that case it is recommended to cast the LogixTag
+ /// to the correct object and use the indexor to set the values.
+ ///
+ ///
+ /// Thrown when the value type cannot be converted to an int
+ /// Thrown when the number of elements in the value array does not equal
+ public override object ValueAsObject
+ {
+ get
+ {
+ if (Elements == 1)
+ return Value;
+ else
+ return _value;
+ }
+ set
+ {
+ if (Elements == 1)
+ {
+ try
+ {
+ Value = Convert.ToInt32(value);
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ else
+ {
+ //Ok, first we have to verify that it's an array
+ Array valueArray = value as Array;
+ if (valueArray != null)
+ {
+ if (valueArray.Length != Elements)
+ throw new ArrayLengthException(Resources.ErrorStrings.ArrayLengthException);
+ try
+ {
+ for (int i = 0; i < valueArray.Length; i++)
+ {
+ this[i] = Convert.ToInt32(valueArray.GetValue(i));
+ }
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+
+ }
+ else
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new DINT Logix Tag
+ ///
+ /// Address (Tag Name) in the processor.
+ public LogixDINT(string TagAddress)
+ : base(TagAddress)
+ {
+
+ }
+
+ ///
+ /// Creates a new DINT Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ public LogixDINT(string TagAddress, LogixProcessor Processor)
+ : base(TagAddress, Processor)
+ {
+
+ }
+
+ ///
+ /// Creates a new DINT Logix Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ /// Number of elements to read
+ public LogixDINT(string TagAddress, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, Processor, ElementCount)
+ {
+
+ }
+
+ #endregion
+
+ #region Public Methods
+
+
+
+ #endregion
+
+ #region Internal Methods
+
+#if MONO
+ internal override void Initialize(object InitData)
+#else
+ internal override void Initialize(object InitData = null)
+#endif
+ {
+ _value = new int[Elements];
+ _pendingWriteValue = new int[Elements];
+ _hasPendingWrite = new bool[Elements];
+ }
+
+#if MONO
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset)
+#else
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset = 0)
+#endif
+ {
+ //Let's mask off the lower part of the data type...
+ ushort tempType = (ushort)(DataType & 0xFFF);
+ bool canRaiseEvent = false;
+
+ if (tempType == 0xC4)
+ {
+ //32 Bit Signed Int
+
+ uint startElement = byteOffset / 4;
+ int dataPos = 0;
+ int temp;
+
+ //Might be an array though...
+ for (uint i = startElement; i < Elements; i++)
+ {
+ temp = BitConverter.ToInt32(data, dataPos);
+ _lastWriteChanged |= (temp != _value[i]);
+ _value[i] = temp;
+ dataPos += 4;
+ if (i == Elements - 1)
+ canRaiseEvent = true;
+ if (dataPos >= data.Length)
+ break;
+ }
+ }
+ else
+ {
+ //Who knows???
+ LastError = Resources.ErrorStrings.TypeMismatch + "0x" + tempType.ToString("X2");
+ LastErrorNumber = (int)LogixErrors.TypeMismatch;
+ }
+
+ if (canRaiseEvent && _lastWriteChanged)
+ {
+ _lastWriteChanged = false;
+ RaiseTagDataChanged(this);
+ return true;
+ }
+
+ return false;
+ }
+
+ internal override byte[] OnDataWrite()
+ {
+ byte[] retVal = new byte[Elements * 4];
+
+ for (int i = 0; i < Elements; i++)
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes(_value[i]), 0, retVal, i * 4, 4);
+ }
+
+ return retVal;
+ }
+
+ internal override bool HasPendingWrite()
+ {
+ return PendingWriteCount() > 0;
+ }
+
+ internal override void ClearPendingWrite()
+ {
+ for (int i = 0; i < Elements; i++)
+ _hasPendingWrite[i] = false;
+ }
+
+ internal override byte[] GetWriteData(out int byteAlignment)
+ {
+ List retVal = new List();
+ byteAlignment = 4;
+
+ if (Elements == 1)
+ {
+ retVal.AddRange(BitConverter.GetBytes(_pendingWriteValue[0]));
+ }
+ else
+ {
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ retVal.AddRange(BitConverter.GetBytes(_pendingWriteValue[i]));
+ else
+ retVal.AddRange(BitConverter.GetBytes(_value[i]));
+ }
+
+ }
+
+ return retVal.ToArray();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private int PendingWriteCount()
+ {
+ int count = 0;
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ count++;
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region Conversion Methods
+
+ public static implicit operator int(LogixDINT tag)
+ {
+ return tag[0];
+ }
+
+ #endregion
+
+ }
+
+ ///
+ /// Represents a type of INT in a Logix Processor
+ ///
+ public sealed class LogixINT : LogixTag
+ {
+
+ #region Fields
+
+ private short[] _value = new short[] { 0 };
+ private short[] _pendingWriteValue = new short[] { 0 };
+ private bool[] _hasPendingWrite = new bool[] { false };
+ private bool _lastWriteChanged = false;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or Sets the Value of the tag
+ ///
+ public short Value
+ {
+ get
+ {
+ return _value[0];
+ }
+ set
+ {
+ _pendingWriteValue[0] = value;
+ _hasPendingWrite[0] = true;
+ }
+ }
+
+ ///
+ /// Gets or Sets the tag at the specified index
+ ///
+ /// Index to get or set
+ /// Value at the specified index
+ /// Thrown when index >= Elements or index < 0
+ public short this[int index]
+ {
+ get
+ {
+ if (index >= Elements)
+ throw new IndexOutOfRangeException();
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ return _value[index];
+ }
+ set
+ {
+ if (index >= Elements)
+ throw new IndexOutOfRangeException();
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ _pendingWriteValue[index] = value;
+ _hasPendingWrite[index] = true;
+ }
+ }
+
+ ///
+ /// Gets the specified value at the row and column index
+ ///
+ /// Row index
+ /// Column index
+ /// Value at the specified row and column
+ /// Thrown when the dimensions haven't been set before accessing this property
+ /// Thrown when row*col > Elements or either index < 0
+ public short this[int row, int col]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the specified value at the row, column, and element index
+ ///
+ /// Row index
+ /// Column Index
+ /// Element Index
+ /// Value at the specified row, column and element index
+ /// Thrown when the dimensions haven't been set before accessing this property
+ /// Thrown when row*col > Elements or either index < 0
+ public short this[int row, int col, int element]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0 || Dim3 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the Logix type of the tag
+ ///
+ public override LogixTypes LogixType
+ {
+ get { return LogixTypes.Int; }
+ }
+
+ ///
+ /// Gets or Sets the LogixINT as an object
+ ///
+ ///
+ ///
+ /// When using this to set the array value, the passed in array must be a single
+ /// dimension array with the same number of elements as the LogixINT tag.
+ ///
+ ///
+ /// It is also good to note that this function makes use of the class to convert
+ /// from the passed in object to the appropriate data type. If the object is an array, this function is
+ /// called on every element of the array, not just the first element. This can be a performance burden if
+ /// setting large numbers of elements in this fashion. In that case it is recommended to cast the LogixTag
+ /// to the correct object and use the indexor to set the values.
+ ///
+ ///
+ /// Thrown when the value type cannot be converted to a short
+ /// Thrown when the number of elements in the value array does not equal
+ public override object ValueAsObject
+ {
+ get
+ {
+ if (Elements == 1)
+ return Value;
+ else
+ return _value;
+ }
+ set
+ {
+ if (Elements == 1)
+ {
+ try
+ {
+ Value = Convert.ToInt16(value);
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ else
+ {
+ //Ok, first we have to verify that it's an array
+ Array valueArray = value as Array;
+ if (valueArray != null)
+ {
+ if (valueArray.Length != Elements)
+ throw new ArrayLengthException(Resources.ErrorStrings.ArrayLengthException);
+ try
+ {
+ for (int i = 0; i < valueArray.Length; i++)
+ {
+ this[i] = Convert.ToInt16(valueArray.GetValue(i));
+ }
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+
+ }
+ else
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new INT Logix Tag
+ ///
+ /// Address (Tag Name) in the processor.
+ public LogixINT(string TagAddress)
+ : base(TagAddress)
+ {
+
+ }
+
+ ///
+ /// Creates a new INT Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ public LogixINT(string TagAddress, LogixProcessor Processor)
+ : base(TagAddress, Processor)
+ {
+
+ }
+
+ ///
+ /// Creates a new INT Logix Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ /// Number of elements to read
+ public LogixINT(string TagAddress, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, Processor, ElementCount)
+ {
+
+ }
+
+ #endregion
+
+ #region Public Methods
+
+
+
+ #endregion
+
+ #region Protected / Internal Methods
+
+#if MONO
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset)
+#else
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset = 0)
+#endif
+ {
+ //Let's mask off the lower part of the data type...
+ ushort tempType = (ushort)(DataType & 0xFFF);
+ bool canRaiseEvent = false;
+
+ if (tempType == 0xC3)
+ {
+ //16 Bit Signed Int
+
+ uint startElement = byteOffset / 2;
+ int dataPos = 0;
+ short temp = 0;
+
+ //Might be an array though...
+ for (uint i = startElement; i < Elements; i++)
+ {
+ temp = BitConverter.ToInt16(data, dataPos);
+ _lastWriteChanged |= (temp != _value[i]);
+ _value[i] = temp;
+ dataPos += 4;
+ if (i == Elements - 1)
+ canRaiseEvent = true;
+ if (dataPos >= data.Length)
+ break;
+ }
+ }
+ else
+ {
+ //Who knows???
+ LastError = Resources.ErrorStrings.TypeMismatch + "0x" + tempType.ToString("X2");
+ LastErrorNumber = (int)LogixErrors.TypeMismatch;
+ }
+
+ if (canRaiseEvent && _lastWriteChanged)
+ {
+ _lastWriteChanged = false;
+ RaiseTagDataChanged(this);
+ return true;
+ }
+ return false;
+ }
+
+ internal override byte[] OnDataWrite()
+ {
+ byte[] retVal = new byte[Elements * 2];
+
+ for (int i = 0; i < Elements; i++)
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes(_value[i]), 0, retVal, i * 2, 2);
+ }
+
+ return retVal;
+ }
+
+ internal override bool HasPendingWrite()
+ {
+ return PendingWriteCount() > 0;
+ }
+
+ internal override void ClearPendingWrite()
+ {
+ for (int i = 0; i < Elements; i++)
+ _hasPendingWrite[i] = false;
+ }
+
+#if MONO
+ internal override void Initialize(object InitData)
+#else
+ internal override void Initialize(object InitData = null)
+#endif
+ {
+ _value = new short[Elements];
+ _pendingWriteValue = new short[Elements];
+ _hasPendingWrite = new bool[Elements];
+ }
+
+ internal override byte[] GetWriteData(out int byteAlignment)
+ {
+ List retVal = new List();
+ byteAlignment = 2;
+
+ if (Elements == 1)
+ {
+ retVal.AddRange(BitConverter.GetBytes(_pendingWriteValue[0]));
+ }
+ else
+ {
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ retVal.AddRange(BitConverter.GetBytes(_pendingWriteValue[i]));
+ else
+ retVal.AddRange(BitConverter.GetBytes(_value[i]));
+ }
+
+ }
+
+ return retVal.ToArray();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private int PendingWriteCount()
+ {
+ int count = 0;
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ count++;
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region Conversion Methods
+
+ public static implicit operator short(LogixINT tag)
+ {
+ return tag[0];
+ }
+
+ #endregion
+
+ }
+
+ ///
+ /// Represents a type of SINT in a Logix Processor
+ ///
+ public sealed class LogixSINT : LogixTag
+ {
+
+ #region Fields
+
+ private sbyte[] _value = new sbyte[] { 0 };
+ private sbyte[] _pendingWriteValue = new sbyte[] { 0 };
+ private bool[] _hasPendingWrite = new bool[] { false };
+ private bool _lastWriteChanged = false;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or Sets the Value of the tag
+ ///
+ public sbyte Value
+ {
+ get
+ {
+ return _value[0];
+ }
+ set
+ {
+ _pendingWriteValue[0] = value;
+ _hasPendingWrite[0] = true;
+ }
+ }
+
+ ///
+ /// Gets or Sets the tag at the specified index
+ ///
+ /// Index to get or set
+ /// Value at the specified index
+ /// Thrown when index >= Elements or index < 0
+ public sbyte this[int index]
+ {
+ get
+ {
+ if (index >= Elements)
+ throw new IndexOutOfRangeException();
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ return _value[index];
+ }
+ set
+ {
+ if (index >= Elements)
+ throw new IndexOutOfRangeException();
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ _pendingWriteValue[index] = value;
+ _hasPendingWrite[index] = true;
+ }
+ }
+
+ ///
+ /// Gets the specified value at the row and column index
+ ///
+ /// Row index
+ /// Column index
+ /// Value at the specified row and column
+ /// Thrown when the dimensions haven't been set before accessing this property
+ /// Thrown when row*col > Elements or either index < 0
+ public sbyte this[int row, int col]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the specified value at the row, column, and element index
+ ///
+ /// Row index
+ /// Column Index
+ /// Element Index
+ /// Value at the specified row, column and element index
+ /// Thrown when the dimensions haven't been set before accessing this property
+ /// Thrown when row*col > Elements or either index < 0
+ public sbyte this[int row, int col, int element]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0 || Dim3 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the Logix type of the tag
+ ///
+ public override LogixTypes LogixType
+ {
+ get { return LogixTypes.SInt; }
+ }
+
+ ///
+ /// Gets or Sets the LogixSINT as an object
+ ///
+ ///
+ ///
+ /// When using this to set the array value, the passed in array must be a single
+ /// dimension array with the same number of elements as the LogixSINT tag.
+ ///
+ ///
+ /// It is also good to note that this function makes use of the class to convert
+ /// from the passed in object to the appropriate data type. If the object is an array, this function is
+ /// called on every element of the array, not just the first element. This can be a performance burden if
+ /// setting large numbers of elements in this fashion. In that case it is recommended to cast the LogixTag
+ /// to the correct object and use the indexor to set the values.
+ ///
+ ///
+ /// Thrown when the value type cannot be converted to a signed byte
+ /// Thrown when the number of elements in the value array does not equal
+ public override object ValueAsObject
+ {
+ get
+ {
+ if (Elements == 1)
+ return Value;
+ else
+ return _value;
+ }
+ set
+ {
+ if (Elements == 1)
+ {
+ try
+ {
+ Value = Convert.ToSByte(value);
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ else
+ {
+ //Ok, first we have to verify that it's an array
+ Array valueArray = value as Array;
+ if (valueArray != null)
+ {
+ if (valueArray.Length != Elements)
+ throw new ArrayLengthException(Resources.ErrorStrings.ArrayLengthException);
+ try
+ {
+ for (int i = 0; i < valueArray.Length; i++)
+ {
+ this[i] = Convert.ToSByte(valueArray.GetValue(i));
+ }
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+
+ }
+ else
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new SINT Logix Tag
+ ///
+ /// Address (Tag Name) in the processor.
+ public LogixSINT(string TagAddress)
+ : base(TagAddress)
+ {
+
+ }
+
+ ///
+ /// Creates a new SINT Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ public LogixSINT(string TagAddress, LogixProcessor Processor)
+ : base(TagAddress, Processor)
+ {
+
+ }
+
+ ///
+ /// Creates a new INT Logix Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ /// Number of elements to read
+ public LogixSINT(string TagAddress, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, Processor, ElementCount)
+ {
+
+ }
+
+ #endregion
+
+ #region Public Methods
+
+
+
+ #endregion
+
+ #region Protected / Internal Methods
+
+#if MONO
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset)
+#else
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset = 0)
+#endif
+ {
+ //Let's mask off the lower part of the data type...
+ ushort tempType = (ushort)(DataType & 0xFFF);
+ bool canRaiseEvent = false;
+
+ if (tempType == 0xC2)
+ {
+ //16 Bit Signed Int
+
+ uint startElement = byteOffset / 4;
+ int dataPos = 0;
+ sbyte temp = 0;
+ //Might be an array though...
+ for (uint i = startElement; i < Elements; i++)
+ {
+ temp = (unchecked((sbyte)(data[dataPos])));
+ _lastWriteChanged |= (_value[i] != temp);
+ _value[i] = temp;
+ dataPos += 1;
+ if (i == Elements - 1)
+ canRaiseEvent = true;
+ if (dataPos >= data.Length)
+ break;
+ }
+ }
+ else
+ {
+ //Who knows???
+ LastError = Resources.ErrorStrings.TypeMismatch + "0x" + tempType.ToString("X2");
+ LastErrorNumber = (int)LogixErrors.TypeMismatch;
+ }
+
+ if (canRaiseEvent && _lastWriteChanged)
+ {
+ _lastWriteChanged = false;
+ RaiseTagDataChanged(this);
+ return true;
+ }
+
+ return false;
+ }
+
+ internal override byte[] OnDataWrite()
+ {
+ byte[] retVal = new byte[Elements];
+
+ for (int i = 0; i < Elements; i++)
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes(_value[i]), 0, retVal, i, 1);
+ }
+
+ return retVal;
+ }
+
+ internal override bool HasPendingWrite()
+ {
+ return PendingWriteCount() > 0;
+ }
+
+ internal override void ClearPendingWrite()
+ {
+ for (int i = 0; i < Elements; i++)
+ _hasPendingWrite[i] = false;
+ }
+
+#if MONO
+ internal override void Initialize(object InitData)
+#else
+ internal override void Initialize(object InitData = null)
+#endif
+ {
+ _value = new sbyte[Elements];
+ _pendingWriteValue = new sbyte[Elements];
+ _hasPendingWrite = new bool[Elements];
+ }
+
+ internal override byte[] GetWriteData(out int byteAlignment)
+ {
+ List retVal = new List();
+ byteAlignment = 2;
+
+ if (Elements == 1)
+ {
+ retVal.AddRange(BitConverter.GetBytes(_pendingWriteValue[0]));
+ }
+ else
+ {
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ retVal.AddRange(BitConverter.GetBytes(_pendingWriteValue[i]));
+ else
+ retVal.AddRange(BitConverter.GetBytes(_value[i]));
+ }
+
+ }
+
+ return retVal.ToArray();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private int PendingWriteCount()
+ {
+ int count = 0;
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ count++;
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region Conversion Methods
+
+ public static implicit operator sbyte(LogixSINT tag)
+ {
+ return tag[0];
+ }
+
+ #endregion
+
+ }
+
+ ///
+ /// Represents a type of LINT in a Logix Processor
+ ///
+ public sealed class LogixLINT : LogixTag
+ {
+
+ #region Fields
+
+ private long[] _value = new long[] { 0 };
+ private long[] _pendingWriteValue = new long[] { 0 };
+ private bool[] _hasPendingWrite = new bool[] { false };
+ private bool _lastWriteChanged = false;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or Sets the Value of the tag
+ ///
+ public long Value
+ {
+ get
+ {
+ return _value[0];
+ }
+ set
+ {
+ _pendingWriteValue[0] = value;
+ _hasPendingWrite[0] = true;
+ }
+ }
+
+ ///
+ /// Gets or Sets the tag at the specified index
+ ///
+ /// Index to get or set
+ /// Value at the specified index
+ /// Thrown when index >= Elements or index < 0
+ public long this[int index]
+ {
+ get
+ {
+ if (index >= Elements)
+ throw new IndexOutOfRangeException();
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ return _value[index];
+ }
+ set
+ {
+ if (index >= Elements)
+ throw new IndexOutOfRangeException();
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ _pendingWriteValue[index] = value;
+ _hasPendingWrite[index] = true;
+ }
+ }
+
+ ///
+ /// Gets the specified value at the row and column index
+ ///
+ /// Row index
+ /// Column index
+ /// Value at the specified row and column
+ /// Thrown when the dimensions haven't been set before accessing this property
+ /// Thrown when row*col > Elements or either index < 0
+ public long this[int row, int col]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the specified value at the row, column, and element index
+ ///
+ /// Row index
+ /// Column Index
+ /// Element Index
+ /// Value at the specified row, column and element index
+ /// Thrown when the dimensions haven't been set before accessing this property
+ /// Thrown when row*col > Elements or either index < 0
+ public long this[int row, int col, int element]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0 || Dim3 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the Logix type of the tag
+ ///
+ public override LogixTypes LogixType
+ {
+ get { return LogixTypes.LInt; }
+ }
+
+ ///
+ /// Gets or Sets the LogixLINT as an object
+ ///
+ ///
+ ///
+ /// When using this to set the array value, the passed in array must be a single
+ /// dimension array with the same number of elements as the LogixLINT tag.
+ ///
+ ///
+ /// It is also good to note that this function makes use of the class to convert
+ /// from the passed in object to the appropriate data type. If the object is an array, this function is
+ /// called on every element of the array, not just the first element. This can be a performance burden if
+ /// setting large numbers of elements in this fashion. In that case it is recommended to cast the LogixTag
+ /// to the correct object and use the indexor to set the values.
+ ///
+ ///
+ /// Thrown when the value type cannot be converted to a long
+ /// Thrown when the number of elements in the value array does not equal
+ public override object ValueAsObject
+ {
+ get
+ {
+ if (Elements == 1)
+ return Value;
+ else
+ return _value;
+ }
+ set
+ {
+ if (Elements == 1)
+ {
+ try
+ {
+ Value = Convert.ToInt64(value);
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ else
+ {
+ //Ok, first we have to verify that it's an array
+ Array valueArray = value as Array;
+ if (valueArray != null)
+ {
+ if (valueArray.Length != Elements)
+ throw new ArrayLengthException(Resources.ErrorStrings.ArrayLengthException);
+ try
+ {
+ for (int i = 0; i < valueArray.Length; i++)
+ {
+ this[i] = Convert.ToInt64(valueArray.GetValue(i));
+ }
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+
+ }
+ else
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new LINT Logix Tag
+ ///
+ /// Address (Tag Name) in the processor.
+ public LogixLINT(string TagAddress)
+ : base(TagAddress)
+ {
+
+ }
+
+ ///
+ /// Creates a new LINT Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ public LogixLINT(string TagAddress, LogixProcessor Processor)
+ : base(TagAddress, Processor)
+ {
+
+ }
+
+ ///
+ /// Creates a new LINT Logix Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ /// Number of elements to read
+ public LogixLINT(string TagAddress, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, Processor, ElementCount)
+ {
+
+ }
+
+ #endregion
+
+ #region Public Methods
+
+
+
+ #endregion
+
+ #region Protected / Internal Methods
+
+#if MONO
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset)
+#else
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset = 0)
+#endif
+ {
+ //Let's mask off the lower part of the data type...
+ ushort tempType = (ushort)(DataType & 0xFFF);
+ bool canRaiseEvent = false;
+
+ if (tempType == 0xC5)
+ {
+ //64 Bit Signed Int
+
+ uint startElement = byteOffset / 8;
+ int dataPos = 0;
+ long temp = 0;
+
+ //Might be an array though...
+ for (uint i = startElement; i < Elements; i++)
+ {
+ temp = BitConverter.ToInt64(data, dataPos);
+ _lastWriteChanged |= (_value[i] != temp);
+ _value[i] = temp;
+ dataPos += 8;
+ if (i == Elements - 1)
+ canRaiseEvent = true;
+ if (dataPos >= data.Length)
+ break;
+ }
+ }
+ else
+ {
+ //Who knows???
+ LastError = Resources.ErrorStrings.TypeMismatch + "0x" + tempType.ToString("X2");
+ LastErrorNumber = (int)LogixErrors.TypeMismatch;
+ }
+
+ if (canRaiseEvent && _lastWriteChanged)
+ {
+ _lastWriteChanged = false;
+ RaiseTagDataChanged(this);
+ return true;
+ }
+
+ return false;
+ }
+
+ internal override byte[] OnDataWrite()
+ {
+ byte[] retVal = new byte[Elements * 8];
+
+ for (int i = 0; i < Elements; i++)
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes(_value[i]), 0, retVal, i * 8, 8);
+ }
+
+ return retVal;
+ }
+
+ internal override bool HasPendingWrite()
+ {
+ return PendingWriteCount() > 0;
+ }
+
+ internal override void ClearPendingWrite()
+ {
+ for (int i = 0; i < Elements; i++)
+ _hasPendingWrite[i] = false;
+ }
+
+#if MONO
+ internal override void Initialize(object InitData)
+#else
+ internal override void Initialize(object InitData = null)
+#endif
+ {
+ _value = new long[Elements];
+ _pendingWriteValue = new long[Elements];
+ _hasPendingWrite = new bool[Elements];
+ }
+
+ internal override byte[] GetWriteData(out int byteAlignment)
+ {
+ List retVal = new List();
+ byteAlignment = 8;
+
+ if (Elements == 1)
+ {
+ retVal.AddRange(BitConverter.GetBytes(_pendingWriteValue[0]));
+ }
+ else
+ {
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ retVal.AddRange(BitConverter.GetBytes(_pendingWriteValue[i]));
+ else
+ retVal.AddRange(BitConverter.GetBytes(_value[i]));
+ }
+
+ }
+
+ return retVal.ToArray();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private int PendingWriteCount()
+ {
+ int count = 0;
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ count++;
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region Conversion Methods
+
+ public static implicit operator long(LogixLINT tag)
+ {
+ return tag[0];
+ }
+
+ #endregion
+
+ }
+
+ ///
+ /// Represents a type of REAL in a Logix Processor
+ ///
+ public sealed class LogixREAL : LogixTag
+ {
+
+ #region Fields
+
+ private float[] _value = new float[] { 0 };
+ private float[] _pendingWriteValue = new float[] { 0 };
+ private bool[] _hasPendingWrite = new bool[] { false };
+ private bool _lastWriteChanged = false;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets or Sets the Value of the tag
+ ///
+ public float Value
+ {
+ get
+ {
+ return _value[0];
+ }
+ set
+ {
+ _pendingWriteValue[0] = value;
+ _hasPendingWrite[0] = true;
+ }
+ }
+
+ ///
+ /// Gets or Sets the tag at the specified index
+ ///
+ /// Index to get or set
+ /// Value at the specified index
+ /// Thrown when index >= Elements or index < 0
+ public float this[int index]
+ {
+ get
+ {
+ if (index >= Elements)
+ throw new IndexOutOfRangeException();
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ return _value[index];
+ }
+ set
+ {
+ if (index >= Elements)
+ throw new IndexOutOfRangeException();
+ if (index < 0)
+ throw new IndexOutOfRangeException();
+
+ _pendingWriteValue[index] = value;
+ _hasPendingWrite[index] = true;
+ }
+ }
+
+ ///
+ /// Gets the specified value at the row and column index
+ ///
+ /// Row index
+ /// Column index
+ /// Value at the specified row and column
+ /// Thrown when the dimensions haven't been set before accessing this property
+ /// Thrown when row*col > Elements or either index < 0
+ public float this[int row, int col]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = row * Dim2 + col;
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the specified value at the row, column, and element index
+ ///
+ /// Row index
+ /// Column Index
+ /// Element Index
+ /// Value at the specified row, column and element index
+ /// Thrown when the dimensions haven't been set before accessing this property
+ /// Thrown when row*col > Elements or either index < 0
+ public float this[int row, int col, int element]
+ {
+ get
+ {
+ if (Dim1 == 0 || Dim2 == 0 || Dim3 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ return this[idx];
+ }
+ set
+ {
+ if (Dim1 == 0 || Dim2 == 0)
+ throw new DimensionsNotSetException(Resources.ErrorStrings.DimensionsNotSet);
+
+ if (row * col * element > Elements)
+ throw new IndexOutOfRangeException();
+
+ if (row < 0 || col < 0 || element < 0)
+ throw new IndexOutOfRangeException();
+
+ int idx = ((row * Dim2 + col) * Dim3 + element);
+
+ this[idx] = value;
+ }
+ }
+
+ ///
+ /// Gets the Logix type of the tag
+ ///
+ public override LogixTypes LogixType
+ {
+ get { return LogixTypes.Real; }
+ }
+
+ ///
+ /// Gets or Sets the LogixREAL as an object
+ ///
+ ///
+ ///
+ /// When using this to set the array value, the passed in array must be a single
+ /// dimension array with the same number of elements as the LogixREAL tag.
+ ///
+ ///
+ /// It is also good to note that this function makes use of the class to convert
+ /// from the passed in object to the appropriate data type. If the object is an array, this function is
+ /// called on every element of the array, not just the first element. This can be a performance burden if
+ /// setting large numbers of elements in this fashion. In that case it is recommended to cast the LogixTag
+ /// to the correct object and use the indexor to set the values.
+ ///
+ ///
+ /// Thrown when the value type cannot be converted to a float
+ /// Thrown when the number of elements in the value array does not equal
+ public override object ValueAsObject
+ {
+ get
+ {
+ if (Elements == 1)
+ return Value;
+ else
+ return _value;
+ }
+ set
+ {
+ if (Elements == 1)
+ {
+ try
+ {
+ Value = Convert.ToSingle(value);
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ else
+ {
+ //Ok, first we have to verify that it's an array
+ Array valueArray = value as Array;
+ if (valueArray != null)
+ {
+ if (valueArray.Length != Elements)
+ throw new ArrayLengthException(Resources.ErrorStrings.ArrayLengthException);
+ try
+ {
+ for (int i = 0; i < valueArray.Length; i++)
+ {
+ this[i] = Convert.ToSingle(valueArray.GetValue(i));
+ }
+ }
+ catch (Exception e)
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+
+ }
+ else
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new REAL Logix Tag
+ ///
+ /// Address (Tag Name) in the processor.
+ public LogixREAL(string TagAddress)
+ : base(TagAddress)
+ {
+
+ }
+
+ ///
+ /// Creates a new REAL Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ public LogixREAL(string TagAddress, LogixProcessor Processor)
+ : base(TagAddress, Processor)
+ {
+
+ }
+
+ ///
+ /// Creates a new REAL Logix Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Processor where the tag resides
+ /// Number of elements to read
+ public LogixREAL(string TagAddress, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, Processor, ElementCount)
+ {
+
+ }
+
+ #endregion
+
+ #region Public Methods
+
+
+
+ #endregion
+
+ #region Internal Methods
+
+#if MONO
+ internal override void Initialize(object InitData)
+#else
+ internal override void Initialize(object InitData = null)
+#endif
+ {
+ _value = new float[Elements];
+ _pendingWriteValue = new float[Elements];
+ _hasPendingWrite = new bool[Elements];
+ }
+
+#if MONO
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset)
+#else
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset = 0)
+#endif
+ {
+ //Let's mask off the lower part of the data type...
+ ushort tempType = (ushort)(DataType & 0xFFF);
+ bool canRaiseEvent = false;
+
+ if (tempType == 0xCA)
+ {
+ //32 Bit Signed Float
+
+ uint startElement = byteOffset / 4;
+ int dataPos = 0;
+ float temp = 0;
+
+ //Might be an array though...
+ for (uint i = startElement; i < Elements; i++)
+ {
+ temp = BitConverter.ToSingle(data, dataPos);
+ _lastWriteChanged |= (_value[i] != temp);
+ _value[i] = temp;
+ dataPos += 4;
+ if (i == Elements - 1)
+ canRaiseEvent = true;
+ if (dataPos >= data.Length)
+ break;
+ }
+ }
+ else
+ {
+ //Who knows???
+ LastError = Resources.ErrorStrings.TypeMismatch + "0x" + tempType.ToString("X2");
+ LastErrorNumber = (int)LogixErrors.TypeMismatch;
+ }
+
+ if (canRaiseEvent && _lastWriteChanged)
+ {
+ _lastWriteChanged = false;
+ RaiseTagDataChanged(this);
+ return true;
+ }
+
+ return false;
+ }
+
+ internal override byte[] OnDataWrite()
+ {
+ byte[] retVal = new byte[Elements * 4];
+
+ for (int i = 0; i < Elements; i++)
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes(_value[i]), 0, retVal, i * 4, 4);
+ }
+
+ return retVal;
+ }
+
+ internal override bool HasPendingWrite()
+ {
+ return PendingWriteCount() > 0;
+ }
+
+ internal override void ClearPendingWrite()
+ {
+ for (int i = 0; i < Elements; i++)
+ _hasPendingWrite[i] = false;
+ }
+
+ internal override byte[] GetWriteData(out int byteAlignment)
+ {
+ List retVal = new List();
+ byteAlignment = 4;
+
+ if (Elements == 1)
+ {
+ retVal.AddRange(BitConverter.GetBytes(_pendingWriteValue[0]));
+ }
+ else
+ {
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ retVal.AddRange(BitConverter.GetBytes(_pendingWriteValue[i]));
+ else
+ retVal.AddRange(BitConverter.GetBytes(_value[i]));
+ }
+
+ }
+
+ return retVal.ToArray();
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private int PendingWriteCount()
+ {
+ int count = 0;
+ for (int i = 0; i < Elements; i++)
+ {
+ if (_hasPendingWrite[i])
+ count++;
+ }
+
+ return count;
+ }
+
+ #endregion
+
+ #region Conversion Methods
+
+ public static implicit operator float(LogixREAL tag)
+ {
+ return tag[0];
+ }
+
+ #endregion
+
+ }
+
+ #endregion
+
+ #region UDT Support
+
+ internal class UDTItem
+ {
+ #region Fields
+
+ private TemplateInfo _templateInfo;
+ private byte[] _data;
+ private byte[] _pendingData;
+ private bool _hasPendingWrite;
+ private bool _writeDataInitd = false;
+
+ #endregion
+
+ #region Properties
+
+ public object this[string name]
+ {
+ get { return GetMemberValue(name); }
+ set { SetMemberValue(name, value); }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ public UDTItem(TemplateInfo info)
+ {
+ _templateInfo = info;
+ _data = new byte[_templateInfo.TagSize];
+ _pendingData = new byte[_templateInfo.TagSize];
+ _hasPendingWrite = false;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public bool Update(byte[] newData, uint offset)
+ {
+ //This function returns true so that the tag knows if it needs
+ //to raise the changed event or not...
+ //The new data should be just as long as the old data...
+
+ bool changed = !UtilityBelt.CompareArrays(_data, newData, offset);
+
+ int bytesToCopy = (int)(_data.Length - offset);
+ if (bytesToCopy > newData.Length)
+ bytesToCopy = newData.Length;
+
+ if (bytesToCopy > 0)
+ Buffer.BlockCopy(newData, 0, _data, (int)offset, bytesToCopy);
+
+ if (!_writeDataInitd)
+ {
+ //Need to find out if we got a full set of data first
+ if (offset + bytesToCopy >= _data.Length)
+ {
+ _writeDataInitd = true;
+ Buffer.BlockCopy(_data, 0, _pendingData, 0, _data.Length);
+ }
+ }
+
+ return changed;
+ }
+
+ public void UpdatePendingData(byte[] newData)
+ {
+ //The data lengths must match...
+ if (newData.Length != _pendingData.Length)
+ throw new ArrayLengthException(Resources.ErrorStrings.ArrayLengthException);
+
+ Buffer.BlockCopy(newData, 0, _pendingData, 0, _pendingData.Length);
+ }
+
+ public MemberInfo GetMemberInfo(string name)
+ {
+ for (int i = 0; i < _templateInfo.MemberInfo.Count; i++)
+ {
+ if (string.Compare(name, _templateInfo.MemberInfo[i].MemberName, true) == 0)
+ {
+ //Got it...
+ return _templateInfo.MemberInfo[i];
+ }
+ }
+
+ return null; //Could not find the member
+ }
+
+ public object GetMemberValue(string name)
+ {
+ //We need to get some info about the offset of this tag...
+ MemberInfo mi = GetMemberInfo(name);
+
+ if (mi == null)
+ throw new UDTMemberNotFoundException(Resources.ErrorStrings.UDTMemberNotFound);
+
+ //Now depending on the type we have to convert it to
+ //the original type. For now if it's an embedded struct
+ //we'll just return the bytes instead of any other data
+ bool isArray = (mi.MemberType & 0x6000) > 0;
+ ushort realType = (ushort)((mi.MemberType & 0x00FF));
+ switch ((CIPType)realType)
+ {
+ case CIPType.BITS:
+ return TypeConverter.GetBoolArray(TypeConverter.GetInt32Array(_data, mi.MemberOffset, mi.MemberSize));
+ case CIPType.BOOL:
+ if ((_data[mi.MemberOffset] & (0x01 << mi.Info)) > 0)
+ return true;
+ return false;
+ case CIPType.SINT:
+ if (isArray)
+ {
+ //Info has the number of bytes...
+ return TypeConverter.GetByteArray(_data, mi.MemberOffset, mi.Info);
+ }
+ else
+ return _data[mi.MemberOffset];
+ case CIPType.INT:
+ if (isArray)
+ {
+ return TypeConverter.GetShortArray(_data, mi.MemberOffset, mi.Info * 2);
+ }
+ else
+ return BitConverter.ToInt16(_data, mi.MemberOffset);
+ case CIPType.DINT:
+ if (isArray)
+ {
+ return TypeConverter.GetInt32Array(_data, mi.MemberOffset, mi.Info * 4);
+ }
+ else
+ return BitConverter.ToInt32(_data, mi.MemberOffset);
+ case CIPType.LINT:
+ if (isArray)
+ {
+ return TypeConverter.GetInt64Array(_data, mi.MemberOffset, mi.Info * 8);
+ }
+ else
+ return BitConverter.ToInt64(_data, mi.MemberOffset);
+ case CIPType.REAL:
+ if (isArray)
+ {
+ return TypeConverter.GetFloatArray(_data, mi.MemberOffset, mi.Info * 4);
+ }
+ else
+ return BitConverter.ToSingle(_data, mi.MemberOffset);
+ case CIPType.STRUCT:
+ return TypeConverter.GetByteArray(_data, mi.MemberOffset, mi.MemberSize);
+ default:
+ //Unknown type...
+ return null;
+ }
+ }
+
+ public void SetMemberValue(string name, object value)
+ {
+ //We need to get some info about the offset of this tag...
+ MemberInfo mi = GetMemberInfo(name);
+
+ if (mi == null)
+ throw new UDTMemberNotFoundException(Resources.ErrorStrings.UDTMemberNotFound);
+
+ //Now depending on the type we have to convert it to
+ //the original type. For now if it's an embedded struct
+ //we'll just return the bytes instead of any other data
+ bool isArray = (mi.MemberType & 0x6000) > 0;
+ ushort realType = (ushort)((mi.MemberType & 0x00FF));
+ byte[] temp;
+
+ try
+ {
+ //First we have to convert it to the right type...
+ switch ((CIPType)realType)
+ {
+ case CIPType.BITS:
+ if (isArray)
+ {
+ if (!(value is bool[]))
+ break;
+ temp = TypeConverter.GetBytes(TypeConverter.GetBoolArray(value as bool[]), mi.MemberSize);
+ Buffer.BlockCopy(temp, 0, _pendingData, mi.MemberOffset, mi.MemberSize);
+ _hasPendingWrite = true;
+ }
+ else
+ {
+ //Should just be one bool val, we need to convert it to an int32 and store
+ //it at the data location...
+ if (!(value is bool))
+ break;
+ //We have to set/unset the right bit...
+ int bitVal = mi.Info & 0x00FF;
+ if ((bool)value)
+ _pendingData[mi.MemberOffset] |= (byte)(0x01 << bitVal);
+ else
+ _pendingData[mi.MemberOffset] &= (byte)~(0x01 << bitVal);
+ _hasPendingWrite = true;
+ }
+ return;
+ case CIPType.BOOL:
+ //This cannot be an array
+ if (!(value is bool))
+ break;
+ if ((bool)value)
+ {
+ //Need to set the bit...
+ _pendingData[mi.MemberOffset] |= (byte)(0x01 << mi.Info);
+ }
+ else
+ {
+ //Need to clear the bit
+ _pendingData[mi.MemberOffset] &= (byte)~(0x01 << mi.Info);
+ }
+ _hasPendingWrite = true;
+ return;
+ case CIPType.SINT:
+ if (isArray)
+ {
+ if (!(value is sbyte[] || !(value is byte[])))
+ break;
+ Array src = value as Array;
+ if (src == null)
+ return;
+ Buffer.BlockCopy(src, 0, _pendingData, mi.MemberOffset, src.Length);
+ }
+ else
+ {
+ if (!(value is sbyte) || !(value is byte))
+ break;
+ _pendingData[mi.MemberOffset] = (unchecked((byte)value));
+ }
+ _hasPendingWrite = true;
+ return;
+ case CIPType.INT:
+ if (isArray)
+ {
+ if (!(value is short[]))
+ break;
+ short[] valArray = value as short[];
+ if (valArray == null)
+ break;
+ temp = TypeConverter.GetBytes(valArray, mi.MemberSize);
+ Buffer.BlockCopy(temp, 0, _pendingData, mi.MemberOffset, mi.MemberSize);
+ }
+ else
+ {
+ if (!(value is short))
+ break;
+ Buffer.BlockCopy(BitConverter.GetBytes((short)value), 0, _pendingData, mi.MemberOffset, 2);
+ }
+ _hasPendingWrite = true;
+ return;
+ case CIPType.DINT:
+ if (isArray)
+ {
+ if (!(value is int[]))
+ break;
+ int[] valArray = value as int[];
+ if (valArray == null)
+ break;
+ temp = TypeConverter.GetBytes(valArray, mi.MemberSize);
+ Buffer.BlockCopy(temp, 0, _pendingData, mi.MemberOffset, mi.MemberSize);
+ }
+ else
+ {
+ if (!(value is int))
+ break;
+ Buffer.BlockCopy(BitConverter.GetBytes((int)value), 0, _pendingData, mi.MemberOffset, 4);
+ }
+
+ _hasPendingWrite = true;
+ return;
+ case CIPType.LINT:
+ if (isArray)
+ {
+ if (!(value is long[]))
+ break;
+ long[] valArray = value as long[];
+ if (valArray == null)
+ break;
+ temp = TypeConverter.GetBytes(valArray, mi.MemberSize);
+ Buffer.BlockCopy(temp, 0, _pendingData, mi.MemberOffset, mi.MemberSize);
+ }
+ else
+ {
+ if (!(value is long))
+ break;
+ Buffer.BlockCopy(BitConverter.GetBytes((long)value), 0, _pendingData, mi.MemberOffset, 8);
+ }
+
+ _hasPendingWrite = true;
+ return;
+ case CIPType.REAL:
+ if (isArray)
+ {
+ if (!(value is float[]))
+ break;
+ float[] valArray = value as float[];
+ if (valArray == null)
+ break;
+ temp = TypeConverter.GetBytes(valArray, mi.MemberSize);
+ Buffer.BlockCopy(temp, 0, _pendingData, mi.MemberOffset, mi.MemberSize);
+ }
+ else
+ {
+ if (!(value is float))
+ break;
+ Buffer.BlockCopy(BitConverter.GetBytes((float)value), 0, _pendingData, mi.MemberOffset, 4);
+ }
+
+ _hasPendingWrite = true;
+ return;
+ case CIPType.STRUCT:
+ if (!(value is byte[]))
+ break;
+ Buffer.BlockCopy((byte[])value, 0, _pendingData, mi.MemberOffset, mi.MemberSize);
+ _hasPendingWrite = true;
+ return;
+ default:
+ //Unknown type...
+ return;
+ }
+
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+
+ }
+ catch
+ {
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+ }
+
+ }
+
+ public void ClearPendingWrite()
+ {
+ _hasPendingWrite = false;
+ }
+
+ public bool HasPendingWrite()
+ {
+ return _hasPendingWrite;
+ }
+
+ public byte[] GetWriteData()
+ {
+ return _pendingData;
+ }
+
+ public static implicit operator byte[](UDTItem item)
+ {
+ return item._data;
+ }
+
+ #endregion
+
+ }
+
+ ///
+ /// Represents a User Defined Type in a Logix Processor
+ ///
+ /// In order to create a LogixUDT tag, use the method. The
+ /// tag factory will automatically read the template data from the processor and return one of the built
+ /// in types or this custom type.
+ public class LogixUDT : LogixTag
+ {
+
+ #region Fields
+
+ private TemplateInfo _templateInfo;
+ private UDTItem[] _udtItem;
+ private bool _lastWriteChanged = false;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Gets the LogixType of this tag
+ ///
+ public override LogixTypes LogixType
+ {
+ get { return LogixTypes.User_Defined; }
+ }
+
+ ///
+ /// Gets or Sets the value of the specified member.
+ ///
+ ///
+ /// Caution must be taken when setting the UDT value. If the UDT value is an array and you address
+ /// the element as such: udtType["myArray"][2] = 4, the system can't figure out that you've changed
+ /// the 2nd element of the array. This means that the tag won't be updated when the group is written
+ /// to the processor. To perform a write like that, first copy the entire array out, then set it like:
+ /// Array myArray = udtType["myArray"];
+ /// myArray[2] = 4;
+ /// udtType["myArray"] = myArray;
+ ///
+ /// Name of the element to retrieve
+ /// Typed object of the specified element
+ public object this[string name]
+ {
+ get { return _udtItem[0][name]; }
+ set { _udtItem[0][name] = value; }
+ }
+
+ ///
+ /// Gets or Sets the value of the specified member.
+ ///
+ ///
+ /// Caution must be taken when setting the UDT value. If the UDT value is an array and you address
+ /// the element as such: udtType["myArray"][2] = 4, the system can't figure out that you've changed
+ /// the 2nd element of the array. This means that the tag won't be updated when the group is written
+ /// to the processor. To perform a write like that, first copy the entire array out, then set it like:
+ /// Array myArray = udtType["myArray"];
+ /// myArray[2] = 4;
+ /// udtType["myArray"] = myArray;
+ ///
+ /// Name of the element to retrieve
+ /// Typed object of the specified element
+ public object this[int idx, string name]
+ {
+ get
+ {
+ if (idx > Elements)
+ throw new IndexOutOfRangeException();
+
+ return _udtItem[idx][name];
+ }
+ set
+ {
+ if (idx > Elements)
+ throw new IndexOutOfRangeException();
+
+ _udtItem[idx][name] = value;
+ }
+ }
+
+ ///
+ /// Gets the name of the type structure
+ ///
+ public string TypeName
+ {
+ get { return _templateInfo.TemplateName; }
+ }
+
+ ///
+ /// Gets a list of the structure member names
+ ///
+ public List MemberNames
+ {
+ get
+ {
+ List retVal = new List();
+ foreach (MemberInfo mi in _templateInfo.MemberInfo)
+ retVal.Add(mi.MemberName);
+
+ return retVal;
+ }
+ }
+
+ ///
+ /// Gets or Sets the LogixUDT as an object
+ ///
+ ///
+ ///
+ /// When using this to set the array value, the passed in array must be a single
+ /// dimension array with the same number of elements as the LogixUDT tag.
+ ///
+ ///
+ /// This property only accepts or returns an array of bytes. These bytes represent
+ /// the UDT in memory, with the padding. It is important to keep in mind how the
+ /// UDT is structured when setting values using this method. It is recommended that
+ /// the tag be cast to a LogixUDT first, then use the appropriate method to set the
+ /// correct field.
+ ///
+ ///
+ /// Thrown when the value type cannot be converted to a byte array
+ /// Thrown when the number of elements in the value array does not equal
+ public override object ValueAsObject
+ {
+ get
+ {
+ if (Elements == 1)
+ return (byte[])_udtItem[0];
+ else
+ {
+ List myBytes = new List();
+ for (int i = 0; i < _udtItem.Length; i++)
+ myBytes.AddRange((byte[])_udtItem[i]);
+ return myBytes.ToArray();
+ }
+ }
+ set
+ {
+ //This is trickier...
+ byte[] byteArray = value as byte[];
+
+ if (byteArray == null)
+ throw new TypeConversionException(Resources.ErrorStrings.TypeConversionError);
+
+ if (Elements == 1)
+ {
+ _udtItem[0].UpdatePendingData(byteArray);
+ }
+ else
+ {
+ SetPendingData(byteArray);
+ }
+
+ }
+ }
+
+ #endregion
+
+ #region Events
+
+
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new LogixUDT
+ ///
+ /// Address (Tag Name) of the tag on the processor
+ /// Processor that the tag belongs to
+ public LogixUDT(string TagAddress, LogixProcessor Processor)
+ : base (TagAddress, Processor, 1, null)
+ {
+
+ }
+
+ ///
+ /// Creates a new User-Defined Type Logix Tag
+ ///
+ /// Address (Tag Name) in the processor.
+ /// Template information about the type
+ internal LogixUDT(string TagAddress, TemplateInfo TemplateInfo)
+ : base(TagAddress)
+ {
+ _templateInfo = TemplateInfo;
+ }
+
+ ///
+ /// Creates a new User-Defined Type Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Template information about the type
+ /// Processor where the tag resides
+ internal LogixUDT(string TagAddress, TemplateInfo TemplateInfo, LogixProcessor Processor)
+ : base(TagAddress, Processor, 1, TemplateInfo)
+ {
+ _templateInfo = TemplateInfo;
+ }
+
+ ///
+ /// Creates a new User-Defined Type Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Template information about the type
+ /// Processor where the tag resides
+ /// Number of elements to read
+ internal LogixUDT(string TagAddress, TemplateInfo TemplateInfo, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, Processor, ElementCount, TemplateInfo)
+ {
+ _templateInfo = TemplateInfo;
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ ///
+ /// Gets the type for a particular member in the type
+ ///
+ /// Name of the member
+ /// or
+ /// if
+ /// the member was not found
+ public LogixTypes GetTypeForMember(string MemberName)
+ {
+ foreach (MemberInfo mi in _templateInfo.MemberInfo)
+ {
+ if (string.Compare(mi.MemberName, MemberName) == 0)
+ return mi.LogixType;
+ }
+
+ return LogixTypes.Unknown;
+ }
+
+ #endregion
+
+ #region Private Methods
+
+ private void SetPendingData(byte[] data)
+ {
+ //Need to find out which element this is in, and if it spans elements...
+ int structSize = _templateInfo.TagSize;
+ int element = 0;
+ int offset = 0;
+
+ int bytesRemaining = data.Length;
+ int arrayOffset = 0;
+ byte[] temp;
+
+ while (bytesRemaining > 0)
+ {
+ int bytesToRead = structSize - offset;
+ if (bytesToRead < bytesRemaining)
+ {
+ temp = new byte[bytesToRead];
+ Buffer.BlockCopy(data, arrayOffset, temp, 0, bytesToRead);
+ arrayOffset += bytesToRead;
+ bytesRemaining -= bytesToRead;
+ _udtItem[element].UpdatePendingData(temp);
+ offset = 0;
+ element++;
+ if (element >= Elements)
+ bytesRemaining = 0;
+ }
+ else
+ {
+ //Not enough data to complete the request...
+ temp = new byte[bytesRemaining];
+ Buffer.BlockCopy(data, arrayOffset, temp, 0, bytesRemaining);
+ _udtItem[element].UpdatePendingData(temp);
+ bytesRemaining = 0;
+ }
+
+ }
+
+ }
+
+ #endregion
+
+ #region Internal Methods
+
+#if MONO
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset)
+#else
+ internal override bool OnDataUpdated(byte[] data, uint byteOffset = 0)
+#endif
+ {
+ //Need to find out which element this is in, and if it spans elements...
+ int structSize = _templateInfo.TagSize;
+ int element = (int)(byteOffset / structSize);
+ int offset = (int)(byteOffset - (element * structSize));
+
+ int bytesRemaining = data.Length - 2;
+ int arrayOffset = 2;
+ byte[] temp;
+ bool canRaiseEvent = false;
+
+ while (bytesRemaining > 0)
+ {
+ int bytesToRead = structSize - offset;
+ if (bytesToRead < bytesRemaining)
+ {
+ temp = new byte[bytesToRead];
+ Buffer.BlockCopy(data, arrayOffset, temp, 0, bytesToRead);
+ arrayOffset += bytesToRead;
+ bytesRemaining -= bytesToRead;
+ _lastWriteChanged |= _udtItem[element].Update(temp, (uint)offset);
+ offset = 0;
+ element++;
+ if (element >= Elements)
+ bytesRemaining = 0;
+ }
+ else
+ {
+ //Not enough data to complete the request...
+ temp = new byte[bytesRemaining];
+ Buffer.BlockCopy(data, arrayOffset, temp, 0, bytesRemaining);
+ _lastWriteChanged |= _udtItem[element].Update(temp, (uint)offset);
+ bytesRemaining = 0;
+ }
+
+ if (element >= Elements - 1)
+ canRaiseEvent = true;
+ }
+
+ if (canRaiseEvent && _lastWriteChanged)
+ {
+ _lastWriteChanged = false;
+ RaiseTagDataChanged(this);
+ return true;
+ }
+
+ return false;
+ }
+
+ internal override byte[] OnDataWrite()
+ {
+ List retVal = new List();
+ for (int i = 0; i < _udtItem.Length; i++)
+ retVal.AddRange(_udtItem[i].GetWriteData());
+ return retVal.ToArray();
+ }
+
+ internal override bool HasPendingWrite()
+ {
+ for (int i = 0; i < _udtItem.Length; i++)
+ {
+ if (_udtItem[i].HasPendingWrite())
+ return true;
+ }
+
+ return false;
+ }
+
+ internal override void ClearPendingWrite()
+ {
+ for (int i = 0; i < _udtItem.Length; i++)
+ _udtItem[i].ClearPendingWrite();
+ }
+
+#if MONO
+ internal override void Initialize(object InitData)
+#else
+ internal override void Initialize(object InitData = null)
+#endif
+ {
+ if (InitData == null)
+ {
+ //need to get the template info
+ _templateInfo = LogixTagFactory.GetTemplateInfo(Address, Processor, Elements);
+ }
+ else
+ _templateInfo = (TemplateInfo)InitData;
+ StructHandle = _templateInfo.TemplateHandle;
+ _udtItem = new UDTItem[Elements];
+ for (int i = 0; i < Elements; i++)
+ _udtItem[i] = new UDTItem(_templateInfo);
+ }
+
+ internal override byte[] GetWriteData(out int byteAlignment)
+ {
+ byteAlignment = 1;
+ return OnDataWrite();
+ }
+
+ #endregion
+
+ }
+
+ #endregion
+
+ #region Major Types
+
+ ///
+ /// Represents a type of COUNTER in a Logix Processor
+ ///
+ public sealed class LogixCONTROL : LogixUDT
+ {
+
+ #region Properties
+
+ ///
+ /// Gets the of this tag
+ ///
+ public override LogixTypes LogixType
+ {
+ get
+ {
+ return LogixTypes.Control;
+ }
+ }
+
+ ///
+ /// Gets or Sets the Length member
+ ///
+ public int LEN
+ {
+ get { return (int)base["LEN"]; }
+ set { base["LEN"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the POS member
+ ///
+ public int POS
+ {
+ get { return (int)base["POS"]; }
+ set { base["POS"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the Enabled member
+ ///
+ public bool EN
+ {
+ get { return (bool)base["EN"]; }
+ set { base["EN"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the EU member
+ ///
+ public bool EU
+ {
+ get { return (bool)base["EU"]; }
+ set { base["EU"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the Done member
+ ///
+ public bool DN
+ {
+ get { return (bool)base["DN"]; }
+ set { base["DN"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the EM member
+ ///
+ public bool EM
+ {
+ get { return (bool)base["EM"]; }
+ set { base["EM"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the Error member
+ ///
+ public bool ER
+ {
+ get { return (bool)base["ER"]; }
+ set { base["ER"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the UL member
+ ///
+ public bool UL
+ {
+ get { return (bool)base["UL"]; }
+ set { base["UL"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the IN member
+ ///
+ public bool IN
+ {
+ get { return (bool)base["IN"]; }
+ set { base["IN"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the FD member
+ ///
+ public bool FD
+ {
+ get { return (bool)base["FD"]; }
+ set { base["FD"] = value; }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new CONTROL Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Template information about the type
+ /// Processor where the tag resides
+ internal LogixCONTROL(string TagAddress, TemplateInfo TemplateInfo, LogixProcessor Processor)
+ : base(TagAddress, TemplateInfo, Processor)
+ {
+
+ }
+
+ ///
+ /// Creates a new CONTROL Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Template information about the type
+ /// Processor where the tag resides
+ /// Number of elements to read
+ internal LogixCONTROL(string TagAddress, TemplateInfo TemplateInfo, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, TemplateInfo, Processor, ElementCount)
+ {
+
+ }
+
+ #endregion
+
+ }
+
+ ///
+ /// Represents a type of TIMER in a Logix Processor
+ ///
+ public sealed class LogixTIMER : LogixUDT
+ {
+
+ #region Properties
+
+ ///
+ /// Gets the of this tag
+ ///
+ public override LogixTypes LogixType
+ {
+ get
+ {
+ return LogixTypes.Timer;
+ }
+ }
+
+ ///
+ /// Gets or Sets the counter preset member
+ ///
+ public int PRE
+ {
+ get { return (int)base["PRE"]; }
+ set { base["PRE"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the accumulated value member
+ ///
+ public int ACC
+ {
+ get { return (int)base["ACC"]; }
+ set { base["ACC"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the enabled member
+ ///
+ public bool EN
+ {
+ get { return (bool)base["EN"]; }
+ set { base["EN"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the timing operation flag
+ ///
+ public bool TT
+ {
+ get { return (bool)base["TT"]; }
+ set { base["TT"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the done member
+ ///
+ public bool DN
+ {
+ get { return (bool)base["DN"]; }
+ set { base["DN"] = value; }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new TIMER Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Template information about the type
+ /// Processor where the tag resides
+ internal LogixTIMER(string TagAddress, TemplateInfo TemplateInfo, LogixProcessor Processor)
+ : base(TagAddress, TemplateInfo, Processor)
+ {
+
+ }
+
+ ///
+ /// Creates a new TIMER Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Template information about the type
+ /// Processor where the tag resides
+ /// Number of elements to read
+ internal LogixTIMER(string TagAddress, TemplateInfo TemplateInfo, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, TemplateInfo, Processor, ElementCount)
+ {
+
+ }
+
+ #endregion
+
+ }
+
+ ///
+ /// Represents a type of COUNTER in a Logix Processor
+ ///
+ public sealed class LogixCOUNTER : LogixUDT
+ {
+
+ #region Properties
+
+ ///
+ /// Gets the of this tag
+ ///
+ public override LogixTypes LogixType
+ {
+ get
+ {
+ return LogixTypes.Counter;
+ }
+ }
+
+ ///
+ /// Gets or Sets the counter preset member
+ ///
+ public int PRE
+ {
+ get { return (int)base["PRE"]; }
+ set { base["PRE"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the accumulated value member
+ ///
+ public int ACC
+ {
+ get { return (int)base["ACC"]; }
+ set { base["ACC"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the count up enabled member
+ ///
+ public bool CU
+ {
+ get { return (bool)base["CU"]; }
+ set { base["CU"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the count down enabled member
+ ///
+ public bool CD
+ {
+ get { return (bool)base["CD"]; }
+ set { base["CD"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the done member
+ ///
+ public bool DN
+ {
+ get { return (bool)base["DN"]; }
+ set { base["DN"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the overflow member
+ ///
+ public bool OV
+ {
+ get { return (bool)base["OV"]; }
+ set { base["OV"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the underflow member
+ ///
+ public bool UN
+ {
+ get { return (bool)base["UN"]; }
+ set { base["UN"] = value; }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new COUNTER Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Template information about the type
+ /// Processor where the tag resides
+ internal LogixCOUNTER(string TagAddress, TemplateInfo TemplateInfo, LogixProcessor Processor)
+ : base(TagAddress, TemplateInfo, Processor)
+ {
+
+ }
+
+ ///
+ /// Creates a new COUNTER Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Template information about the type
+ /// Processor where the tag resides
+ /// Number of elements to read
+ internal LogixCOUNTER(string TagAddress, TemplateInfo TemplateInfo, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, TemplateInfo, Processor, ElementCount)
+ {
+
+ }
+
+ #endregion
+
+ }
+
+ ///
+ /// Represents a type of STRING in a logix procesor
+ ///
+ public sealed class LogixSTRING : LogixUDT
+ {
+
+ #region Properties
+
+ ///
+ /// Gets the Logix type of this tag
+ ///
+ public override LogixTypes LogixType
+ {
+ get
+ {
+ return LogixTypes.String;
+ }
+ }
+
+ ///
+ /// Gets or Sets the length of the string data in bytes
+ ///
+ public int Len
+ {
+ get { return (int)base["LEN"]; }
+ set { base["LEN"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the string data in bytes
+ ///
+ /// The size of this array can't be any more than
+ /// 82 elements.
+ ///
+ public byte[] Data
+ {
+ get { return (byte[])base["DATA"]; }
+ set { base["DATA"] = value; }
+ }
+
+ ///
+ /// Gets or Sets the string representation of this tag
+ ///
+ /// This property automatically trims the string to fit
+ /// in the 82 character limit and sets the LEN field.
+ ///
+ public string StringValue
+ {
+ get
+ {
+ byte[] chars = Data;
+ int len = Len;
+ return System.Text.ASCIIEncoding.ASCII.GetString(chars, 0, len);
+ }
+ set
+ {
+ if (value.Length > 82)
+ value = value.Substring(0, 82);
+ int originalLen = value.Length;
+ if (value.Length < 82)
+ value = value + new String('\0', (82 - value.Length));
+
+ byte[] chars = System.Text.ASCIIEncoding.ASCII.GetBytes(value);
+ Len = originalLen;
+ Data = chars;
+ }
+ }
+
+ #endregion
+
+ #region Construction / Deconstruction
+
+ ///
+ /// Creates a new STRING Logix Tag on the specified Processor
+ ///
+ /// Address (Tag Name) in the processor
+ /// Template information about the type
+ /// Processor where the tag resides
+ internal LogixSTRING(string TagAddress, TemplateInfo TemplateInfo, LogixProcessor Processor)
+ : base(TagAddress, TemplateInfo, Processor)
+ {
+
+ }
+
+ ///
+ /// Creates a new STRING Tag on the specified Processor with the number of Elements
+ ///
+ /// Address (Tag Name) in the processor
+ /// Template information about the type
+ /// Processor where the tag resides
+ /// Number of elements to read
+ internal LogixSTRING(string TagAddress, TemplateInfo TemplateInfo, LogixProcessor Processor, ushort ElementCount)
+ : base(TagAddress, TemplateInfo, Processor, ElementCount)
+ {
+
+ }
+
+ #endregion
+
+ #region Public Methods
+
+ public override string ToString()
+ {
+ return StringValue;
+ }
+
+ #endregion
+
+ }
+
+ #endregion
+
+}
diff --git a/ControlLogixNET/MultiServiceRequest.cs b/ControlLogixNET/MultiServiceRequest.cs
new file mode 100644
index 0000000..3b2d009
--- /dev/null
+++ b/ControlLogixNET/MultiServiceRequest.cs
@@ -0,0 +1,189 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using EIPNET.EIP;
+using EIPNET.CIP;
+
+namespace ControlLogixNET
+{
+ internal class MultiServiceRequest
+ {
+ private int _replySize = 0;
+
+ byte[] _header = new byte[] { 0x0A, 0x02, 0x20, 0x02, 0x24, 0x01 };
+ public List Services { get; set; }
+ public int ReplySize
+ {
+ get { return _replySize; }
+ set { _replySize = value; }
+ }
+
+ public MultiServiceRequest()
+ {
+ Services = new List();
+ }
+
+ public int Size {
+ get
+ {
+ int retVal = _header.Length + (2 * Services.Count);
+
+ for (int i = 0; i < Services.Count; i++)
+ retVal += Services[i].Length;
+
+ return retVal + 2;
+ }
+ }
+
+ public int AddService(byte[] Service)
+ {
+ Services.Add(Service);
+ return Services.Count - 1;
+ }
+
+ public byte[] Pack()
+ {
+ byte[] retVal = new byte[Size];
+
+ Buffer.BlockCopy(_header, 0, retVal, 0, _header.Length);
+ int arrayOffset = _header.Length;
+
+ //Now add 2 bytes for the count
+ Buffer.BlockCopy(BitConverter.GetBytes((ushort)Services.Count), 0, retVal, arrayOffset, 2);
+ arrayOffset += 2;
+
+ byte[] offsetArray = new byte[2 * Services.Count];
+ int pos = 2 + offsetArray.Length;
+ for (int i = 0; i < Services.Count; i++)
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes((ushort)pos), 0, offsetArray, 2 * i, 2);
+ pos += Services[i].Length;
+ }
+
+ List serviceData = new List();
+ for (int i = 0; i < Services.Count; i++)
+ serviceData.AddRange(Services[i]);
+
+ byte[] serviceBytes = serviceData.ToArray();
+ Buffer.BlockCopy(offsetArray, 0, retVal, arrayOffset, offsetArray.Length);
+ arrayOffset += offsetArray.Length;
+ Buffer.BlockCopy(serviceBytes, 0, retVal, arrayOffset, serviceBytes.Length);
+
+ return retVal;
+ }
+ }
+
+ internal class MultiServiceReply
+ {
+ public byte ReplyService { get; internal set; }
+ public byte Reserved { get; internal set; }
+ public byte GenSTS { get; internal set; }
+ public byte Reserved2 { get; internal set; }
+ public ushort Count { get; internal set; }
+ public List ServiceReplies { get; internal set; }
+ public bool IsPartial { get; internal set; }
+ public ushort PartialOffset { get; internal set; }
+
+ public MultiServiceReply()
+ {
+ ServiceReplies = new List();
+ }
+
+ public MultiServiceReply(EncapsReply reply)
+ {
+ EncapsRRData rrData = new EncapsRRData();
+ CommonPacket cpf = new CommonPacket();
+
+ int temp = 0;
+ rrData.Expand(reply.EncapsData, 0, out temp);
+ cpf = rrData.CPF;
+
+ MR_Response response = new MR_Response();
+ response.Expand(cpf.DataItem.Data, 2, out temp);
+
+ if (response.GeneralStatus == 0x1E)
+ IsPartial = true;
+
+ ReplyService = response.ReplyService;
+ GenSTS = response.GeneralStatus;
+
+ ServiceReplies = new List();
+
+ if (response.ResponseData != null)
+ Expand(response.ResponseData);
+ }
+
+ public void Expand(byte[] Data)
+ {
+ int offset = 0;
+ Count = BitConverter.ToUInt16(Data, 0);
+ offset += 2;
+
+ try
+ {
+ for (int i = 0; i < Count; i++)
+ {
+ ushort pos = BitConverter.ToUInt16(Data, offset);
+ offset += 2;
+ ushort nextPos = BitConverter.ToUInt16(Data, offset);
+ if (i == (Count - 1))
+ nextPos = (ushort)(Data.Length);
+ ServiceReply svcReply = GetServiceReply(pos, Data, (ushort)(nextPos - pos));
+ ServiceReplies.Add(svcReply);
+ }
+ }
+ catch { }
+
+ }
+
+ private ServiceReply GetServiceReply(ushort offset, byte[] data, ushort len)
+ {
+ return new ServiceReply(data, offset, len);
+ }
+
+ }
+
+ internal class ServiceReply
+ {
+ public byte Service { get; internal set; }
+ public byte Status { get; internal set; }
+ public byte AdditionalStatusSize { get; internal set; }
+ public byte[] AdditionalStatus { get; internal set; }
+ public byte[] FullStatus { get; internal set; }
+ public bool IsPartial { get; internal set; }
+ public byte[] ServiceData { get; internal set; }
+
+ public ServiceReply(byte[] sourceArray, int start, int len)
+ {
+ //First byte is the Service
+ Service = sourceArray[start];
+ Status = sourceArray[start + 2];
+ if (sourceArray[start + 2] == 0x06)
+ IsPartial = true;
+
+ AdditionalStatusSize = sourceArray[start + 3];
+ int offset = start + 4;
+
+ if (AdditionalStatusSize > 0)
+ {
+ byte[] temp = new byte[AdditionalStatusSize * 2];
+ Buffer.BlockCopy(sourceArray, offset, temp, 0, temp.Length);
+ AdditionalStatus = temp;
+ }
+
+ FullStatus = new byte[2 + (AdditionalStatusSize * 2)];
+ Buffer.BlockCopy(BitConverter.GetBytes((ushort)Status), 0, FullStatus, 0, 2);
+ if (AdditionalStatusSize > 0)
+ Buffer.BlockCopy(AdditionalStatus, 0, FullStatus, 2, AdditionalStatus.Length);
+
+ if (len > 4)
+ {
+ byte[] temp = new byte[len - 4];
+ Buffer.BlockCopy(sourceArray, start + 4, temp, 0, temp.Length);
+ ServiceData = temp;
+ }
+ }
+
+ }
+}
diff --git a/ControlLogixNET/NamespaceDoc.cs b/ControlLogixNET/NamespaceDoc.cs
new file mode 100644
index 0000000..81e1d22
--- /dev/null
+++ b/ControlLogixNET/NamespaceDoc.cs
@@ -0,0 +1,17 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.CompilerServices;
+
+namespace ControlLogixNET
+{
+ ///
+ /// This namespace contains most of the classes and utilities to interface with a ControlLogix
+ /// processor.
+ ///
+ [CompilerGenerated]
+ class NamespaceDoc
+ {
+ }
+}
diff --git a/ControlLogixNET/ProcessorFaultState.cs b/ControlLogixNET/ProcessorFaultState.cs
new file mode 100644
index 0000000..79c5c75
--- /dev/null
+++ b/ControlLogixNET/ProcessorFaultState.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ ///
+ /// Processor Fault State
+ ///
+ public enum ProcessorFaultState
+ {
+ ///
+ /// No Fault
+ ///
+ None = 0,
+ ///
+ /// Minor Recoverable Fault
+ ///
+ MinorRecoverable = 1,
+ ///
+ /// Minor Unrecoverable Fault
+ ///
+ MinorUnrecoverable = 2,
+ ///
+ /// Major Recoverable Fault
+ ///
+ MajorRecoverable = 3,
+ ///
+ /// Major Unrecoverable Fault
+ ///
+ MajorUnrecoverable = 4
+ }
+}
diff --git a/ControlLogixNET/ProcessorKeySwitch.cs b/ControlLogixNET/ProcessorKeySwitch.cs
new file mode 100644
index 0000000..272e1a3
--- /dev/null
+++ b/ControlLogixNET/ProcessorKeySwitch.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ ///
+ /// Position of the Processor Key Switch
+ ///
+ public enum ProcessorKeySwitch
+ {
+ ///
+ /// Unknown Position
+ ///
+ Unknown = 0,
+ ///
+ /// Run Mode
+ ///
+ RunMode = 1,
+ ///
+ /// Program Mode
+ ///
+ ProgramMode = 2,
+ ///
+ /// Remote Mode
+ ///
+ RemoteMode = 3
+ }
+}
diff --git a/ControlLogixNET/ProcessorResetType.cs b/ControlLogixNET/ProcessorResetType.cs
new file mode 100644
index 0000000..b309fa6
--- /dev/null
+++ b/ControlLogixNET/ProcessorResetType.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ ///
+ /// Used to determine the type of reset to perform
+ ///
+ internal enum ProcessorResetType
+ {
+ ///
+ /// Emulate as closely as possible to a power cycle
+ ///
+ PowerCycle = 0,
+ ///
+ /// Return to the out-of-the-box configuration, then power cycle
+ ///
+ FactoryReset = 1
+ }
+}
diff --git a/ControlLogixNET/ProcessorState.cs b/ControlLogixNET/ProcessorState.cs
new file mode 100644
index 0000000..3231733
--- /dev/null
+++ b/ControlLogixNET/ProcessorState.cs
@@ -0,0 +1,46 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ ///
+ /// State of the ControlLogix Processor
+ ///
+ public enum ProcessorState
+ {
+ ///
+ /// Solid Red (Power-Up)
+ ///
+ SolidRed = 0,
+ ///
+ /// Firmware Update Mode
+ ///
+ FirmwareUpdate = 1,
+ ///
+ /// Communication Fault
+ ///
+ CommunicationFault = 2,
+ ///
+ /// Awaiting Connection
+ ///
+ AwaitingConnection = 3,
+ ///
+ /// Bad Configuration
+ ///
+ ConfigurationBad = 4,
+ ///
+ /// Major Fault
+ ///
+ MajorFault = 5,
+ ///
+ /// Connected
+ ///
+ Connected = 6,
+ ///
+ /// Program Mode
+ ///
+ ProgramMode = 7
+ }
+}
diff --git a/ControlLogixNET/Properties/AssemblyInfo.cs b/ControlLogixNET/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..19070ca
--- /dev/null
+++ b/ControlLogixNET/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ControlLogixNET")]
+[assembly: AssemblyDescription("Library for interfacing to Allen-Bradley ControlLogix Processors")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("Ingenious LLC")]
+[assembly: AssemblyProduct("ControlLogixNET")]
+[assembly: AssemblyCopyright("Copyright © Ingenious LLC 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("d9df3c0d-e310-4292-bada-09529709156a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.1.*")]
+[assembly: AssemblyFileVersion("1.1.*")]
diff --git a/ControlLogixNET/ReadDataServiceReply.cs b/ControlLogixNET/ReadDataServiceReply.cs
new file mode 100644
index 0000000..bf6c6dc
--- /dev/null
+++ b/ControlLogixNET/ReadDataServiceReply.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using EIPNET.EIP;
+using EIPNET.CIP;
+
+namespace ControlLogixNET
+{
+ internal class ReadDataServiceReply
+ {
+ public byte Service { get; internal set; }
+ public byte Reserved { get; internal set; }
+ public ushort Status { get; internal set; }
+ public byte[] ByteStatus { get; internal set; }
+ public ushort DataType { get; internal set; }
+ public byte[] Data { get; internal set; }
+
+ public ReadDataServiceReply(EncapsReply reply)
+ {
+ //First we have to get the data item...
+ EncapsRRData rrData = new EncapsRRData();
+
+ CommonPacket cpf = new CommonPacket();
+ int temp = 0;
+ rrData.Expand(reply.EncapsData, 0, out temp);
+ cpf = rrData.CPF;
+
+ //The data item contains the information in an MR_Response
+ MR_Response response = new MR_Response();
+ response.Expand(cpf.DataItem.Data, 2, out temp);
+
+ Service = response.ReplyService;
+ Status = response.GeneralStatus;
+
+ byte[] bbTemp = new byte[4];
+ Buffer.BlockCopy(BitConverter.GetBytes(Status), 0, bbTemp, 0, 2);
+
+ if (Status == 0xFF)
+ {
+ if (response.AdditionalStatus_Size > 0)
+ Buffer.BlockCopy(response.AdditionalStatus, 0, bbTemp, 2, 2);
+ }
+
+ ByteStatus = bbTemp;
+
+ //Now check the response code...
+ if (response.GeneralStatus != 0 && response.GeneralStatus != 0x06)
+ return;
+
+ if (response.ResponseData != null)
+ {
+ //Now we suck out the data type...
+ DataType = BitConverter.ToUInt16(response.ResponseData, 0);
+ byte[] tempB = new byte[response.ResponseData.Length - 2];
+ Buffer.BlockCopy(response.ResponseData, 2, tempB, 0, tempB.Length);
+ Data = tempB;
+ }
+ else
+ {
+ DataType = 0x0000;
+ }
+
+
+ }
+
+ }
+}
diff --git a/ControlLogixNET/ReadDataServiceRequest.cs b/ControlLogixNET/ReadDataServiceRequest.cs
new file mode 100644
index 0000000..585284b
--- /dev/null
+++ b/ControlLogixNET/ReadDataServiceRequest.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ internal class ReadDataServiceRequest
+ {
+ public byte Service { get; set; }
+ public byte PathSize { get; set; }
+ public byte[] Path { get; set; }
+ public ushort Elements { get; set; }
+ public uint DataOffset { get; set; }
+ public bool IsFragmented { get; set; }
+ public byte[] AddtlData { get; set; }
+
+ public int Size { get { return 2 + (PathSize * 2) + 2 + (IsFragmented ? 4 : 0) + (AddtlData == null ? 0 : AddtlData.Length); } }
+
+ public byte[] Pack()
+ {
+ byte[] retVal = new byte[Size];
+ if (AddtlData != null)
+ retVal = new byte[Size - 2];
+ retVal[0] = Service;
+ retVal[1] = (byte)(PathSize);
+ Buffer.BlockCopy(Path, 0, retVal, 2, Path.Length);
+ int offset = 2 + (2 * PathSize);
+
+ if (AddtlData != null)
+ {
+ Buffer.BlockCopy(AddtlData, 0, retVal, offset, AddtlData.Length);
+ offset += AddtlData.Length;
+ }
+ else
+ {
+ if (IsFragmented)
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes(Elements), 0, retVal, offset, 2);
+ offset += 2;
+ Buffer.BlockCopy(BitConverter.GetBytes(DataOffset), 0, retVal, offset, 4);
+ offset += 4;
+ //Buffer.BlockCopy(BitConverter.GetBytes(Elements), 0, retVal, retVal.Length - 6, 2);
+ //Buffer.BlockCopy(BitConverter.GetBytes(DataOffset), 0, retVal, retVal.Length - 4, 4);
+ }
+ else
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes(Elements), 0, retVal, offset, 2);
+ offset += 2;
+ //Buffer.BlockCopy(BitConverter.GetBytes(Elements), 0, retVal, retVal.Length - 2, 2);
+ }
+ }
+
+ return retVal;
+ }
+ }
+}
diff --git a/ControlLogixNET/ReadTemplateRequest.cs b/ControlLogixNET/ReadTemplateRequest.cs
new file mode 100644
index 0000000..077c03e
--- /dev/null
+++ b/ControlLogixNET/ReadTemplateRequest.cs
@@ -0,0 +1,12 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ internal class ReadTemplateRequest
+ {
+
+ }
+}
diff --git a/ControlLogixNET/Resources/ErrorStrings.Designer.cs b/ControlLogixNET/Resources/ErrorStrings.Designer.cs
new file mode 100644
index 0000000..d7fb146
--- /dev/null
+++ b/ControlLogixNET/Resources/ErrorStrings.Designer.cs
@@ -0,0 +1,297 @@
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.235
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+namespace ControlLogixNET.Resources {
+ using System;
+
+
+ ///
+ /// A strongly-typed resource class, for looking up localized strings, etc.
+ ///
+ // This class was auto-generated by the StronglyTypedResourceBuilder
+ // class via a tool like ResGen or Visual Studio.
+ // To add or remove a member, edit your .ResX file then rerun ResGen
+ // with the /str option, or rebuild your VS project.
+ [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
+ [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
+ [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
+ internal class ErrorStrings {
+
+ private static global::System.Resources.ResourceManager resourceMan;
+
+ private static global::System.Globalization.CultureInfo resourceCulture;
+
+ [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")]
+ internal ErrorStrings() {
+ }
+
+ ///
+ /// Returns the cached ResourceManager instance used by this class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Resources.ResourceManager ResourceManager {
+ get {
+ if (object.ReferenceEquals(resourceMan, null)) {
+ global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ControlLogixNET.Resources.ErrorStrings", typeof(ErrorStrings).Assembly);
+ resourceMan = temp;
+ }
+ return resourceMan;
+ }
+ }
+
+ ///
+ /// Overrides the current thread's CurrentUICulture property for all
+ /// resource lookups using this strongly typed resource class.
+ ///
+ [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)]
+ internal static global::System.Globalization.CultureInfo Culture {
+ get {
+ return resourceCulture;
+ }
+ set {
+ resourceCulture = value;
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The abbreviated type does not match the data type of the data object..
+ ///
+ internal static string AbbreviatedTypeError {
+ get {
+ return ResourceManager.GetString("AbbreviatedTypeError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Attempted to access beyond the end of the object..
+ ///
+ internal static string AccessBeyondObject {
+ get {
+ return ResourceManager.GetString("AccessBeyondObject", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The specified array length does not match the destination array length..
+ ///
+ internal static string ArrayLengthException {
+ get {
+ return ResourceManager.GetString("ArrayLengthException", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to An error occurred trying to process one of the attributes..
+ ///
+ internal static string AttributeError {
+ get {
+ return ResourceManager.GetString("AttributeError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The beginning offset was beyond the end of the template..
+ ///
+ internal static string BeginOffsetError {
+ get {
+ return ResourceManager.GetString("BeginOffsetError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Device does not support CIP.
+ ///
+ internal static string CIPNotSupported {
+ get {
+ return ResourceManager.GetString("CIPNotSupported", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The dimensions have not been set for this tag. You must set the dimensions before using the multi-dimensional index by using the SetMultipleDimensions function on the tag..
+ ///
+ internal static string DimensionsNotSet {
+ get {
+ return ResourceManager.GetString("DimensionsNotSet", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Specified group already exists on the processor..
+ ///
+ internal static string GroupExists {
+ get {
+ return ResourceManager.GetString("GroupExists", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The specified group was not found on the processor..
+ ///
+ internal static string GroupNotFound {
+ get {
+ return ResourceManager.GetString("GroupNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Argument must be a "LogixTag"..
+ ///
+ internal static string IncorrectArgTagType {
+ get {
+ return ResourceManager.GetString("IncorrectArgTagType", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to An insufficient number of attributes were provided compared to the attribute count..
+ ///
+ internal static string InsufficientAttributes {
+ get {
+ return ResourceManager.GetString("InsufficientAttributes", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The IOI word length did not match the amount of IOI which was processed..
+ ///
+ internal static string InvalidIOILength {
+ get {
+ return ResourceManager.GetString("InvalidIOILength", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The processor did not return enough data to complete the data structure..
+ ///
+ internal static string InvalidResponseSize {
+ get {
+ return ResourceManager.GetString("InvalidResponseSize", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The item referenced could not be found on the processor..
+ ///
+ internal static string ItemNotFound {
+ get {
+ return ResourceManager.GetString("ItemNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The IOI path could not be deciphered by the processor or the matching tag does not exist..
+ ///
+ internal static string MalformedIOI {
+ get {
+ return ResourceManager.GetString("MalformedIOI", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Not enough data was sent to the processor to execute the command..
+ ///
+ internal static string NotEnoughData {
+ get {
+ return ResourceManager.GetString("NotEnoughData", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Could not establish connection with the processor..
+ ///
+ internal static string ProcessorNotConnected {
+ get {
+ return ResourceManager.GetString("ProcessorNotConnected", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Session could not be established..
+ ///
+ internal static string SessionNotEstablished {
+ get {
+ return ResourceManager.GetString("SessionNotEstablished", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Session could not be registered with the processor..
+ ///
+ internal static string SessionNotRegistered {
+ get {
+ return ResourceManager.GetString("SessionNotRegistered", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Session connected and registered..
+ ///
+ internal static string SessionRegistered {
+ get {
+ return ResourceManager.GetString("SessionRegistered", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to A socket error has occurred. Check the error code for the socket exception number..
+ ///
+ internal static string SocketError {
+ get {
+ return ResourceManager.GetString("SocketError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Tag could not be found on the processor..
+ ///
+ internal static string TagNotFound {
+ get {
+ return ResourceManager.GetString("TagNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The passed in object could not be converted to the required type..
+ ///
+ internal static string TypeConversionError {
+ get {
+ return ResourceManager.GetString("TypeConversionError", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The type returned by the processor does not match the tag type. The processor returned a .
+ ///
+ internal static string TypeMismatch {
+ get {
+ return ResourceManager.GetString("TypeMismatch", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to The specified field of the UDT does not exist in this structure..
+ ///
+ internal static string UDTMemberNotFound {
+ get {
+ return ResourceManager.GetString("UDTMemberNotFound", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to An unknown error has occurred..
+ ///
+ internal static string Unknown {
+ get {
+ return ResourceManager.GetString("Unknown", resourceCulture);
+ }
+ }
+ }
+}
diff --git a/ControlLogixNET/Resources/ErrorStrings.resx b/ControlLogixNET/Resources/ErrorStrings.resx
new file mode 100644
index 0000000..dbbd4aa
--- /dev/null
+++ b/ControlLogixNET/Resources/ErrorStrings.resx
@@ -0,0 +1,198 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ The abbreviated type does not match the data type of the data object.
+
+
+ Attempted to access beyond the end of the object.
+
+
+ The specified array length does not match the destination array length.
+
+
+ An error occurred trying to process one of the attributes.
+
+
+ The beginning offset was beyond the end of the template.
+
+
+ Device does not support CIP
+
+
+ The dimensions have not been set for this tag. You must set the dimensions before using the multi-dimensional index by using the SetMultipleDimensions function on the tag.
+
+
+ Specified group already exists on the processor.
+
+
+ The specified group was not found on the processor.
+
+
+ Argument must be a "LogixTag".
+
+
+ An insufficient number of attributes were provided compared to the attribute count.
+
+
+ The IOI word length did not match the amount of IOI which was processed.
+
+
+ The processor did not return enough data to complete the data structure.
+
+
+ The item referenced could not be found on the processor.
+
+
+ The IOI path could not be deciphered by the processor or the matching tag does not exist.
+
+
+ Not enough data was sent to the processor to execute the command.
+
+
+ Could not establish connection with the processor.
+
+
+ Session could not be established.
+
+
+ Session could not be registered with the processor.
+
+
+ Session connected and registered.
+
+
+ A socket error has occurred. Check the error code for the socket exception number.
+
+
+ Tag could not be found on the processor.
+
+
+ The passed in object could not be converted to the required type.
+
+
+ The type returned by the processor does not match the tag type. The processor returned a
+
+
+ The specified field of the UDT does not exist in this structure.
+
+
+ An unknown error has occurred.
+
+
\ No newline at end of file
diff --git a/ControlLogixNET/SequenceNumberGenerator.cs b/ControlLogixNET/SequenceNumberGenerator.cs
new file mode 100644
index 0000000..076e180
--- /dev/null
+++ b/ControlLogixNET/SequenceNumberGenerator.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ internal static class SequenceNumberGenerator
+ {
+
+ private static object _lockObject;
+ private static ushort _sequenceNum;
+
+
+ public static ushort SequenceNumber
+ {
+ get
+ {
+ lock (_lockObject)
+ {
+ if (_sequenceNum == ushort.MaxValue)
+ _sequenceNum = ushort.MinValue;
+ _sequenceNum++;
+ return _sequenceNum;
+ }
+ }
+ }
+
+ static SequenceNumberGenerator()
+ {
+ _lockObject = new object();
+ }
+
+ }
+}
diff --git a/ControlLogixNET/TemplateInfo.cs b/ControlLogixNET/TemplateInfo.cs
new file mode 100644
index 0000000..b069c7b
--- /dev/null
+++ b/ControlLogixNET/TemplateInfo.cs
@@ -0,0 +1,150 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using ControlLogixNET.LogixType;
+using EIPNET.CIP;
+
+namespace ControlLogixNET
+{
+ internal class MemberInfo
+ {
+ public ushort MemberType;
+ public ushort Info;
+ public int MemberOffset;
+ public string MemberName;
+ public int MemberSize;
+ public LogixTypes LogixType;
+ }
+
+ internal class TemplateInfo
+ {
+ public ushort NumberOfMembers { get; private set; }
+ public ushort TagSize { get; private set; }
+ public ushort TemplateHandle { get; private set; }
+ public string TemplateName { get; private set; }
+ public List MemberInfo { get; private set; }
+
+ public TemplateInfo(byte[] templateData, GetStructAttribsReply attribs)
+ {
+ MemberInfo = new List();
+
+ //Ok, this is a little confusing, there's supposed to be a header, but
+ //it seems that the data returned is not in the format specified in the
+ //1756-RM005 publication. There is no header data (meaning we can't
+ //know how many elements are in the template), so we kinda have to
+ //pick through and cheat... Luckily this data is found in the
+ //GetStructAttribs reply
+
+ //The format of the data returned is:
+ //[2:Info][2:Type][4:Offset]
+ //When we reach a byte that is greater than 0x00
+ int offset = 0;
+ int lastMember = -1;
+ int lastOffset = 0;
+
+ for (int i = 0; i < attribs.MemberCount; i++)
+ {
+ MemberInfo mi = new MemberInfo();
+ mi.Info = BitConverter.ToUInt16(templateData, offset);
+ offset += 2;
+ mi.MemberType = BitConverter.ToUInt16(templateData, offset);
+ offset += 2;
+ mi.MemberOffset = BitConverter.ToInt32(templateData, offset);
+ if (lastMember >= 0)
+ {
+ //Compute size for the last member...
+ int size = mi.MemberOffset - lastOffset;
+ MemberInfo[lastMember].MemberSize = size;
+ }
+ lastOffset = mi.MemberOffset;
+ lastMember++;
+ offset += 4;
+ switch ((CIPType)(mi.MemberType & 0x00FF))
+ {
+ case CIPType.BOOL:
+ mi.LogixType = LogixTypes.Bool;
+ break;
+ case CIPType.DINT:
+ mi.LogixType = LogixTypes.DInt;
+ break;
+ case CIPType.INT:
+ mi.LogixType = LogixTypes.Int;
+ break;
+ case CIPType.LINT:
+ mi.LogixType = LogixTypes.LInt;
+ break;
+ case CIPType.REAL:
+ mi.LogixType = LogixTypes.Real;
+ break;
+ case CIPType.SINT:
+ mi.LogixType = LogixTypes.SInt;
+ break;
+ case CIPType.STRUCT:
+ mi.LogixType = LogixTypes.User_Defined;
+ break;
+ default:
+ mi.LogixType = LogixTypes.Unknown;
+ break;
+ }
+ MemberInfo.Add(mi);
+
+ }
+
+ //Compute the size for the last member
+ int lastSize = TagSize - lastOffset;
+ MemberInfo[MemberInfo.Count - 1].MemberSize = lastSize;
+
+ NumberOfMembers = (ushort)MemberInfo.Count;
+ TemplateHandle = attribs.Handle;
+ TagSize = attribs.MemorySize;
+
+ //And now we have to go through the rest of the data and pick out
+ //null terminated strings...
+ int start = offset;
+ string currentStr = "";
+ int idx = -1;
+ for (int i = start; i < templateData.Length; i++)
+ {
+ if (templateData[i] == 0x00)
+ {
+ if (string.IsNullOrEmpty(currentStr))
+ continue;
+
+ if (idx == -1)
+ {
+ //This is the structure name...
+ TemplateName = currentStr;
+ if (TemplateName.Contains(';'))
+ TemplateName = TemplateName.Substring(0, TemplateName.IndexOf(';'));
+ }
+ else
+ {
+ MemberInfo[idx].MemberName = currentStr;
+ }
+
+ currentStr = string.Empty;
+ idx++;
+ }
+ else
+ {
+ currentStr += (char)templateData[i];
+ }
+ }
+
+ }
+
+ public override string ToString()
+ {
+ StringBuilder sb = new StringBuilder();
+ sb.AppendLine(TemplateName);
+
+ for (int i = 0; i < MemberInfo.Count; i++)
+ {
+ sb.AppendLine("\t" + MemberInfo[i].MemberName);
+ }
+
+ return sb.ToString();
+ }
+ }
+}
diff --git a/ControlLogixNET/TypeConverter.cs b/ControlLogixNET/TypeConverter.cs
new file mode 100644
index 0000000..cd5fc4d
--- /dev/null
+++ b/ControlLogixNET/TypeConverter.cs
@@ -0,0 +1,244 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ internal static class TypeConverter
+ {
+ public static int[] GetInt32Array(byte[] sourceArray, int offset, int length)
+ {
+ int skipSize = 4;
+ List retVals = new List();
+
+ if (sourceArray.Length < offset + length)
+ throw new IndexOutOfRangeException("Offset and length must refer to a position in the array");
+
+ for (int i = offset; i < offset + length; i += skipSize)
+ {
+ retVals.Add(BitConverter.ToInt32(sourceArray, i));
+ }
+
+ return retVals.ToArray();
+ }
+
+ public static long[] GetInt64Array(byte[] sourceArray, int offset, int length)
+ {
+ int skipSize = 8;
+ List retVals = new List();
+
+ if (sourceArray.Length < offset + length)
+ throw new IndexOutOfRangeException("Offset and length must refer to a position in the array");
+
+ for (int i = offset; i < offset + length; i += skipSize)
+ {
+ retVals.Add(BitConverter.ToInt64(sourceArray, i));
+ }
+
+ return retVals.ToArray();
+ }
+
+ public static short[] GetShortArray(byte[] sourceArray, int offset, int length)
+ {
+ int skipSize = 2;
+ List retVals = new List();
+
+ if (sourceArray.Length < offset + length)
+ throw new IndexOutOfRangeException("Offset and length must refer to a position in the array");
+
+ for (int i = offset; i < offset + length; i += skipSize)
+ {
+ retVals.Add(BitConverter.ToInt16(sourceArray, i));
+ }
+
+ return retVals.ToArray();
+ }
+
+ public static float[] GetFloatArray(byte[] sourceArray, int offset, int length)
+ {
+ int skipSize = 4;
+ List retVals = new List();
+
+ if (sourceArray.Length < offset + length)
+ throw new IndexOutOfRangeException("Offset and length must refer to a position in the array");
+
+ for (int i = offset; i < offset + length; i += skipSize)
+ {
+ retVals.Add(BitConverter.ToSingle(sourceArray, i));
+ }
+
+ return retVals.ToArray();
+ }
+
+ public static byte[] GetByteArray(byte[] sourceArray, int offset, int length)
+ {
+ byte[] temp = new byte[length];
+ Buffer.BlockCopy(sourceArray, (int)offset, temp, 0, length);
+ return temp;
+ }
+
+ public static bool[] GetBoolArray(int[] sourceArray)
+ {
+ List retVal = new List();
+ for (int i = 0; i < sourceArray.Length; i++)
+ {
+ for (int b = 0; b < 32; b++)
+ {
+ int mask = 1 << b;
+ if ((sourceArray[i] & mask) == mask)
+ retVal.Add(true);
+ else
+ retVal.Add(false);
+ }
+ }
+ return retVal.ToArray();
+ }
+
+ public static int[] GetBoolArray(bool[] sourceArray)
+ {
+ List retVal = new List();
+ int current = 0;
+ int bit = 0;
+
+ for (int i = 0; i < sourceArray.Length; i++)
+ {
+ if (sourceArray[i])
+ {
+ current &= 0x01 << bit;
+ }
+
+ bit++;
+
+ if (bit > 31)
+ {
+ retVal.Add(current);
+ current = 0;
+ bit = 0;
+ }
+ }
+
+ return retVal.ToArray();
+ }
+
+#if MONO
+ public static byte[] GetBytes(int[] intArray)
+ {
+ return GetBytes(intArray, 0);
+ }
+#endif
+#if MONO
+ public static byte[] GetBytes(int[] intArray, int sizeInBytes)
+#else
+ public static byte[] GetBytes(int[] intArray, int sizeInBytes = 0)
+#endif
+ {
+ if (sizeInBytes == 0)
+ sizeInBytes = intArray.Length * 4;
+
+ List retVal = new List();
+ for (int i = 0; i < sizeInBytes; i++)
+ {
+ if (i * 4 < intArray.Length)
+ {
+ retVal.AddRange(BitConverter.GetBytes(intArray[i / 4]));
+ i += 3;
+ }
+ else
+ retVal.Add(0);
+ }
+
+ return retVal.ToArray();
+ }
+
+#if MONO
+ public static byte[] GetBytes(short[] shortArray)
+ {
+ return GetBytes(shortArray, 0);
+ }
+#endif
+#if MONO
+ public static byte[] GetBytes(short[] shortArray, int sizeInBytes)
+#else
+ public static byte[] GetBytes(short[] shortArray, int sizeInBytes = 0)
+#endif
+ {
+ if (sizeInBytes == 0)
+ sizeInBytes = shortArray.Length * 2;
+
+ List retVal = new List();
+ for (int i = 0; i < sizeInBytes; i++)
+ {
+ if (i * 2 < shortArray.Length)
+ {
+ retVal.AddRange(BitConverter.GetBytes(shortArray[i / 2]));
+ i += 1;
+ }
+ else
+ retVal.Add(0);
+ }
+
+ return retVal.ToArray();
+ }
+
+#if MONO
+ public static byte[] GetBytes(long[] longArray)
+ {
+ return GetBytes(longArray, 0);
+ }
+#endif
+#if MONO
+ public static byte[] GetBytes(long[] longArray, int sizeInBytes)
+#else
+ public static byte[] GetBytes(long[] longArray, int sizeInBytes = 0)
+#endif
+ {
+ if (sizeInBytes == 0)
+ sizeInBytes = longArray.Length * 8;
+
+ List retVal = new List();
+ for (int i = 0; i < sizeInBytes; i++)
+ {
+ if (i * 8 < longArray.Length)
+ {
+ retVal.AddRange(BitConverter.GetBytes(longArray[i / 4]));
+ i += 7;
+ }
+ else
+ retVal.Add(0);
+ }
+
+ return retVal.ToArray();
+ }
+
+#if MONO
+ public static byte[] GetBytes(float[] floatArray)
+ {
+ return GetBytes(floatArray, 0);
+ }
+#endif
+#if MONO
+ public static byte[] GetBytes(float[] floatArray, int sizeInBytes)
+#else
+ public static byte[] GetBytes(float[] floatArray, int sizeInBytes = 0)
+#endif
+ {
+ if (sizeInBytes == 0)
+ sizeInBytes = floatArray.Length * 4;
+
+ List retVal = new List();
+ for (int i = 0; i < sizeInBytes; i++)
+ {
+ if (i * 4 < floatArray.Length)
+ {
+ retVal.AddRange(BitConverter.GetBytes(floatArray[i / 4]));
+ i += 3;
+ }
+ else
+ retVal.Add(0);
+ }
+
+ return retVal.ToArray();
+ }
+ }
+}
diff --git a/ControlLogixNET/UtilityBelt.cs b/ControlLogixNET/UtilityBelt.cs
new file mode 100644
index 0000000..65fed18
--- /dev/null
+++ b/ControlLogixNET/UtilityBelt.cs
@@ -0,0 +1,63 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ internal static class UtilityBelt
+ {
+ ///
+ /// Compares 2 arrays. The arrays must be the same size...
+ ///
+ /// First array
+ /// Second array
+ /// True if they are equal, false if they are not
+ public static bool CompareArrays(byte[] array1, byte[] array2)
+ {
+ if (array1.Length != array2.Length)
+ return false;
+
+ for (int i = 0; i < array1.Length; i++)
+ {
+ if (array1[i] != array2[i])
+ return false;
+ }
+
+ return true;
+ }
+
+#if MONO
+ public static bool CompareArrays(byte[] sourceArray, byte[] compareArray, uint offset)
+ {
+ return CompareArrays(sourceArray, compareArray, offset, 0);
+ }
+#endif
+
+#if MONO
+ public static bool CompareArrays(byte[] sourceArray, byte[] compareArray, uint offset, uint len)
+#else
+ ///
+ /// Compares a source array at the specified offset to the compare array
+ ///
+ /// Source array
+ /// Compare array
+ /// Position to start compare in the source array
+ /// True if they are equal, false if not
+ public static bool CompareArrays(byte[] sourceArray, byte[] compareArray, uint offset, uint len = 0)
+#endif
+ {
+ int compPos = 0;
+ if (len == 0)
+ len = (uint)compareArray.Length;
+ for (uint i = offset; i < len; i++)
+ {
+ if (sourceArray[i] != compareArray[compPos])
+ return false;
+ compPos++;
+ }
+ return true;
+ }
+
+ }
+}
diff --git a/ControlLogixNET/WriteDataServiceReply.cs b/ControlLogixNET/WriteDataServiceReply.cs
new file mode 100644
index 0000000..e370fda
--- /dev/null
+++ b/ControlLogixNET/WriteDataServiceReply.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using EIPNET.EIP;
+using EIPNET.CIP;
+
+namespace ControlLogixNET
+{
+ internal class WriteDataServiceReply
+ {
+ public byte Service { get; internal set; }
+ public byte Reserved { get; internal set; }
+ public ushort Status { get; internal set; }
+
+ public WriteDataServiceReply(EncapsReply reply)
+ {
+ EncapsRRData rrData = new EncapsRRData();
+
+ CommonPacket cpf = new CommonPacket();
+ int temp = 0;
+ rrData.Expand(reply.EncapsData, 0, out temp);
+ cpf = rrData.CPF;
+
+ MR_Response response = new MR_Response();
+ response.Expand(cpf.DataItem.Data, 2, out temp);
+
+ if (response.GeneralStatus != 0)
+ return;
+
+ Service = response.ReplyService;
+ Status = response.GeneralStatus;
+ }
+ }
+}
diff --git a/ControlLogixNET/WriteDataServiceRequest.cs b/ControlLogixNET/WriteDataServiceRequest.cs
new file mode 100644
index 0000000..2ba0fcb
--- /dev/null
+++ b/ControlLogixNET/WriteDataServiceRequest.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ControlLogixNET
+{
+ internal class WriteDataServiceRequest
+ {
+ public byte Service { get; set; }
+ public byte PathSize { get; set; }
+ public byte[] Path { get; set; }
+ public ushort DataType { get; set; }
+ public ushort StructHandle { get; set; }
+ public ushort Elements { get; set; }
+ public byte[] Data { get; set; }
+ public bool IsFragmented { get; set; }
+ public uint Offset { get; set; }
+
+ public int Size { get { return 6 + (2 * PathSize) + (Data == null ? 0 : Data.Length) + (IsFragmented ? 4 : 0) + (StructHandle == 0 ? 0 : 2); } }
+
+ public byte[] Pack()
+ {
+ byte[] retVal = new byte[Size];
+ retVal[0] = Service;
+ retVal[1] = PathSize;
+ Buffer.BlockCopy(Path, 0, retVal, 2, Path.Length);
+ int offset = 2 + (2 * PathSize);
+ Buffer.BlockCopy(BitConverter.GetBytes(DataType), 0, retVal, offset, 2);
+ offset += 2;
+ if (StructHandle != 0)
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes(StructHandle), 0, retVal, offset, 2);
+ offset += 2;
+ }
+ Buffer.BlockCopy(BitConverter.GetBytes(Elements), 0, retVal, offset, 2);
+ offset += 2;
+ if (IsFragmented)
+ {
+ Buffer.BlockCopy(BitConverter.GetBytes(Offset), 0, retVal, offset, 4);
+ offset += 4;
+ }
+ if (Data != null)
+ Buffer.BlockCopy(Data, 0, retVal, offset, Data.Length);
+
+ return retVal;
+ }
+ }
+}
diff --git a/Examples/ArrayTags/ArrayTags.csproj b/Examples/ArrayTags/ArrayTags.csproj
new file mode 100644
index 0000000..4c56029
--- /dev/null
+++ b/Examples/ArrayTags/ArrayTags.csproj
@@ -0,0 +1,79 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {FA46ECC1-6E51-47FA-B025-A38BF75515B8}
+ Exe
+ Properties
+ ArrayTags
+ ArrayTags
+ v4.0
+ 512
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\ControlLogixNET_Locked\ControlLogixNET.dll
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\EIPNET_Locked\EIPNET.dll
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\ICommon_Locked\ICommon.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Examples/ArrayTags/ArrayTags.csproj.vspscc b/Examples/ArrayTags/ArrayTags.csproj.vspscc
new file mode 100644
index 0000000..b6d3289
--- /dev/null
+++ b/Examples/ArrayTags/ArrayTags.csproj.vspscc
@@ -0,0 +1,10 @@
+""
+{
+"FILE_VERSION" = "9237"
+"ENLISTMENT_CHOICE" = "NEVER"
+"PROJECT_FILE_RELATIVE_PATH" = ""
+"NUMBER_OF_EXCLUDED_FILES" = "0"
+"ORIGINAL_PROJECT_FILE_PATH" = ""
+"NUMBER_OF_NESTED_PROJECTS" = "0"
+"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
+}
diff --git a/Examples/ArrayTags/Program.cs b/Examples/ArrayTags/Program.cs
new file mode 100644
index 0000000..f5e8902
--- /dev/null
+++ b/Examples/ArrayTags/Program.cs
@@ -0,0 +1,121 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using ControlLogixNET;
+using ControlLogixNET.LogixType;
+using System.Threading;
+
+namespace ArrayTags
+{
+ class Program
+ {
+ /*
+ * HOW TO USE THIS SAMPLE
+ *
+ * 1. First change the hostNameOrIp to the IP address or host name of your PLC
+ * 2. Then change the path to be the path to your PLC, see comments below
+ * 3. Create a 1 dimensional DINT array on the processor called dintArray1[10]
+ * 4. Create a 2 dimensional DINT array on the processor called dintArray2[10,10]
+ * 5. Create a 3 dimensional DINT array on the processor called dintArray3[10,10,10]
+ * 6. Run
+ *
+ */
+
+ static void Main(string[] args)
+ {
+ //First we create the processor object. Typically the path is the slot
+ //number of the processor module in the backplane, but if your communications
+ //card is not in the same chassis as your processor, this is the path through
+ //the chassis to get to your processor. You will have to add a 1 for every
+ //chassis you go through, for example:
+ //Chassis 1: ENBT card in Slot 1 (slot is irrelavent), ControlNet Card in Slot 2
+ //Chassis 2: L61 in Slot 4
+ //Path would be: { 2, 1, 4 }
+ //Basically it's the target slot, 1 for backplane, target slot, 1 for backplane...
+ //until you get to the processor.
+ string hostNameOrIp = "192.168.1.10";
+ byte[] path = new byte[] { 1 };
+ LogixProcessor processor = new LogixProcessor(hostNameOrIp, path);
+
+ //The processor has to be connected before you add any tags or tag groups.
+ if (!processor.Connect())
+ {
+ Console.WriteLine("Could not connect to the processor");
+ Console.ReadKey(false);
+ return;
+ }
+
+ //First create a group. Groups are much more efficient at reading and writing
+ //large numbers of tags.
+ LogixTagGroup myGroup = processor.CreateTagGroup("MyGroup");
+
+ //Now let's create our first array. The number of elements is the TOTAL number
+ //of elements to read, in all dimensions.
+ LogixDINT dintArray1 = new LogixDINT("dintArray1", processor, 10);
+
+ //We don't need to set the number of dimensions on the tag here because it
+ //assumes that it's a single dimension tag. All tags are set up to be arrays
+ //by default, the .Value or similar member always returns the 0th element
+ //of the array. With a tag that is not an array, that is where the value is.
+
+ //Let's create the 2 dimensional array
+ LogixDINT dintArray2 = new LogixDINT("dintArray2", processor, 100);
+
+ //The number of elements are the subscripts multiplied by each other. In this
+ //case, 10*10 = 100. If you put a lower value here you will only read that
+ //much of the array. ControlLogix packs it's arrays in row major format, so
+ //just keep that in mind if reading partial arrays.
+
+ //If you want to set it up to read with a multidimensional accessor, we need
+ //to tell the tag what the size of the dimensions are.
+ dintArray2.SetMultipleDimensions(10, 10);
+
+ //We can now access the tag by the tagName[row,column] format. If you didn't
+ //set the size, you would get an exception when trying to access the tag
+ //using that format.
+
+ //Let's create the last tag
+ LogixDINT dintArray3 = new LogixDINT("dintArray3", processor, 1000);
+
+ //Set the dimensions
+ dintArray3.SetMultipleDimensions(10, 10, 10);
+
+ //Now let's add our tags to the tag group...
+ myGroup.AddTag(dintArray1);
+ myGroup.AddTag(dintArray2);
+ myGroup.AddTag(dintArray3);
+
+ Console.WriteLine("6D Systems LLC\n\n");
+ Console.WriteLine("Tags created...");
+
+ //Now let's pick out some random members and display them...
+ Console.WriteLine("dintArray1[4] = " + dintArray1[4].ToString());
+ Console.WriteLine("dintArray2[5,2] = " + dintArray2[5, 2].ToString());
+ Console.WriteLine("dintArray3[4,7,3] = " + dintArray3[4, 7, 3].ToString());
+ Console.WriteLine("\nPress any key to write a new value to each of the above tags");
+ Console.ReadKey(false);
+
+ //Now let's write some data to those tags...
+ Random rnd = new Random();
+ dintArray1[4] = rnd.Next(int.MinValue, int.MaxValue);
+ dintArray2[5, 2] = rnd.Next(int.MinValue, int.MaxValue);
+ dintArray3[4, 7, 3] = rnd.Next(int.MinValue, int.MaxValue);
+
+ //Let's update the tag group
+ processor.UpdateGroups();
+
+ //Now print them back out for the user...
+ Console.WriteLine("\nNew tag values...");
+ Console.WriteLine("dintArray1[4] = " + dintArray1[4].ToString());
+ Console.WriteLine("dintArray2[5,2] = " + dintArray2[5, 2].ToString());
+ Console.WriteLine("dintArray3[4,7,3] = " + dintArray3[4, 7, 3].ToString());
+ Console.WriteLine("\nPress any key to quit");
+ Console.ReadKey(false);
+
+ //Remember to disconnect from the processor
+ processor.Disconnect();
+
+ }
+ }
+}
diff --git a/Examples/ArrayTags/Properties/AssemblyInfo.cs b/Examples/ArrayTags/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..ad07f7f
--- /dev/null
+++ b/Examples/ArrayTags/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ArrayTags")]
+[assembly: AssemblyDescription("Demonstration of using Array tags in a ControlLogix Processor")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("6D Systems LLC")]
+[assembly: AssemblyProduct("ArrayTags")]
+[assembly: AssemblyCopyright("Copyright © 6D Systems LLC 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("578332e3-a9d2-403f-ae85-afda0b1de0cf")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Examples/Processor/Processor.csproj b/Examples/Processor/Processor.csproj
new file mode 100644
index 0000000..f9d84c0
--- /dev/null
+++ b/Examples/Processor/Processor.csproj
@@ -0,0 +1,77 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {54A5D090-5322-4087-8E92-4A5C5E8E194E}
+ Exe
+ Properties
+ Processor
+ Processor
+ v4.0
+ 512
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}
+ ControlLogixNET
+
+
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}
+ ICommon
+
+
+
+
+
\ No newline at end of file
diff --git a/Examples/Processor/Processor.csproj.vspscc b/Examples/Processor/Processor.csproj.vspscc
new file mode 100644
index 0000000..b6d3289
--- /dev/null
+++ b/Examples/Processor/Processor.csproj.vspscc
@@ -0,0 +1,10 @@
+""
+{
+"FILE_VERSION" = "9237"
+"ENLISTMENT_CHOICE" = "NEVER"
+"PROJECT_FILE_RELATIVE_PATH" = ""
+"NUMBER_OF_EXCLUDED_FILES" = "0"
+"ORIGINAL_PROJECT_FILE_PATH" = ""
+"NUMBER_OF_NESTED_PROJECTS" = "0"
+"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
+}
diff --git a/Examples/Processor/Program.cs b/Examples/Processor/Program.cs
new file mode 100644
index 0000000..e8d9cbc
--- /dev/null
+++ b/Examples/Processor/Program.cs
@@ -0,0 +1,156 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using ControlLogixNET;
+
+namespace Processor
+{
+ class Program
+ {
+ /*
+ * HOW TO USE THIS SAMPLE
+ *
+ * 1. First change the hostNameOrIp to the IP address or host name of your PLC
+ * 2. Then change the path to be the path to your PLC, see comments below
+ * 3. Run
+ *
+ */
+
+ static void Main(string[] args)
+ {
+ //First we create the processor object. Typically the path is the slot
+ //number of the processor module in the backplane, but if your communications
+ //card is not in the same chassis as your processor, this is the path through
+ //the chassis to get to your processor. You will have to add a 1 for every
+ //chassis you go through, for example:
+ //Chassis 1: ENBT card in Slot 1 (slot is irrelavent), ControlNet Card in Slot 2
+ //Chassis 2: L61 in Slot 4
+ //Path would be: { 2, 1, 4 }
+ //Basically it's the target slot, 1 for backplane, target slot, 1 for backplane...
+ //until you get to the processor.
+ string hostNameOrIp = "192.168.1.10";
+ byte[] path = new byte[] { 0 };
+ LogixProcessor processor = new LogixProcessor(hostNameOrIp, path);
+
+ //Connect to the PLC, you can create the events before or after the connect function
+ if (!processor.Connect())
+ {
+ Console.WriteLine("Could not connect to the processor");
+ Console.ReadKey(false);
+ return;
+ }
+
+ //Create the events, the processor state is updated every second, and if there is
+ //a change in either the fault state, key switch position, or processor state (RUN, PROGRAM, TEST),
+ //then one of these events will be fired.
+ processor.FaultStateChanged += new LogixFaultStateChangedEvent(processor_FaultStateChanged);
+ processor.KeySwitchChanged += new LogixKeyPositionChangedEvent(processor_KeySwitchChanged);
+ processor.ProcessorStateChanged += new LogixProcessorStateChangedEvent(processor_ProcessorStateChanged);
+
+ Console.WriteLine("6D Systems LLC");
+ Console.WriteLine("Processor State Example: Change the key switch, fault state, or processor\nmode to see a message displayed");
+ Console.WriteLine("\nProcessor Information:\n" + processor);
+ Console.WriteLine("\n\n");
+
+ //The processor can, through source code, be put in Program mode or Run mode. This is useful
+ //if you are developing a critical process where you want to be able to shut all the outputs
+ //off on the PLC at one time. Mode changes only work if the processor key is in Remote
+
+ //The .UserData field can be used to store any data you desire, and it will be persisted
+ //with the processor object. This is useful, for example, for storing information about
+ //a processor when it's in a dictionary...
+
+ processor.UserData = "MainPLC_1";
+
+ bool quitFlag = false;
+
+ while (!quitFlag)
+ {
+ Console.WriteLine("\n\n=============================MENU=============================");
+ Console.WriteLine("Press the 'P' key to put the processor in Program mode");
+ Console.WriteLine("Press the 'R' key to put the processor in Run mode");
+ Console.WriteLine("Press the 'U' key to display the processor UserData");
+ Console.WriteLine("Press the 'T' key to display all the tags on the processor");
+ Console.WriteLine("Press the 'Q' key to quit");
+ Console.WriteLine("==============================================================");
+
+ char key = Console.ReadKey(true).KeyChar;
+
+ switch (key)
+ {
+ case 'p':
+ case 'P':
+ Console.WriteLine("Setting processor to Program mode...");
+ processor.SetProgramMode();
+ break;
+ case 'r':
+ case 'R':
+ Console.WriteLine("Setting processor to Run mode...");
+ processor.SetRunMode();
+ break;
+ case 'u':
+ case 'U':
+ Console.WriteLine("UserData: " + (string)processor.UserData);
+ break;
+ case 't':
+ case 'T':
+ List tagInfo = processor.EnumerateTags();
+ if (tagInfo == null)
+ {
+ Console.WriteLine("No tags found");
+ break;
+ }
+ Console.WriteLine("There are " + tagInfo.Count + " tags...");
+ foreach (LogixTagInfo info in tagInfo)
+ {
+ string name = info.TagName;
+ if (info.Dimensions > 0)
+ name += "[" + info.Dimension1Size.ToString();
+ if (info.Dimensions > 1)
+ name += ", " + info.Dimension2Size.ToString();
+ if (info.Dimensions > 2)
+ name += ", " + info.Dimension3Size.ToString();
+ if (info.Dimensions > 0)
+ name += "]";
+ Console.WriteLine("\t" + name);
+ }
+ break;
+ case 'q':
+ case 'Q':
+ quitFlag = true;
+ break;
+ default:
+ break;
+ }
+ }
+
+ //Always remember to disconnect the PLC. If you forget, the PLC won't allow you to reconnect
+ //until the session times out. This is typically about 45-60 seconds.
+ processor.Disconnect();
+ }
+
+ static void processor_ProcessorStateChanged(LogixProcessor sender, LogixProcessorStateChangedEventArgs e)
+ {
+ //This function will be called whenever the processor changes state. The processor state is information
+ //like what mode it's in, if it has a communications fault, if it's in firmware update mode, etc.
+ Console.WriteLine("Processor State Changed from " + e.OldState.ToString() + " to " + e.NewState.ToString());
+ }
+
+ static void processor_KeySwitchChanged(LogixProcessor sender, LogixKeyChangedEventArgs e)
+ {
+ //This function will be called when the key switch changes position. The key switch is on the front of
+ //the processor and can either be in Run, Program or Remote mode. There is an additional member of the
+ //ProcessorKeySwitch enumeration called "Unknown" which is used when the value hasn't been read yet or
+ //can't be obtained.
+ Console.WriteLine("Processor Key Position Changed from " + e.OldPosition.ToString() + " to " + e.NewPosition.ToString());
+ }
+
+ static void processor_FaultStateChanged(LogixProcessor sender, LogixFaultStateChangedEventArgs e)
+ {
+ //This function is called when the processor fault state changes. The fault states are None, Minor
+ //Recoverable, Minor Unrecoverable, Major Recoverable, and Major Unrecoverable.
+ Console.WriteLine("Processor Fault Mode Changed from " + e.OldState.ToString() + " to " + e.NewState.ToString());
+ }
+ }
+}
diff --git a/Examples/Processor/Properties/AssemblyInfo.cs b/Examples/Processor/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..4085f4b
--- /dev/null
+++ b/Examples/Processor/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("Processor")]
+[assembly: AssemblyDescription("Example use of the LogixProcessor class")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("6D Systems LLC")]
+[assembly: AssemblyProduct("Processor")]
+[assembly: AssemblyCopyright("Copyright © 6D Systems LLC 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("547482d2-bd8e-4258-96b9-82b8848fd48d")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Examples/SimpleOperations/Program.cs b/Examples/SimpleOperations/Program.cs
new file mode 100644
index 0000000..2de7923
--- /dev/null
+++ b/Examples/SimpleOperations/Program.cs
@@ -0,0 +1,404 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using ControlLogixNET;
+using ControlLogixNET.LogixType;
+
+namespace SimpleOperations
+{
+ class Program
+ {
+ /*
+ * HOW TO USE THIS SAMPLE
+ *
+ * 1. First change the hostNameOrIp to the IP address or host name of your PLC
+ * 2. Then change the path to be the path to your PLC, see comments below
+ * 3. Run
+ *
+ */
+
+ static void Main(string[] args)
+ {
+ //First we create the processor object. Typically the path is the slot
+ //number of the processor module in the backplane, but if your communications
+ //card is not in the same chassis as your processor, this is the path through
+ //the chassis to get to your processor. You will have to add a 1 for every
+ //chassis you go through, for example:
+ //Chassis 1: ENBT card in Slot 1 (slot is irrelavent), ControlNet Card in Slot 2
+ //Chassis 2: L61 in Slot 4
+ //Path would be: { 2, 1, 4 }
+ //Basically it's the target slot, 1 for backplane, target slot, 1 for backplane...
+ //until you get to the processor.
+ string hostNameOrIp = "192.168.1.10";
+ byte[] path = new byte[] { 0 };
+ LogixProcessor processor = new LogixProcessor(hostNameOrIp, path);
+
+ //The processor has to be connected before you add any tags or tag groups.
+ if (!processor.Connect())
+ {
+ Console.WriteLine("Could not connect to the processor");
+ Console.ReadKey(false);
+ return;
+ }
+
+ string menu = "6D Systems LLC\n"
+ + "-----------------------------------------\n"
+ + "(1) - Get Information About A Tag\n"
+ + "(2) - Read a tag\n"
+ + "(3) - Write a tag\n"
+ + "(Q) - Quit\n"
+ + "-----------------------------------------\n"
+ + "Enter your choice:";
+
+ bool quitFlag = false;
+
+ while (!quitFlag)
+ {
+ Console.Clear();
+ Console.Write(menu);
+
+ string key = Console.ReadLine();
+
+ switch (key)
+ {
+ case "1":
+ TagInformation(processor);
+ break;
+ case "2":
+ ReadTag(processor);
+ break;
+ case "3":
+ WriteTag(processor);
+ break;
+ case "q":
+ case "Q":
+ quitFlag = true;
+ continue;
+ default:
+ Console.WriteLine("Invalid entry");
+ break;
+ }
+
+ Console.WriteLine("Hit any key to go back to the menu");
+ Console.ReadKey(false);
+ }
+
+ //Remember to disconnect from the processor. If you forget, the processor won't allow you
+ //to reconnect until the session times out, which is typically 60 seconds.
+
+ processor.Disconnect();
+ }
+
+ static void TagInformation(LogixProcessor processor)
+ {
+ //Getting detailed tag information is actually an expensive process. Currently
+ //there is no way to get detailed information about a tag except to request all
+ //the tag information in the PLC. The GetTagInformation will read all the tags
+ //in the PLC, then return the one you are looking for.
+ string address = GetTagAddress();
+
+ if (string.IsNullOrEmpty(address))
+ return;
+
+ LogixTagInfo tagInfo = processor.GetTagInformation(address);
+
+ if (tagInfo == null)
+ {
+ Console.WriteLine("The tag '" + address + "' could not be found on the processor");
+ Console.ReadKey(false);
+ return;
+ }
+
+ Console.WriteLine(tagInfo.ToString());
+ }
+
+ static void ReadTag(LogixProcessor processor)
+ {
+ //Reading a tag is very easy. First, create a tag...
+ string address = GetTagAddress();
+
+ if (string.IsNullOrEmpty(address))
+ return;
+
+ //Now we have to create the tag on the processor. The easiest way to
+ //do this without knowing the underlying type is to use the
+ //LogixTagFactory class.
+ LogixTag userTag = LogixTagFactory.CreateTag(address, processor);
+
+ if (userTag == null)
+ {
+ Console.WriteLine("Could not create the tag " + address + " on the processor");
+ return;
+ }
+
+ //The tag is automatically read when it is created. The LogixProcessor does this
+ //to verify the tag exists and to get type information about the tag. From this
+ //point on you can read/write the tag all you want, either by using tag groups
+ //or by directly writing it with the LogixProcessor.WriteTag() function.
+
+ //We'll demonstrate a read anyway...
+ if (!processor.ReadTag(userTag))
+ Console.WriteLine("Could not read the tag: " + userTag.LastError);
+
+ //Print the value out with our handy helper function
+ PrintTagValue(userTag);
+
+ //And go back to the main menu
+ }
+
+ static void WriteTag(LogixProcessor processor)
+ {
+ //Writing a tag is also very easy. First, create a tag...
+ string address = GetTagAddress();
+
+ if (string.IsNullOrEmpty(address))
+ return;
+
+ //Now we have to create the tag on the processor. The easiest way to
+ //do this without knowing the underlying type is to use the
+ //LogixTagFactory class.
+ LogixTag userTag = LogixTagFactory.CreateTag(address, processor);
+
+ if (userTag == null)
+ {
+ Console.WriteLine("Could not create the tag " + address + " on the processor");
+ return;
+ }
+
+ switch (userTag.LogixType)
+ {
+ case LogixTypes.Bool:
+ WriteBool(userTag, processor);
+ break;
+ case LogixTypes.DInt:
+ case LogixTypes.LInt:
+ case LogixTypes.Real:
+ case LogixTypes.SInt:
+ WriteOther(userTag, processor);
+ break;
+ case LogixTypes.Control:
+ case LogixTypes.Counter:
+ case LogixTypes.Timer:
+ case LogixTypes.User_Defined:
+ WriteStructure(userTag, processor);
+ break;
+ default:
+ Console.WriteLine("The LogixType of " + userTag.LogixType.ToString() + " is not supported in this sample");
+ return;
+ }
+
+ PrintTagValue(userTag);
+
+ //And go back to the menu
+ }
+
+ static void PrintTagValue(LogixTag tag)
+ {
+ //Now we'll determine the value of the tag...
+ switch (tag.LogixType)
+ {
+ case LogixTypes.Bool:
+ Console.WriteLine("BOOL value is: " + ((LogixBOOL)tag).Value.ToString());
+ break;
+ case LogixTypes.Control:
+ //The control tag is a lot more complicated, there is no way currently to know
+ //which member was updated, so all you can do is say it was updated, we'll print
+ //out one of the members though.
+ Console.WriteLine("Control.POS is: " + ((LogixCONTROL)tag).POS.ToString());
+ break;
+ case LogixTypes.Counter:
+ //Same as the counter above, we'll just print out the ACC value
+ Console.WriteLine("Counter.ACC value is: " + ((LogixCOUNTER)tag).ACC.ToString());
+ break;
+ case LogixTypes.DInt:
+ //Print out the value. DINT's are equivalent to int in .NET
+ Console.WriteLine("DINT value is: " + ((LogixDINT)tag).Value.ToString());
+ break;
+ case LogixTypes.Int:
+ //An INT in a logix processor is more like a short in .NET
+ Console.WriteLine("INT value is: " + ((LogixINT)tag).Value.ToString());
+ break;
+ case LogixTypes.LInt:
+ //LINT's are equivalent to long in .NET
+ Console.WriteLine("LINT value is: " + ((LogixLINT)tag).Value.ToString());
+ break;
+ case LogixTypes.Real:
+ //REALs are single precision floats
+ Console.WriteLine("REAL value is: " + ((LogixREAL)tag).Value.ToString());
+ break;
+ case LogixTypes.SInt:
+ //SINTs are signed bytes
+ Console.WriteLine("SINT value is: " + ((LogixSINT)tag).Value.ToString());
+ break;
+ case LogixTypes.String:
+ //Strings are just like .NET strings, so notice how we can skip the .StringValue
+ //member, since the .ToString() will automatically be called, which returns the
+ //same value as .StringValue
+ Console.WriteLine("STRING value is: " + ((LogixSTRING)tag));
+ break;
+ case LogixTypes.Timer:
+ //Timers again are like the CONTROL and COUNTER types
+ Console.WriteLine("Timer.ACC value is: " + ((LogixTIMER)tag).ACC.ToString());
+ break;
+ case LogixTypes.User_Defined:
+ //The only way to get the value out of a UDT, PDT, or MDT is to define the
+ //structure or know the member name you wish to read. We'll just print that
+ //we know its a UDT, MDT, or PDT that changed.
+ Console.WriteLine("User defined type");
+ break;
+ default:
+ break;
+ }
+ }
+
+ static string GetTagAddress()
+ {
+ Console.Write("\nEnter a Tag Address: ");
+
+ string address = Console.ReadLine();
+
+ if (string.IsNullOrEmpty(address))
+ {
+ Console.WriteLine("Address can't be a null or empty string");
+ return string.Empty;
+ }
+
+ return address;
+ }
+
+ static void WriteStructure(LogixTag tag, LogixProcessor processor)
+ {
+ Console.Write("The tag is a structure called " + ((LogixUDT)tag).TypeName + ", please enter a member name: ");
+ string memberName = Console.ReadLine();
+
+ //First we have to find out if the member exists, if it doesn't we can't write to it...
+ List memberNames = ((LogixUDT)tag).MemberNames;
+
+ bool hasMember = false;
+ for (int i = 0; i < memberNames.Count; i++)
+ {
+ if (string.Compare(memberNames[i], memberName) == 0)
+ {
+ hasMember = true;
+ break;
+ }
+ }
+
+ if (!hasMember)
+ {
+ Console.WriteLine("The specified member could not be found in the structure");
+ return;
+ }
+
+ Console.Write("Enter a value: ");
+ string sValue = Console.ReadLine();
+
+ //Now we have to convert it to the right type...
+ try
+ {
+ switch (tag.LogixType)
+ {
+ case LogixTypes.Bool:
+ if (sValue == "1")
+ ((LogixUDT)tag)[memberName] = true;
+ else
+ ((LogixUDT)tag)[memberName] = false;
+ break;
+ case LogixTypes.DInt:
+ ((LogixUDT)tag)[memberName] = Convert.ToInt32(sValue);
+ break;
+ case LogixTypes.Int:
+ ((LogixUDT)tag)[memberName] = Convert.ToInt16(sValue);
+ break;
+ case LogixTypes.LInt:
+ ((LogixUDT)tag)[memberName] = Convert.ToInt64(sValue);
+ break;
+ case LogixTypes.Real:
+ ((LogixUDT)tag)[memberName] = Convert.ToSingle(sValue);
+ break;
+ case LogixTypes.SInt:
+ ((LogixUDT)tag)[memberName] = Convert.ToSByte(sValue);
+ break;
+ case LogixTypes.User_Defined:
+ default:
+ Console.WriteLine("This demo does not support writing to nested structure tags");
+ return;
+ }
+
+ //At this point the tag has not been committed to the processor. The
+ //tag must be written, then read back for the value to change. The
+ //easiest way to do this with a single tag is to use the processor
+ //LogixProcessor.WriteRead() which performs the write, then the
+ //subsequent read on the tag.
+ processor.WriteRead(tag);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Could not convert " + sValue + " to the correct type for " + tag.Address);
+ }
+ }
+
+ static void WriteBool(LogixTag tag, LogixProcessor processor)
+ {
+ Console.WriteLine("Enter 1 for True, 0 for False: ");
+ char key = Console.ReadKey().KeyChar;
+
+ if (key == '1')
+ ((LogixBOOL)tag).Value = true;
+ else
+ ((LogixBOOL)tag).Value = false;
+
+ //At this point the tag has not been committed to the processor. The
+ //tag must be written, then read back for the value to change. The
+ //easiest way to do this with a single tag is to use the processor
+ //LogixProcessor.WriteRead() which performs the write, then the
+ //subsequent read on the tag.
+ processor.WriteRead(tag);
+ }
+
+ static void WriteOther(LogixTag tag, LogixProcessor processor)
+ {
+ Console.Write("Enter a value: ");
+ string sValue = Console.ReadLine();
+
+ //Now we have to convert it to the right type...
+ try
+ {
+ switch (tag.LogixType)
+ {
+ case LogixTypes.DInt:
+ ((LogixDINT)tag).Value = Convert.ToInt32(sValue);
+ break;
+ case LogixTypes.Int:
+ ((LogixINT)tag).Value = Convert.ToInt16(sValue);
+ break;
+ case LogixTypes.LInt:
+ ((LogixLINT)tag).Value = Convert.ToInt64(sValue);
+ break;
+ case LogixTypes.Real:
+ ((LogixREAL)tag).Value = Convert.ToSingle(sValue);
+ break;
+ case LogixTypes.SInt:
+ ((LogixSINT)tag).Value = Convert.ToSByte(sValue);
+ break;
+ default:
+ return;
+ }
+
+ //At this point the tag has not been committed to the processor. The
+ //tag must be written, then read back for the value to change. The
+ //easiest way to do this with a single tag is to use the processor
+ //LogixProcessor.WriteRead() which performs the write, then the
+ //subsequent read on the tag.
+ processor.WriteRead(tag);
+ }
+ catch (Exception e)
+ {
+ Console.WriteLine("Could not convert " + sValue + " to the correct type for " + tag.Address);
+ }
+
+ }
+
+ }
+}
diff --git a/Examples/SimpleOperations/Properties/AssemblyInfo.cs b/Examples/SimpleOperations/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..a894b0b
--- /dev/null
+++ b/Examples/SimpleOperations/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("SimpleOperations")]
+[assembly: AssemblyDescription("Demonstration of simple operations on a tag")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("6D Systems LLC")]
+[assembly: AssemblyProduct("SimpleOperations")]
+[assembly: AssemblyCopyright("Copyright © 6D Systems LLC 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("ce3d3091-5157-4a83-bc91-8bd17c106c6f")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Examples/SimpleOperations/SimpleOperations.csproj b/Examples/SimpleOperations/SimpleOperations.csproj
new file mode 100644
index 0000000..edf8512
--- /dev/null
+++ b/Examples/SimpleOperations/SimpleOperations.csproj
@@ -0,0 +1,77 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {E653D839-E72D-4B45-B6B3-754A8EF98AEE}
+ Exe
+ Properties
+ SimpleOperations
+ SimpleOperations
+ v4.0
+ 512
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {9A6CFFE2-0C70-45A7-92FC-88DA9000E591}
+ ControlLogixNET
+
+
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}
+ ICommon
+
+
+
+
+
\ No newline at end of file
diff --git a/Examples/SimpleOperations/SimpleOperations.csproj.vspscc b/Examples/SimpleOperations/SimpleOperations.csproj.vspscc
new file mode 100644
index 0000000..b6d3289
--- /dev/null
+++ b/Examples/SimpleOperations/SimpleOperations.csproj.vspscc
@@ -0,0 +1,10 @@
+""
+{
+"FILE_VERSION" = "9237"
+"ENLISTMENT_CHOICE" = "NEVER"
+"PROJECT_FILE_RELATIVE_PATH" = ""
+"NUMBER_OF_EXCLUDED_FILES" = "0"
+"ORIGINAL_PROJECT_FILE_PATH" = ""
+"NUMBER_OF_NESTED_PROJECTS" = "0"
+"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
+}
diff --git a/Examples/StructureTags/Program.cs b/Examples/StructureTags/Program.cs
new file mode 100644
index 0000000..2a0c8b7
--- /dev/null
+++ b/Examples/StructureTags/Program.cs
@@ -0,0 +1,123 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using ControlLogixNET;
+using ControlLogixNET.LogixType;
+
+namespace StructureTags
+{
+ class Program
+ {
+ /*
+ * HOW TO USE THIS SAMPLE
+ *
+ * 1. First change the hostNameOrIp to the IP address or host name of your PLC
+ * 2. Then change the path to be the path to your PLC, see comments below
+ * 3. Create a user defined type tag in your processor called myUDT1
+ * 4. Create an ALARM tag in your processor called myAlarm1
+ * 5. Run
+ *
+ */
+
+ static void Main(string[] args)
+ {
+ //First we create the processor object. Typically the path is the slot
+ //number of the processor module in the backplane, but if your communications
+ //card is not in the same chassis as your processor, this is the path through
+ //the chassis to get to your processor. You will have to add a 1 for every
+ //chassis you go through, for example:
+ //Chassis 1: ENBT card in Slot 1 (slot is irrelavent), ControlNet Card in Slot 2
+ //Chassis 2: L61 in Slot 4
+ //Path would be: { 2, 1, 4 }
+ //Basically it's the target slot, 1 for backplane, target slot, 1 for backplane...
+ //until you get to the processor.
+ string hostNameOrIp = "192.168.1.10";
+ byte[] path = new byte[] { 1 };
+ LogixProcessor processor = new LogixProcessor(hostNameOrIp, path);
+
+ //The processor has to be connected before you add any tags or tag groups.
+ if (!processor.Connect())
+ {
+ Console.WriteLine("Could not connect to the processor");
+ Console.ReadKey(false);
+ return;
+ }
+
+ Console.WriteLine("6D Systems LLC\n\n");
+
+ //First create a group. Groups are much more efficient at reading and writing
+ //large numbers of tags or complex tags like UDTs.
+ LogixTagGroup myGroup = processor.CreateTagGroup("MyGroup");
+
+ //Ok, let's create the first tag which is some random user defined type
+ LogixTag genericTag = LogixTagFactory.CreateTag("myUDT1", processor);
+ LogixUDT udtTag = genericTag as LogixUDT;
+
+ if (udtTag == null)
+ {
+ Console.WriteLine("The tag 'myUDT1' on the processor is not a structure tag");
+ Console.WriteLine("Press any key to quit");
+ Console.ReadKey(false);
+ processor.Disconnect();
+ return;
+ }
+
+ //Let's print out some information about the UDT
+ PrintStructure(udtTag);
+
+ //The value of any member can also be set with the tagName[memberName] = value syntax
+
+ //Now let's get information about the alarm tag that was created...
+ LogixTag genericAlarm = LogixTagFactory.CreateTag("myAlarm1", processor);
+ LogixUDT alarmTag = genericAlarm as LogixUDT;
+
+ if (alarmTag == null)
+ {
+ Console.WriteLine("The tag 'myAlarm1' is not a structure tag");
+ Console.WriteLine("Press any key to quit");
+ Console.ReadKey(false);
+ processor.Disconnect();
+ return;
+ }
+
+ //Print out information about it...
+ PrintStructure(alarmTag);
+
+ //Now, let's set up the tags in the group, set the group to auto update, and watch
+ //for tag update events...
+ myGroup.AddTag(udtTag);
+ myGroup.AddTag(alarmTag);
+
+ udtTag.TagValueUpdated += new ICommon.TagValueUpdateEventHandler(TagValueUpdated);
+ alarmTag.TagValueUpdated += new ICommon.TagValueUpdateEventHandler(TagValueUpdated);
+
+ processor.EnableAutoUpdate(500);
+
+ Console.WriteLine("Press Enter to quit");
+
+ Console.ReadLine();
+
+ processor.Disconnect();
+ }
+
+ static void TagValueUpdated(ICommon.ITag sender, ICommon.TagValueUpdateEventArgs e)
+ {
+ LogixUDT logixUDT = sender as LogixUDT;
+
+ if (logixUDT != null)
+ PrintStructure(logixUDT);
+ }
+
+ private static void PrintStructure(LogixUDT structureTag)
+ {
+ List memberNames = structureTag.MemberNames;
+ Console.WriteLine("myUDT1 is a " + structureTag.TypeName + " with members:");
+ foreach (string mName in memberNames)
+ {
+ LogixTypes memberType = structureTag.GetTypeForMember(mName);
+ Console.WriteLine("\t" + mName + " : " + memberType.ToString() + " - Value: " + structureTag[mName].ToString());
+ }
+ }
+ }
+}
diff --git a/Examples/StructureTags/Properties/AssemblyInfo.cs b/Examples/StructureTags/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..ae4b648
--- /dev/null
+++ b/Examples/StructureTags/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("StructureTags")]
+[assembly: AssemblyDescription("Demonstration of how to use structure tags")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("6D Systems LLC")]
+[assembly: AssemblyProduct("StructureTags")]
+[assembly: AssemblyCopyright("Copyright © 6D Systems LLC 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("315b32c0-075a-46c9-a9f6-cd9e6218d5d6")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Examples/StructureTags/StructureTags.csproj b/Examples/StructureTags/StructureTags.csproj
new file mode 100644
index 0000000..5f67a9b
--- /dev/null
+++ b/Examples/StructureTags/StructureTags.csproj
@@ -0,0 +1,79 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {F9FF55D3-526E-46E7-B703-45E980FABA33}
+ Exe
+ Properties
+ StructureTags
+ StructureTags
+ v4.0
+ 512
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\ControlLogixNET_Locked\ControlLogixNET.dll
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\EIPNET_Locked\EIPNET.dll
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\ICommon_Locked\ICommon.dll
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Examples/StructureTags/StructureTags.csproj.vspscc b/Examples/StructureTags/StructureTags.csproj.vspscc
new file mode 100644
index 0000000..b6d3289
--- /dev/null
+++ b/Examples/StructureTags/StructureTags.csproj.vspscc
@@ -0,0 +1,10 @@
+""
+{
+"FILE_VERSION" = "9237"
+"ENLISTMENT_CHOICE" = "NEVER"
+"PROJECT_FILE_RELATIVE_PATH" = ""
+"NUMBER_OF_EXCLUDED_FILES" = "0"
+"ORIGINAL_PROJECT_FILE_PATH" = ""
+"NUMBER_OF_NESTED_PROJECTS" = "0"
+"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
+}
diff --git a/Examples/TagGroups/Program.cs b/Examples/TagGroups/Program.cs
new file mode 100644
index 0000000..5839c55
--- /dev/null
+++ b/Examples/TagGroups/Program.cs
@@ -0,0 +1,181 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using ControlLogixNET;
+using ControlLogixNET.LogixType;
+
+namespace TagGroups
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ //First we create the processor object. Typically the path is the slot
+ //number of the processor module in the backplane, but if your communications
+ //card is not in the same chassis as your processor, this is the path through
+ //the chassis to get to your processor. You will have to add a 1 for every
+ //chassis you go through, for example:
+ //Chassis 1: ENBT card in Slot 1 (slot is irrelavent), ControlNet Card in Slot 2
+ //Chassis 2: L61 in Slot 4
+ //Path would be: { 2, 1, 4 }
+ //Basically it's the target slot, 1 for backplane, target slot, 1 for backplane...
+ //until you get to the processor.
+ string hostNameOrIp = "192.168.1.10";
+ byte[] path = new byte[] { 1 };
+ LogixProcessor processor = new LogixProcessor(hostNameOrIp, path);
+
+ //Connect to the PLC, you can create the events before or after the connect function
+ if (!processor.Connect())
+ {
+ Console.WriteLine("Could not connect to the processor");
+ Console.ReadKey(false);
+ return;
+ }
+
+ //Tag groups allow you to group tags in a useful manner. For example in an HMI you
+ //could create tag groups for each page. Disabling a tag group that is not in use
+ //frees up resources on the processor and the network.
+
+ //You also have to be careful about the two different kinds of Enabled properties.
+ //There is an enabled property for the tag group, and there is an Enabled property
+ //for the tag itself. Disabling the tag group stops it from being updated, so no
+ //tags belonging to that group will be updated (unless they also belong to another
+ //active tag group). Disabling the tag by setting the LogixTag.Enabled property
+ //to false means that the tag won't accept new data or pending values, and that
+ //any tag group that it belongs to won't update it.
+
+ //First, we need to create a LogixTagGroup on the processor. The easiest way to do
+ //this is to use the LogixProcessor.CreateTagGroup() method. This allows the
+ //processor to create the tag group, verify it doesn't conflict with another tag
+ //group, and manage the group.
+
+ LogixTagGroup tg = processor.CreateTagGroup("MyGroup");
+
+ //Now that we've created a tag group, we can add some tags to it. Adding and removing
+ //tags from a tag group is an expensive process. The tag group will automatically
+ //re-optimize all the tags it's responsible for when you add or remove a tag. It's
+ //recommended that you don't add or remove tags very often, if you don't need a tag
+ //to be updated anymore just set the LogixTag.Enabled property to false.
+
+ //Here we are going to ask the user (probably you) for some tags. The easiest way to
+ //create tags without knowing the underlying data type in the processor is to use
+ //the LogixTagFactory.
+
+ bool quitFlag = false;
+ LogixDINT dTag = new LogixDINT("tst_Dint", processor);
+ dTag.TagValueUpdated += new ICommon.TagValueUpdateEventHandler(TagValueUpdated);
+ tg.AddTag(dTag);
+
+ while (!quitFlag)
+ {
+ Console.Write("Please enter a tag name to monitor, enter 'done' when finished: ");
+
+ string tagName = Console.ReadLine();
+
+ if (tagName.ToLower() == "done")
+ {
+ quitFlag = true;
+ continue;
+ }
+
+ LogixTag userTag = LogixTagFactory.CreateTag(tagName, processor);
+
+ if (userTag == null)
+ {
+ //When the tag factory returns null, the tag was not found or some other
+ //catastrophic error occurred trying to reference it on the processor.
+ Console.WriteLine("The tag " + tagName + " could not be created");
+ continue;
+ }
+
+ //If we got here, we were able to successfully create the tag. Let's print
+ //some information about it...
+ Console.WriteLine("Created " + tagName + " as a(n) " + userTag.LogixType.ToString());
+
+ //Let's reference the update functions...
+ userTag.TagValueUpdated += new ICommon.TagValueUpdateEventHandler(TagValueUpdated);
+
+ //Now let's add it to the tag group...
+ tg.AddTag(userTag);
+ }
+
+ //The processor has a feature that allows them to automatically update the tag group. This
+ //helps to free up your logic and not worry about having to update tag groups that are
+ //enabled or disabled. The argument for this function is the time between updates in
+ //milliseconds. The actual time from the start of one update to the start of another is
+ //dependant on how many tags there are and how much data needs to be transferred.
+ processor.EnableAutoUpdate(500);
+
+ Console.WriteLine("Press Enter to quit");
+ Console.ReadLine();
+
+ processor.Disconnect();
+
+ }
+
+ static void TagValueUpdated(ICommon.ITag sender, ICommon.TagValueUpdateEventArgs e)
+ {
+ //Here we'll just display the name of the tag and that it was updated. If you
+ //want to extract the value you'll have to cast it to the correct type. You
+ //can do this by using a switch as shown below.
+ Console.WriteLine("Tag " + e.Tag.Address + " Updated");
+
+ switch (((LogixTag)sender).LogixType)
+ {
+ case LogixTypes.Bool:
+ Console.WriteLine("New value is: " + ((LogixBOOL)sender).Value.ToString());
+ break;
+ case LogixTypes.Control:
+ //The control tag is a lot more complicated, there is no way currently to know
+ //which member was updated, so all you can do is say it was updated, we'll print
+ //out one of the members though.
+ Console.WriteLine("New value is: " + ((LogixCONTROL)sender).POS.ToString());
+ break;
+ case LogixTypes.Counter:
+ //Same as the counter above, we'll just print out the ACC value
+ Console.WriteLine("New ACC value is: " + ((LogixCOUNTER)sender).ACC.ToString());
+ break;
+ case LogixTypes.DInt:
+ //Print out the value. DINT's are equivalent to int in .NET
+ Console.WriteLine("New DINT value is: " + ((LogixDINT)sender).Value.ToString());
+ break;
+ case LogixTypes.Int:
+ //An INT in a logix processor is more like a short in .NET
+ Console.WriteLine("New INT value is: " + ((LogixINT)sender).Value.ToString());
+ break;
+ case LogixTypes.LInt:
+ //LINT's are equivalent to long in .NET
+ Console.WriteLine("New LINT value is: " + ((LogixLINT)sender).Value.ToString());
+ break;
+ case LogixTypes.Real:
+ //REALs are single precision floats
+ Console.WriteLine("New REAL value is: " + ((LogixREAL)sender).Value.ToString());
+ break;
+ case LogixTypes.SInt:
+ //SINTs are signed bytes
+ Console.WriteLine("New SINT value is: " + ((LogixSINT)sender).Value.ToString());
+ break;
+ case LogixTypes.String:
+ //Strings are just like .NET strings, so notice how we can skip the .StringValue
+ //member, since the .ToString() will automatically be called, which returns the
+ //same value as .StringValue
+ Console.WriteLine("New STRING value is: " + ((LogixSTRING)sender));
+ break;
+ case LogixTypes.Timer:
+ //Timers again are like the CONTROL and COUNTER types
+ Console.WriteLine("New Timer.ACC value is: " + ((LogixTIMER)sender).ACC.ToString());
+ break;
+ case LogixTypes.User_Defined:
+ //The only way to get the value out of a UDT, PDT, or MDT is to define the
+ //structure or know the member name you wish to read. We'll just print that
+ //we know its a UDT, MDT, or PDT that changed.
+ Console.WriteLine("The user defined type has changed");
+ break;
+ default:
+ break;
+ }
+ }
+
+ }
+}
diff --git a/Examples/TagGroups/Properties/AssemblyInfo.cs b/Examples/TagGroups/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..63febca
--- /dev/null
+++ b/Examples/TagGroups/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("TagGroups")]
+[assembly: AssemblyDescription("Demonstration on how to use tag groups")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("6D Systems LLC")]
+[assembly: AssemblyProduct("TagGroups")]
+[assembly: AssemblyCopyright("Copyright © 6D Systems LLC 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("7fe39fcb-ef66-4c18-8ea4-4a52f169994a")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Examples/TagGroups/TagGroups.csproj b/Examples/TagGroups/TagGroups.csproj
new file mode 100644
index 0000000..d68167f
--- /dev/null
+++ b/Examples/TagGroups/TagGroups.csproj
@@ -0,0 +1,79 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {B8B61369-057F-4893-8C52-366BC8C09EBC}
+ Exe
+ Properties
+ TagGroups
+ TagGroups
+ v4.0
+ 512
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\ControlLogixNET_Locked\ControlLogixNET.dll
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\EIPNET_Locked\EIPNET.dll
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\ICommon_Locked\ICommon.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Examples/TagGroups/TagGroups.csproj.vspscc b/Examples/TagGroups/TagGroups.csproj.vspscc
new file mode 100644
index 0000000..b6d3289
--- /dev/null
+++ b/Examples/TagGroups/TagGroups.csproj.vspscc
@@ -0,0 +1,10 @@
+""
+{
+"FILE_VERSION" = "9237"
+"ENLISTMENT_CHOICE" = "NEVER"
+"PROJECT_FILE_RELATIVE_PATH" = ""
+"NUMBER_OF_EXCLUDED_FILES" = "0"
+"ORIGINAL_PROJECT_FILE_PATH" = ""
+"NUMBER_OF_NESTED_PROJECTS" = "0"
+"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
+}
diff --git a/Examples/UserDefinedType/Program.cs b/Examples/UserDefinedType/Program.cs
new file mode 100644
index 0000000..27987d3
--- /dev/null
+++ b/Examples/UserDefinedType/Program.cs
@@ -0,0 +1,151 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using ControlLogixNET;
+using ControlLogixNET.LogixType;
+
+namespace UserDefinedType
+{
+ class Program
+ {
+ /*
+ * HOW TO USE THIS SAMPLE
+ *
+ * 1. First change the hostNameOrIp to the IP address or host name of your PLC
+ * 2. Then change the path to be the path to your PLC, see comments below
+ * 3. Create a new User Defined Type on the processor called CustomUDT
+ * 4. Add the following members to the type:
+ * 1. Enabled : BOOL
+ * 2. UpperLimit : DINT
+ * 3. LowerLimit : DINT
+ * 4. RunningValue : REAL
+ * 5. Over : BOOL
+ * 6. Under : BOOL
+ * 5. Create a new tag of type CustomUDT called myCustomUDT
+ * 6. Run
+ *
+ */
+ static void Main(string[] args)
+ {
+ //First we create the processor object. Typically the path is the slot
+ //number of the processor module in the backplane, but if your communications
+ //card is not in the same chassis as your processor, this is the path through
+ //the chassis to get to your processor. You will have to add a 1 for every
+ //chassis you go through, for example:
+ //Chassis 1: ENBT card in Slot 1 (slot is irrelavent), ControlNet Card in Slot 2
+ //Chassis 2: L61 in Slot 4
+ //Path would be: { 2, 1, 4 }
+ //Basically it's the target slot, 1 for backplane, target slot, 1 for backplane...
+ //until you get to the processor.
+ string hostNameOrIp = "192.168.1.10";
+ byte[] path = new byte[] { 1 };
+ LogixProcessor processor = new LogixProcessor(hostNameOrIp, path);
+
+ //The processor has to be connected before you add any tags or tag groups.
+ if (!processor.Connect())
+ {
+ Console.WriteLine("Could not connect to the processor");
+ Console.ReadKey(false);
+ return;
+ }
+
+ Console.WriteLine("6D Systems LLC\n\n");
+
+ //First create a group. Groups are much more efficient at reading and writing
+ //large numbers of tags or complex tags like UDTs.
+ LogixTagGroup myGroup = processor.CreateTagGroup("MyGroup");
+
+ CustomUDT myCustomUDT = new CustomUDT("myCustomUDT", processor);
+ myCustomUDT.TagValueUpdated += new ICommon.TagValueUpdateEventHandler(TagValueUpdated);
+
+ //Add the tag to the group...
+ myGroup.AddTag(myCustomUDT);
+
+ //Set the group to auto update
+ processor.EnableAutoUpdate(500);
+
+ //Print out some structure information:
+ PrintStructure(myCustomUDT);
+
+ //Now wait for updates...
+ Console.WriteLine("Change some data in the custom type, then hit Enter to quit");
+
+ Console.ReadLine();
+
+ processor.Disconnect();
+ }
+
+ static void TagValueUpdated(ICommon.ITag sender, ICommon.TagValueUpdateEventArgs e)
+ {
+ LogixUDT udtTag = sender as LogixUDT;
+
+ if (udtTag != null)
+ PrintStructure(udtTag);
+ }
+
+ private static void PrintStructure(LogixUDT structureTag)
+ {
+ List memberNames = structureTag.MemberNames;
+ Console.WriteLine("myUDT1 is a " + structureTag.TypeName + " with members:");
+ foreach (string mName in memberNames)
+ {
+ LogixTypes memberType = structureTag.GetTypeForMember(mName);
+ Console.WriteLine("\t" + mName + " : " + memberType.ToString() + " - Value: " + structureTag[mName].ToString());
+ }
+ }
+ }
+
+ ///
+ /// Custom User Defined Type
+ ///
+ ///
+ /// This shows how to create a custom user defined type on the processor.
+ ///
+ public class CustomUDT : LogixUDT
+ {
+ private LogixUDT _myUDTBase;
+
+ public bool Enabled
+ {
+ get { return (bool)this["Enabled"]; }
+ set { this["Enabled"] = value; }
+ }
+
+ public int UpperLimit
+ {
+ get { return (int)this["UpperLimit"]; }
+ set { this["UpperLimit"] = value; }
+ }
+
+ public int LowerLimit
+ {
+ get { return (int)this["LowerLimit"]; }
+ set { this["LowerLimit"] = value; }
+ }
+
+ public float RunningValue
+ {
+ get { return (float)this["RunningValue"]; }
+ set { this["RunningValue"] = value; }
+ }
+
+ public bool Over
+ {
+ get { return (bool)this["Over"]; }
+ set { this["Over"] = value; }
+ }
+
+ public bool Under
+ {
+ get { return (bool)this["Under"]; }
+ set { this["Under"] = value; }
+ }
+
+ public CustomUDT(string TagAddress, LogixProcessor Processor)
+ : base(TagAddress, Processor)
+ {
+
+ }
+ }
+}
diff --git a/Examples/UserDefinedType/Properties/AssemblyInfo.cs b/Examples/UserDefinedType/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..f96f1c7
--- /dev/null
+++ b/Examples/UserDefinedType/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("UserDefinedType")]
+[assembly: AssemblyDescription("How to create interactive User Defined Types")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("6D Systems LLC")]
+[assembly: AssemblyProduct("UserDefinedType")]
+[assembly: AssemblyCopyright("Copyright © 6D Systems LLC 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("f04d1a82-0963-466d-a749-bd6059368097")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Examples/UserDefinedType/UserDefinedType.csproj b/Examples/UserDefinedType/UserDefinedType.csproj
new file mode 100644
index 0000000..58d532e
--- /dev/null
+++ b/Examples/UserDefinedType/UserDefinedType.csproj
@@ -0,0 +1,79 @@
+
+
+
+ Debug
+ x86
+ 8.0.30703
+ 2.0
+ {C11608A6-1EC1-4859-9D42-B4A21FD9D5DD}
+ Exe
+ Properties
+ UserDefinedType
+ UserDefinedType
+ v4.0
+ 512
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+ x86
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ x86
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\ControlLogixNET_Locked\ControlLogixNET.dll
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\EIPNET_Locked\EIPNET.dll
+
+
+ False
+ ..\..\ControlLogixNET\bin\Debug\ICommon_Locked\ICommon.dll
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Examples/UserDefinedType/UserDefinedType.csproj.vspscc b/Examples/UserDefinedType/UserDefinedType.csproj.vspscc
new file mode 100644
index 0000000..b6d3289
--- /dev/null
+++ b/Examples/UserDefinedType/UserDefinedType.csproj.vspscc
@@ -0,0 +1,10 @@
+""
+{
+"FILE_VERSION" = "9237"
+"ENLISTMENT_CHOICE" = "NEVER"
+"PROJECT_FILE_RELATIVE_PATH" = ""
+"NUMBER_OF_EXCLUDED_FILES" = "0"
+"ORIGINAL_PROJECT_FILE_PATH" = ""
+"NUMBER_OF_NESTED_PROJECTS" = "0"
+"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
+}
diff --git a/ICommon/ICommon.csproj b/ICommon/ICommon.csproj
new file mode 100644
index 0000000..775c032
--- /dev/null
+++ b/ICommon/ICommon.csproj
@@ -0,0 +1,67 @@
+
+
+
+ Debug
+ AnyCPU
+ 8.0.30703
+ 2.0
+ {1E726BE8-F9B8-4A9B-B052-E0D9154F3896}
+ Library
+ Properties
+ ICommon
+ ICommon
+ v4.0
+ 512
+ SAK
+ SAK
+ SAK
+ SAK
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/ICommon/ICommon.csproj.vspscc b/ICommon/ICommon.csproj.vspscc
new file mode 100644
index 0000000..b6d3289
--- /dev/null
+++ b/ICommon/ICommon.csproj.vspscc
@@ -0,0 +1,10 @@
+""
+{
+"FILE_VERSION" = "9237"
+"ENLISTMENT_CHOICE" = "NEVER"
+"PROJECT_FILE_RELATIVE_PATH" = ""
+"NUMBER_OF_EXCLUDED_FILES" = "0"
+"ORIGINAL_PROJECT_FILE_PATH" = ""
+"NUMBER_OF_NESTED_PROJECTS" = "0"
+"SOURCE_CONTROL_SETTINGS_PROVIDER" = "PROVIDER"
+}
diff --git a/ICommon/IDevice.cs b/ICommon/IDevice.cs
new file mode 100644
index 0000000..df5338a
--- /dev/null
+++ b/ICommon/IDevice.cs
@@ -0,0 +1,55 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ICommon
+{
+ ///
+ /// Represents a device which has tag data
+ ///
+ public interface IDevice : IDisposable
+ {
+ ///
+ /// Gets the last error code
+ ///
+ int ErrorCode { get; }
+ ///
+ /// Gets the last error string
+ ///
+ string ErrorString { get; }
+ ///
+ /// Gets or Sets an object for use by the user
+ ///
+ object UserData { get; set; }
+ ///
+ /// Gets the version information
+ ///
+ Version Version { get; }
+
+ ///
+ /// Connects to the device
+ ///
+ /// True if connected
+ bool Connect();
+ ///
+ /// Disconnects from the device
+ ///
+ /// True if disconnected
+ bool Disconnect();
+
+ ///
+ /// Reads a particular tag from the PLC
+ ///
+ /// Tag to read
+ /// True if the tag was read successfully
+ bool ReadTag(ITag tag);
+ ///
+ /// Writes a particular tag to the PLC
+ ///
+ /// Tag to write
+ /// True if the tag was written successfully
+ bool WriteTag(ITag tag);
+
+ }
+}
diff --git a/ICommon/ITag.cs b/ICommon/ITag.cs
new file mode 100644
index 0000000..3e8f132
--- /dev/null
+++ b/ICommon/ITag.cs
@@ -0,0 +1,113 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ICommon
+{
+ ///
+ /// Interface for Tag objects
+ ///
+ public interface ITag
+ {
+ ///
+ /// Gets or Sets a value determining if the tag is active on the controller
+ ///
+ bool Enabled { get; set; }
+ ///
+ /// Gets or the device to which this tag belongs
+ ///
+ IDevice Device { get; }
+ ///
+ /// Gets the DataType code for the tag
+ ///
+ ushort DataType { get; }
+ ///
+ /// Gets or Sets an object available to the user for any use
+ ///
+ object UserData { get; set; }
+ ///
+ /// Gets or Sets the address of the tag
+ ///
+ string Address { get; }
+ ///
+ /// Gets the last error associated with the tag
+ ///
+ string LastError { get; }
+ ///
+ /// Gets the last error number associated with the tag
+ ///
+ int LastErrorNumber { get; }
+ ///
+ /// Gets the quality of the tag
+ ///
+ TagQuality Quality { get; }
+ ///
+ /// Gets the timestamp of the last successful operation on the tag
+ ///
+ DateTime TimeStamp { get; }
+
+ ///
+ /// Raised when the value of the tag is updated
+ ///
+ event TagValueUpdateEventHandler TagValueUpdated;
+ ///
+ /// Raised when the quality of the tag has changed
+ ///
+ event TagQualityChangedEventHandler TagQualityChanged;
+ }
+
+ //Support Classes
+
+ ///
+ /// Event delegate for the TagValueUpdateEvent
+ ///
+ /// ITag raising the update
+ /// Event arguments
+ public delegate void TagValueUpdateEventHandler(ITag sender, TagValueUpdateEventArgs e);
+ ///
+ /// Event arguments for
+ ///
+ public class TagValueUpdateEventArgs : EventArgs
+ {
+ ///
+ /// Gets the Tag associated with the Update event
+ ///
+ public ITag Tag { get; internal set; }
+
+ ///
+ /// Creates a new TagValueUpdateEventArgs class
+ ///
+ /// Tag associated with the update
+ public TagValueUpdateEventArgs(ITag tag)
+ {
+ Tag = tag;
+ }
+ }
+
+ ///
+ /// Event delegate for the TagQualityChanged
+ ///
+ /// ITag raising the event
+ /// Event arguments
+ public delegate void TagQualityChangedEventHandler(ITag sender, TagQualityChangedEventArgs e);
+ ///
+ /// Event arguments for
+ ///
+ public class TagQualityChangedEventArgs : EventArgs
+ {
+ ///
+ /// Gets the Tag associated with the Update event
+ ///
+ public ITag Tag { get; internal set; }
+
+ ///
+ /// Creates a new TagValueUpdateEventArgs class
+ ///
+ /// Tag associated with the update
+ public TagQualityChangedEventArgs(ITag tag)
+ {
+ Tag = tag;
+ }
+ }
+}
diff --git a/ICommon/Properties/AssemblyInfo.cs b/ICommon/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000..700de21
--- /dev/null
+++ b/ICommon/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyTitle("ICommon")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("6D Systems")]
+[assembly: AssemblyProduct("ICommon")]
+[assembly: AssemblyCopyright("Copyright © 6D Systems 2011")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("595dbc10-887e-4097-b6d0-f0362684751c")]
+
+// Version information for an assembly consists of the following four values:
+//
+// Major Version
+// Minor Version
+// Build Number
+// Revision
+//
+// You can specify all the values or you can default the Build and Revision Numbers
+// by using the '*' as shown below:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/ICommon/TagQuality.cs b/ICommon/TagQuality.cs
new file mode 100644
index 0000000..a89f7af
--- /dev/null
+++ b/ICommon/TagQuality.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace ICommon
+{
+ ///
+ /// Tag Quality
+ ///
+ public enum TagQuality : byte
+ {
+ ///
+ /// Tag quality is bad
+ ///
+ Bad = 0xFF,
+ ///
+ /// Tag quality is unknown
+ ///
+ Unknown = 0,
+ ///
+ /// Tag quality is good
+ ///
+ Good = 1
+ }
+}
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..c2a7f29
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,8 @@
+
+Copyright (C) 2020 CONTROLAWARE LLC (PRIORLY KNOWN AS INGENIOUS LLC)
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..f025268
--- /dev/null
+++ b/README.md
@@ -0,0 +1 @@
+"# Logix.Net"