From 02ac170aea8e8d1715566020ed90d7f5e6979fa3 Mon Sep 17 00:00:00 2001 From: Timothy Nunnink <46979634+tnunnink@users.noreply.github.com> Date: Fri, 8 Dec 2023 13:13:03 -0600 Subject: [PATCH] Updating NuGet package name back to L5Sharp --- src/L5Sharp.Core/L5Sharp.Core.csproj | 3 +- src/L5Sharp/Common/Address.cs | 175 + src/L5Sharp/Common/Argument.cs | 253 + src/L5Sharp/Common/ComponentKey.cs | 82 + src/L5Sharp/Common/CrossReference.cs | 165 + src/L5Sharp/Common/Dimensions.cs | 343 + src/L5Sharp/Common/Instruction.cs | 1735 +++ src/L5Sharp/Common/NeutralText.cs | 177 + src/L5Sharp/Common/ProductType.cs | 116 + src/L5Sharp/Common/Revision.cs | 165 + src/L5Sharp/Common/ScanRate.cs | 90 + src/L5Sharp/Common/TagMap.cs | 38 + src/L5Sharp/Common/TagName.cs | 465 + src/L5Sharp/Common/TaskPriority.cs | 81 + src/L5Sharp/Common/Vendor.cs | 104 + src/L5Sharp/Common/Watchdog.cs | 90 + src/L5Sharp/Components/AddOnInstruction.cs | 269 + src/L5Sharp/Components/Controller.cs | 328 + src/L5Sharp/Components/DataType.cs | 117 + src/L5Sharp/Components/Module.cs | 438 + src/L5Sharp/Components/Program.cs | 195 + src/L5Sharp/Components/Routine.cs | 118 + src/L5Sharp/Components/Tag.cs | 759 ++ src/L5Sharp/Components/Task.cs | 215 + src/L5Sharp/Components/Trend.cs | 381 + src/L5Sharp/Components/WatchList.cs | 88 + src/L5Sharp/Elements/Block.cs | 1056 ++ src/L5Sharp/Elements/Chart.cs | 53 + src/L5Sharp/Elements/Communications.cs | 51 + src/L5Sharp/Elements/Connection.cs | 197 + src/L5Sharp/Elements/DataTypeMember.cs | 162 + src/L5Sharp/Elements/Diagram.cs | 143 + src/L5Sharp/Elements/DiagramElement.cs | 175 + src/L5Sharp/Elements/Line.cs | 98 + src/L5Sharp/Elements/Parameter.cs | 204 + src/L5Sharp/Elements/ParameterConnection.cs | 54 + src/L5Sharp/Elements/Pen.cs | 153 + src/L5Sharp/Elements/Port.cs | 119 + src/L5Sharp/Elements/RedundancyInfo.cs | 73 + src/L5Sharp/Elements/Rung.cs | 214 + src/L5Sharp/Elements/SafetyInfo.cs | 89 + src/L5Sharp/Elements/Security.cs | 82 + src/L5Sharp/Elements/Sheet.cs | 248 + src/L5Sharp/Elements/TextBox.cs | 77 + src/L5Sharp/Elements/WatchTag.cs | 52 + src/L5Sharp/Elements/Wire.cs | 115 + src/L5Sharp/Enums/CaptureSizeType.cs | 26 + src/L5Sharp/Enums/ComponentType.cs | 64 + src/L5Sharp/Enums/ConnectionPriority.cs | 31 + src/L5Sharp/Enums/ConnectionType.cs | 74 + src/L5Sharp/Enums/DataFormat.cs | 44 + src/L5Sharp/Enums/DataTypeClass.cs | 43 + src/L5Sharp/Enums/DataTypeFamily.cs | 24 + src/L5Sharp/Enums/DiagramType.cs | 49 + src/L5Sharp/Enums/Direction.cs | 23 + src/L5Sharp/Enums/ElectronicKeying.cs | 31 + src/L5Sharp/Enums/ExternalAccess.cs | 29 + src/L5Sharp/Enums/Keyword.cs | 124 + src/L5Sharp/Enums/ModuleCategory.cs | 47 + src/L5Sharp/Enums/OnlineEditType.cs | 26 + src/L5Sharp/Enums/Operator.cs | 96 + src/L5Sharp/Enums/PassThroughOption.cs | 27 + src/L5Sharp/Enums/PenType.cs | 26 + src/L5Sharp/Enums/ProductionTrigger.cs | 26 + src/L5Sharp/Enums/ProgramType.cs | 21 + src/L5Sharp/Enums/Radix.cs | 754 ++ src/L5Sharp/Enums/RoutineType.cs | 46 + src/L5Sharp/Enums/RungType.cs | 66 + src/L5Sharp/Enums/SFCExecutionControl.cs | 21 + src/L5Sharp/Enums/SFCLastScan.cs | 26 + src/L5Sharp/Enums/SFCRestartPosition.cs | 21 + src/L5Sharp/Enums/SamplesType.cs | 21 + src/L5Sharp/Enums/Scope.cs | 100 + src/L5Sharp/Enums/SheetOrientation.cs | 21 + src/L5Sharp/Enums/SheetSize.cs | 76 + src/L5Sharp/Enums/TagType.cs | 31 + src/L5Sharp/Enums/TagUsage.cs | 62 + src/L5Sharp/Enums/TaskEventTrigger.cs | 61 + src/L5Sharp/Enums/TaskType.cs | 27 + src/L5Sharp/Enums/TokenType.cs | 37 + src/L5Sharp/Enums/TransmissionType.cs | 26 + src/L5Sharp/Enums/TriggerOperation.cs | 102 + src/L5Sharp/Enums/TriggerTargetType.cs | 21 + src/L5Sharp/Enums/TriggerType.cs | 21 + src/L5Sharp/Enums/Use.cs | 66 + src/L5Sharp/ILogixReferencable.cs | 20 + src/L5Sharp/ILogixSerializable.cs | 15 + src/L5Sharp/L5Sharp.csproj | 42 + src/L5Sharp/L5Sharp.csproj.DotSettings | 10 + src/L5Sharp/L5X.cs | 1245 +++ src/L5Sharp/L5XInfo.cs | 91 + src/L5Sharp/Logix.cs | 14 + src/L5Sharp/LogixCode.cs | 78 + src/L5Sharp/LogixComponent.cs | 180 + src/L5Sharp/LogixContainer.cs | 359 + src/L5Sharp/LogixData.cs | 279 + src/L5Sharp/LogixElement.cs | 767 ++ src/L5Sharp/LogixEnum.cs | 336 + src/L5Sharp/LogixIndex.cs | 385 + src/L5Sharp/LogixMember.cs | 295 + src/L5Sharp/LogixSerializer.cs | 140 + src/L5Sharp/LogixType.cs | 363 + src/L5Sharp/Types/ArrayType.cs | 405 + src/L5Sharp/Types/Atomic.cs | 101 + src/L5Sharp/Types/AtomicType.cs | 95 + src/L5Sharp/Types/Atomics/BOOL.cs | 305 + src/L5Sharp/Types/Atomics/DINT.cs | 305 + src/L5Sharp/Types/Atomics/INT.cs | 390 + src/L5Sharp/Types/Atomics/LINT.cs | 304 + src/L5Sharp/Types/Atomics/LREAL.cs | 275 + src/L5Sharp/Types/Atomics/REAL.cs | 275 + src/L5Sharp/Types/Atomics/SINT.cs | 299 + src/L5Sharp/Types/Atomics/UDINT.cs | 303 + src/L5Sharp/Types/Atomics/UINT.cs | 299 + src/L5Sharp/Types/Atomics/ULINT.cs | 299 + src/L5Sharp/Types/Atomics/USINT.cs | 299 + src/L5Sharp/Types/ComplexType.cs | 119 + src/L5Sharp/Types/NullType.cs | 40 + src/L5Sharp/Types/Predefined/ALARM.cs | 266 + src/L5Sharp/Types/Predefined/ALARM_ANALOG.cs | 689 ++ src/L5Sharp/Types/Predefined/ALARM_DIGITAL.cs | 319 + src/L5Sharp/Types/Predefined/CONTROL.cs | 126 + src/L5Sharp/Types/Predefined/COUNTER.cs | 96 + src/L5Sharp/Types/Predefined/MESSAGE.cs | 184 + src/L5Sharp/Types/Predefined/PHASE.cs | 544 + src/L5Sharp/Types/Predefined/PID.cs | 589 ++ src/L5Sharp/Types/Predefined/STRING.cs | 54 + src/L5Sharp/Types/Predefined/TIMER.cs | 93 + src/L5Sharp/Types/StringType.cs | 306 + src/L5Sharp/Types/StructureType.cs | 361 + src/L5Sharp/Utilities/Catalog/CatalogEntry.cs | 56 + .../Utilities/Catalog/ModuleCatalog.cs | 193 + src/L5Sharp/Utilities/Catalog/PortInfo.cs | 29 + src/L5Sharp/Utilities/L5XExtensions.cs | 236 + src/L5Sharp/Utilities/L5XName.cs | 9378 +++++++++++++++++ src/L5Sharp/Utilities/L5XParser.cs | 124 + src/L5Sharp/Utilities/L5XTypeAttribute.cs | 52 + 137 files changed, 35268 insertions(+), 1 deletion(-) create mode 100644 src/L5Sharp/Common/Address.cs create mode 100644 src/L5Sharp/Common/Argument.cs create mode 100644 src/L5Sharp/Common/ComponentKey.cs create mode 100644 src/L5Sharp/Common/CrossReference.cs create mode 100644 src/L5Sharp/Common/Dimensions.cs create mode 100644 src/L5Sharp/Common/Instruction.cs create mode 100644 src/L5Sharp/Common/NeutralText.cs create mode 100644 src/L5Sharp/Common/ProductType.cs create mode 100644 src/L5Sharp/Common/Revision.cs create mode 100644 src/L5Sharp/Common/ScanRate.cs create mode 100644 src/L5Sharp/Common/TagMap.cs create mode 100644 src/L5Sharp/Common/TagName.cs create mode 100644 src/L5Sharp/Common/TaskPriority.cs create mode 100644 src/L5Sharp/Common/Vendor.cs create mode 100644 src/L5Sharp/Common/Watchdog.cs create mode 100644 src/L5Sharp/Components/AddOnInstruction.cs create mode 100644 src/L5Sharp/Components/Controller.cs create mode 100644 src/L5Sharp/Components/DataType.cs create mode 100644 src/L5Sharp/Components/Module.cs create mode 100644 src/L5Sharp/Components/Program.cs create mode 100644 src/L5Sharp/Components/Routine.cs create mode 100644 src/L5Sharp/Components/Tag.cs create mode 100644 src/L5Sharp/Components/Task.cs create mode 100644 src/L5Sharp/Components/Trend.cs create mode 100644 src/L5Sharp/Components/WatchList.cs create mode 100644 src/L5Sharp/Elements/Block.cs create mode 100644 src/L5Sharp/Elements/Chart.cs create mode 100644 src/L5Sharp/Elements/Communications.cs create mode 100644 src/L5Sharp/Elements/Connection.cs create mode 100644 src/L5Sharp/Elements/DataTypeMember.cs create mode 100644 src/L5Sharp/Elements/Diagram.cs create mode 100644 src/L5Sharp/Elements/DiagramElement.cs create mode 100644 src/L5Sharp/Elements/Line.cs create mode 100644 src/L5Sharp/Elements/Parameter.cs create mode 100644 src/L5Sharp/Elements/ParameterConnection.cs create mode 100644 src/L5Sharp/Elements/Pen.cs create mode 100644 src/L5Sharp/Elements/Port.cs create mode 100644 src/L5Sharp/Elements/RedundancyInfo.cs create mode 100644 src/L5Sharp/Elements/Rung.cs create mode 100644 src/L5Sharp/Elements/SafetyInfo.cs create mode 100644 src/L5Sharp/Elements/Security.cs create mode 100644 src/L5Sharp/Elements/Sheet.cs create mode 100644 src/L5Sharp/Elements/TextBox.cs create mode 100644 src/L5Sharp/Elements/WatchTag.cs create mode 100644 src/L5Sharp/Elements/Wire.cs create mode 100644 src/L5Sharp/Enums/CaptureSizeType.cs create mode 100644 src/L5Sharp/Enums/ComponentType.cs create mode 100644 src/L5Sharp/Enums/ConnectionPriority.cs create mode 100644 src/L5Sharp/Enums/ConnectionType.cs create mode 100644 src/L5Sharp/Enums/DataFormat.cs create mode 100644 src/L5Sharp/Enums/DataTypeClass.cs create mode 100644 src/L5Sharp/Enums/DataTypeFamily.cs create mode 100644 src/L5Sharp/Enums/DiagramType.cs create mode 100644 src/L5Sharp/Enums/Direction.cs create mode 100644 src/L5Sharp/Enums/ElectronicKeying.cs create mode 100644 src/L5Sharp/Enums/ExternalAccess.cs create mode 100644 src/L5Sharp/Enums/Keyword.cs create mode 100644 src/L5Sharp/Enums/ModuleCategory.cs create mode 100644 src/L5Sharp/Enums/OnlineEditType.cs create mode 100644 src/L5Sharp/Enums/Operator.cs create mode 100644 src/L5Sharp/Enums/PassThroughOption.cs create mode 100644 src/L5Sharp/Enums/PenType.cs create mode 100644 src/L5Sharp/Enums/ProductionTrigger.cs create mode 100644 src/L5Sharp/Enums/ProgramType.cs create mode 100644 src/L5Sharp/Enums/Radix.cs create mode 100644 src/L5Sharp/Enums/RoutineType.cs create mode 100644 src/L5Sharp/Enums/RungType.cs create mode 100644 src/L5Sharp/Enums/SFCExecutionControl.cs create mode 100644 src/L5Sharp/Enums/SFCLastScan.cs create mode 100644 src/L5Sharp/Enums/SFCRestartPosition.cs create mode 100644 src/L5Sharp/Enums/SamplesType.cs create mode 100644 src/L5Sharp/Enums/Scope.cs create mode 100644 src/L5Sharp/Enums/SheetOrientation.cs create mode 100644 src/L5Sharp/Enums/SheetSize.cs create mode 100644 src/L5Sharp/Enums/TagType.cs create mode 100644 src/L5Sharp/Enums/TagUsage.cs create mode 100644 src/L5Sharp/Enums/TaskEventTrigger.cs create mode 100644 src/L5Sharp/Enums/TaskType.cs create mode 100644 src/L5Sharp/Enums/TokenType.cs create mode 100644 src/L5Sharp/Enums/TransmissionType.cs create mode 100644 src/L5Sharp/Enums/TriggerOperation.cs create mode 100644 src/L5Sharp/Enums/TriggerTargetType.cs create mode 100644 src/L5Sharp/Enums/TriggerType.cs create mode 100644 src/L5Sharp/Enums/Use.cs create mode 100644 src/L5Sharp/ILogixReferencable.cs create mode 100644 src/L5Sharp/ILogixSerializable.cs create mode 100644 src/L5Sharp/L5Sharp.csproj create mode 100644 src/L5Sharp/L5Sharp.csproj.DotSettings create mode 100644 src/L5Sharp/L5X.cs create mode 100644 src/L5Sharp/L5XInfo.cs create mode 100644 src/L5Sharp/Logix.cs create mode 100644 src/L5Sharp/LogixCode.cs create mode 100644 src/L5Sharp/LogixComponent.cs create mode 100644 src/L5Sharp/LogixContainer.cs create mode 100644 src/L5Sharp/LogixData.cs create mode 100644 src/L5Sharp/LogixElement.cs create mode 100644 src/L5Sharp/LogixEnum.cs create mode 100644 src/L5Sharp/LogixIndex.cs create mode 100644 src/L5Sharp/LogixMember.cs create mode 100644 src/L5Sharp/LogixSerializer.cs create mode 100644 src/L5Sharp/LogixType.cs create mode 100644 src/L5Sharp/Types/ArrayType.cs create mode 100644 src/L5Sharp/Types/Atomic.cs create mode 100644 src/L5Sharp/Types/AtomicType.cs create mode 100644 src/L5Sharp/Types/Atomics/BOOL.cs create mode 100644 src/L5Sharp/Types/Atomics/DINT.cs create mode 100644 src/L5Sharp/Types/Atomics/INT.cs create mode 100644 src/L5Sharp/Types/Atomics/LINT.cs create mode 100644 src/L5Sharp/Types/Atomics/LREAL.cs create mode 100644 src/L5Sharp/Types/Atomics/REAL.cs create mode 100644 src/L5Sharp/Types/Atomics/SINT.cs create mode 100644 src/L5Sharp/Types/Atomics/UDINT.cs create mode 100644 src/L5Sharp/Types/Atomics/UINT.cs create mode 100644 src/L5Sharp/Types/Atomics/ULINT.cs create mode 100644 src/L5Sharp/Types/Atomics/USINT.cs create mode 100644 src/L5Sharp/Types/ComplexType.cs create mode 100644 src/L5Sharp/Types/NullType.cs create mode 100644 src/L5Sharp/Types/Predefined/ALARM.cs create mode 100644 src/L5Sharp/Types/Predefined/ALARM_ANALOG.cs create mode 100644 src/L5Sharp/Types/Predefined/ALARM_DIGITAL.cs create mode 100644 src/L5Sharp/Types/Predefined/CONTROL.cs create mode 100644 src/L5Sharp/Types/Predefined/COUNTER.cs create mode 100644 src/L5Sharp/Types/Predefined/MESSAGE.cs create mode 100644 src/L5Sharp/Types/Predefined/PHASE.cs create mode 100644 src/L5Sharp/Types/Predefined/PID.cs create mode 100644 src/L5Sharp/Types/Predefined/STRING.cs create mode 100644 src/L5Sharp/Types/Predefined/TIMER.cs create mode 100644 src/L5Sharp/Types/StringType.cs create mode 100644 src/L5Sharp/Types/StructureType.cs create mode 100644 src/L5Sharp/Utilities/Catalog/CatalogEntry.cs create mode 100644 src/L5Sharp/Utilities/Catalog/ModuleCatalog.cs create mode 100644 src/L5Sharp/Utilities/Catalog/PortInfo.cs create mode 100644 src/L5Sharp/Utilities/L5XExtensions.cs create mode 100644 src/L5Sharp/Utilities/L5XName.cs create mode 100644 src/L5Sharp/Utilities/L5XParser.cs create mode 100644 src/L5Sharp/Utilities/L5XTypeAttribute.cs diff --git a/src/L5Sharp.Core/L5Sharp.Core.csproj b/src/L5Sharp.Core/L5Sharp.Core.csproj index 538c47b8..cbe35062 100644 --- a/src/L5Sharp.Core/L5Sharp.Core.csproj +++ b/src/L5Sharp.Core/L5Sharp.Core.csproj @@ -18,9 +18,10 @@ MIT git - Initial release. + Merged library into single namespace L5Sharp.Core Copyright (c) Timothy Nunnink 2022 README.md + L5Sharp diff --git a/src/L5Sharp/Common/Address.cs b/src/L5Sharp/Common/Address.cs new file mode 100644 index 00000000..1bfa1a10 --- /dev/null +++ b/src/L5Sharp/Common/Address.cs @@ -0,0 +1,175 @@ +using System; +using System.Linq; +using System.Net; +using System.Text.RegularExpressions; + +namespace L5Sharp.Core; + +/// +/// Provides a wrapper around the string port address value to indicate what type of address the value is. +/// +public class Address +{ + private readonly string _value; + + /// + /// Creates a new with the provided string value. + /// + /// + /// + public Address(string address) + { + _value = address ?? throw new ArgumentNullException(nameof(address)); + } + + /// + /// Indicates whether the current is a slot number address. + /// + /// true if the address value is a valid byte address; otherwise, false. + public bool IsSlot => byte.TryParse(_value, out _); + + /// + /// Indicates whether the current is a IPv4 address. + /// + /// true if the address value is a valid IPv4 address; otherwise, false. + public bool IsIPv4 => _value.Count(c => c == '.') == 3 && IPAddress.TryParse(_value, out _); + + /// + /// Indicates whether the current is a host name address. + /// + /// true if the address value is a valid host name address; otherwise, false. + /// + /// A host name must start with a letter, contain only alpha-numeric characters or special characters '.' and '-', + /// and have a maximum length of 64 characters. + /// + public bool IsHostName => Regex.IsMatch(_value, "^[A-Za-z][A-Za-z0-9.-]{1,63}$"); + + /// + /// Indicates that the address value is an empty string. + /// + public bool IsEmpty => _value.IsEmpty(); + + /// + /// Represents no address value, or an empty string. + /// + public static Address None => new(string.Empty); + + /// + /// Converts the current to a value. + /// + /// The value to convert. + /// A representing the address value. + public static implicit operator Address(string address) => new(address); + + /// + /// Converts the current to a value. + /// + /// The value to convert. + /// A representing the address value. + public static implicit operator string(Address address) => address.ToString(); + + /// + /// Converts the current to a value. + /// + /// The value to convert. + /// A representing the address value. + public static implicit operator Address(byte address) => new(address.ToString()); + + /// + /// Converts the current to a value. + /// + /// The value to convert. + /// A representing the address value. + public static implicit operator byte(Address address) => byte.Parse(address._value); + + /// + /// Creates a new instance from the provided object. + /// + /// The value that represents the port address. + /// A new value from the provided IP. + /// ipAddress is null. + public static Address FromIP(IPAddress ipAddress) + { + if (ipAddress is null) throw new ArgumentNullException(nameof(ipAddress)); + return new Address(ipAddress.ToString()); + } + + /// + /// Creates a new instance from the provided byte slot number value. + /// + /// the byte number value that represents the port address. + /// A new value from the provided slot number. + public static Address FromSlot(byte slot) => new(slot.ToString()); + + /// + /// Creates a new with the common default IP of 192.168.0.1. + /// + /// A with the default IP value. + public static Address DefaultIP() => new("192.168.0.1"); + + /// + /// Creates a new with the default slot 0. + /// + /// A with the default slot value. + public static Address DefaultSlot() => new("0"); + + /// + /// Creates a new with the specified slot number. + /// + /// The slot number to create. + /// + public static Address Slot(byte slot) => new($"{slot}"); + + /// + /// + /// + /// + /// + public IPAddress ToIPAddress() => IsIPv4 + ? IPAddress.Parse(_value) + : throw new InvalidOperationException($"The current address '{_value}' is not a valid IP address"); + + /// + /// + /// + /// + /// + public byte ToSlot() => IsSlot + ? byte.Parse(_value) + : throw new InvalidOperationException($"The current address '{_value}' is not a valid slot number"); + + /// + public override string ToString() => _value; + + /// + public override bool Equals(object? obj) + { + return obj switch + { + Address other => string.Equals(_value, other._value, StringComparison.OrdinalIgnoreCase), + string other => string.Equals(_value, other, StringComparison.OrdinalIgnoreCase), + byte other => string.Equals(_value, other.ToString(), StringComparison.OrdinalIgnoreCase), + IPAddress other => string.Equals(_value, other.ToString(), StringComparison.OrdinalIgnoreCase), + _ => false + }; + } + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Determines if the provided objects are equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are equal; otherwise, false. + public static bool operator ==(Address? left, Address? right) => Equals(left, right); + + /// + /// Determines if the provided objects are not equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are not equal; otherwise, false. + public static bool operator !=(Address? left, Address? right) => !Equals(left, right); +} \ No newline at end of file diff --git a/src/L5Sharp/Common/Argument.cs b/src/L5Sharp/Common/Argument.cs new file mode 100644 index 00000000..4194bd2c --- /dev/null +++ b/src/L5Sharp/Common/Argument.cs @@ -0,0 +1,253 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace L5Sharp.Core; + +/// +/// Represents an argument to an instruction, which could be a tag name reference or an immediate atomic value. +/// +public class Argument +{ + private readonly object _value; + + /// + /// Creates a new wrapping the object value. + /// + /// An object representing the argument. + /// value is null. + private Argument(object value) + { + _value = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// Indicates whether the argument is an immediate atomic value. + /// + /// true if the underlying value is an object; Otherwise, false. + public bool IsAtomic => _value is AtomicType; + + /// + /// Indicates whether the argument is an expression or combination of tag names, operators, and/or immediate values. + /// + /// true if the underlying value is a instance wrapping a complex expression; + /// Otherwise, false. + public bool IsExpression => _value is NeutralText; + + /// + /// Indicates whether the argument is an immediate value, either string literal or atomic value. + /// + /// true if the underlying value is an or object; + /// Otherwise, false. + public bool IsImmediate => _value is AtomicType or string; + + /// + /// Indicates whether the argument is an tag name reference. + /// + /// true if the underlying value is a object; Otherwise, false. + public bool IsTag => _value is TagName; + + /// + /// Indicates whether the argument is a literal string value with the single quote identifiers. + /// + /// true if the underlying value is an object; Otherwise, false. + public bool IsString => _value is string; + + /// + /// The collection of values found in the argument. + /// + /// A of values. + /// + /// Since an argument could represent a complex expression, it may contain more than one tag name value. + /// We need a way to get all tag names from a single argument whether it's a single tag name or expression or + /// multiple tag names. + /// + public IEnumerable Tags => _value switch + { + TagName tagName => new[] { tagName }, + NeutralText text => text.Tags(), + _ => Enumerable.Empty() + }; + + /// + /// Represents an unknown argument that can be found in certain instruction text. + /// + /// A representing an unknown parameter. + /// This is literally the '?' character, as often seen in the TIMER instruction arguments. + public static Argument Unknown => new("?"); + + /// + /// Represents an empty argument. + /// + /// A wrapping an empty string objet value. + /// + /// Some instruction have an empty/optional argument(s) (GSV) and therefore we need a way to represent + /// that value. + /// + public static Argument Empty => new(string.Empty); + + /// + /// Parses the string input into a valid object. + /// + /// Teh string value to parse. + /// A representing the string input. + /// value is null or empty + /// This parse method expects the string to be an immediate value, + /// a single value, or an expression that can be represented as + /// type. + /// + public static Argument Parse(string? value) + { + //Empty value - lets not crash on empty or invalid arguments. + if (string.IsNullOrEmpty(value)) return Empty; + + //Unknown value - Can be found in TON instructions and probably others. + if (value == "?") return Unknown; + + //Literal string value - We need to intercept this before the Atomic.TryParse method to prevent exceptions. + if (value.StartsWith('\'') && value.EndsWith('\'')) return new Argument(value); + + //Immediate atomic value + if (Atomic.TryParse(value, out var atomic) && atomic is not null) return new Argument(atomic); + + //TagName or Expression otherwise + return TagName.IsTag(value) ? new Argument(new TagName(value)) : new Argument(new NeutralText(value)); + } + + /// + /// Implicitly converts the provided to an . + /// + /// The object to convert. + /// A object containing the value of the tag name. + public static implicit operator Argument(TagName tagName) => new(tagName); + + /// + /// Implicitly converts the provided to an . + /// + /// The object to convert. + /// A object containing the value of the tag name. + public static implicit operator Argument(string value) => Parse(value); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(bool value) => new(new BOOL(value)); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(sbyte value) => new(new SINT(value)); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(byte value) => new(new USINT(value)); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(short value) => new(new INT(value)); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(ushort value) => new(new UINT(value)); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(int value) => new(new DINT(value)); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(uint value) => new(new UDINT(value)); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(long value) => new(new LINT(value)); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(ulong value) => new(new ULINT(value)); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(float value) => new(new REAL(value)); + + /// + /// Implicitly converts the provided value to an . + /// + /// The object value to convert. + /// An containing the value of the provided object. + public static implicit operator Argument(double value) => new(new LREAL(value)); + + /// + /// Explicitly converts the provided to a . + /// + /// The object to convert. + /// A object representing the value of the argument. + public static explicit operator TagName(Argument argument) => (TagName)argument._value; + + /// + /// Explicitly converts the provided to an . + /// + /// The object to convert. + /// A object representing the value of the argument. + public static explicit operator AtomicType(Argument argument) => (AtomicType)argument._value; + + /// + /// Explicitly converts the provided to an . + /// + /// The object to convert. + /// A object representing the value of the argument. + public static explicit operator NeutralText(Argument argument) => (NeutralText)argument._value; + + /// + public override bool Equals(object? obj) => _value.Equals(obj); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + public override string ToString() => _value.ToString(); + + /// + /// Determines whether two Argument objects are equal. + /// + /// The left Argument object. + /// The right Argument object. + /// Returns true if the two objects are equal, otherwise false. + public static bool operator ==(Argument left, Argument right) => Equals(left, right); + + /// + /// Defines the inequality operator for the Argument class. + /// + /// The left Argument object. + /// The right Argument object. + /// true if the left Argument is not equal to the right Argument; otherwise, false. + public static bool operator !=(Argument left, Argument right) => Equals(left, right); +} \ No newline at end of file diff --git a/src/L5Sharp/Common/ComponentKey.cs b/src/L5Sharp/Common/ComponentKey.cs new file mode 100644 index 00000000..b66729f8 --- /dev/null +++ b/src/L5Sharp/Common/ComponentKey.cs @@ -0,0 +1,82 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// A composite key for a logix component that can be used to uniquely identify a component in the L5X file. +/// +/// +/// This is an library construct that is primarily used for indexing and quick lookup of components +/// and their references within a give L5X file. Since components of different types can have the same name, +/// we consider both the component type and name to be the unique identifier. +/// +public readonly struct ComponentKey : IEquatable +{ + private readonly string _type; + private readonly string _name; + + /// + /// Creates a new value type with the provided parameters. + /// + /// The logix type the component represents (DataType, Tag, etc.) + /// The base name of the component. + public ComponentKey(string type, string name) + { + _type = type ?? throw new ArgumentNullException(nameof(type)); + _name = name ?? throw new ArgumentNullException(nameof(name)); + } + + /// + /// Determines if this key has the specified component name. + /// + /// The name of the component to test. + /// true if the component key has the provided name; Otherwise, false. + public bool HasName(string name) => string.Equals(_name, name, StringComparison.OrdinalIgnoreCase); + + /// + /// Determines if this key is of the specified component type name. + /// + /// The component type to check. + /// true if the component key is of the provided type name; Otherwise, false. + public bool IsType(string type) => _type.IsEquivalent(type); + + /// + /// Determines if this key is of the specified component type parameter. + /// + /// The component type to check + /// true if the component key is of the provided type parameter; Otherwise, false. + public bool IsType() where TComponent : LogixComponent => + _type.IsEquivalent(typeof(TComponent).L5XType()); + + /// + public bool Equals(ComponentKey other) => + StringComparer.OrdinalIgnoreCase.Equals(_type, other._type) && + StringComparer.OrdinalIgnoreCase.Equals(_name, other._name); + + /// + public override bool Equals(object? obj) => obj is ComponentKey other && Equals(other); + + /// + public override int GetHashCode() => + StringComparer.OrdinalIgnoreCase.GetHashCode(_type) ^ + StringComparer.OrdinalIgnoreCase.GetHashCode(_name); + + /// + /// Determines if two objects are equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if the objects have the same type and name property; Otherwise, false. + public static bool operator ==(ComponentKey left, ComponentKey right) => Equals(left, right); + + /// + /// Determines if two objects are not equal. + /// + /// The first object to compare. + /// The second object to compare. + /// true if the objects have the same type and name property; Otherwise, false. + public static bool operator !=(ComponentKey left, ComponentKey right) => !Equals(left, right); + + /// + public override string ToString() => $"[{_type}]{_name}"; +} \ No newline at end of file diff --git a/src/L5Sharp/Common/CrossReference.cs b/src/L5Sharp/Common/CrossReference.cs new file mode 100644 index 00000000..ebb7fec4 --- /dev/null +++ b/src/L5Sharp/Common/CrossReference.cs @@ -0,0 +1,165 @@ +using System; +using System.Linq; +using System.Xml.Linq; +using JetBrains.Annotations; + +namespace L5Sharp.Core; + +/// +/// Represents a reference to a component within a Logix project. This could be a code reference or a reference +/// from another component. This class is meant to provide a uniform set of information for all types of references, +/// however, code references have some additional information that is useful for further identifying the target or +/// location of the reference. +/// +[PublicAPI] +public class CrossReference +{ + private readonly XElement _element; + + /// + /// Creates a new with a referencing element, component name and type, and optional instruction data. + /// + /// The referencing object. + /// The type of the component that is being referenced. + /// The name of the component that is being referenced. + /// The optional instruction name/key for the reference. + /// This is intended for code references as opposed to component references. Will be null if not applicable. + /// + /// element, type, or name is null. + public CrossReference(XElement element, string type, string reference, string? instruction = null, + string? operand = null) + { + _element = element ?? throw new ArgumentNullException(nameof(element)); + + if (string.IsNullOrEmpty(type)) + throw new ArgumentException("Type cannot be null or empty.", nameof(type)); + if (string.IsNullOrEmpty(reference)) + throw new ArgumentException("Name cannot be null or empty.", nameof(reference)); + + Type = type; + Reference = reference; + Instruction = instruction; + Operand = operand; + } + + /// + /// The corresponding of the reference, indicating both the component type and name + /// this element is in reference to. + /// + public ComponentKey Key => new(Type, Reference); + + /// + /// The type of the component the element references. + /// + /// A indicating the type of the component. + public string Type { get; } + + /// + /// The name of the component or element that is referenced. + /// + /// A indicating the name of the component. + public string Reference { get; } + + /// + /// The that is contains the reference to the component. + /// + /// The object that contains the component reference. This may be another + /// Component, a Code instance, or even a single DiagramElement object. + public LogixElement Element => _element.Deserialize(); + + /// + /// The type of the LogixElement that contains the reference to the component. + /// + /// A representing the name of the element type. + /// This helps further identify the reference element relative to other references. + public string ElementType => _element.Name.LocalName; + + /// + /// A unique identifier of the LogixElement that contains the reference to the component. + /// + /// A containing the name or number identifying the reference element. + /// + /// This will ultimately be either the name of the referencing component (for Tag references), + /// the number of the referencing rung or line of logic (for RLL and ST code), or the ID of the referencing + /// diagram block (for FBD/SFC code). This helps further identify the reference element relative to other references. + /// + public string ElementId => _element.Attribute(L5XName.ID) is not null ? _element.Attribute(L5XName.ID)!.Value + : _element.Attribute(L5XName.Number) is not null ? _element.Attribute(L5XName.Number)!.Value + : _element.Attribute(L5XName.Name) is not null ? _element.Attribute(L5XName.Name)!.Value + : string.Empty; + + /// + /// The name of the Task that the reference is contained within if applicable. + /// + /// A representing the containing task if found; Otherwise, an empty string. + /// + /// This could potentially be helpful for analyzing references to tags that are used across multiple + /// Task components. + /// + public string Task => Scope.Task(_element); + + /// + /// The type that the reference is contained within. + /// + /// A indicating scope of the reference. + public Scope Scope => Scope.Type(_element); + + /// + /// The name of the scoped program, instruction, or controller that the reference is contained within. + /// + /// + /// A representing the name of program, controller, or instruction the reference + /// is contained within. + /// + public string Container => Scope.Container(_element); + + /// + /// The name of the Routine that the reference is contained within, it is a + /// type element. + /// + /// A representing the containing routine if found; Otherwise, an empty string. + public string Routine => _element.Ancestors(L5XName.Routine).FirstOrDefault()?.LogixName() ?? string.Empty; + + /// + /// The instruction object containing the reference to the component if this reference is a logic or code reference. + /// + /// If the reference is a code reference, then the object the reference + /// was found; Otherwise, null. + /// + /// The helps further identify where in the logix element the reference is located. Having the associated + /// instruction can help for searching or filtering references and finding other references sharing the common + /// instruction. + /// + public string? Instruction { get; } + + /// + /// + /// + public string? Operand { get; } + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) + return true; + + if (obj is not CrossReference other) + return false; + + return Key == other.Key && + Scope == other.Scope && + Container.IsEquivalent(other.Container) && + Routine.IsEquivalent(other.Routine) && + ElementId.IsEquivalent(other.ElementId) && + ElementType.IsEquivalent(other.ElementType); + } + + /// + public override int GetHashCode() + { + return HashCode.Combine(Key, Scope, Container, Routine, ElementId, ElementType); + } + + /// + public override string ToString() => Key.ToString(); +} \ No newline at end of file diff --git a/src/L5Sharp/Common/Dimensions.cs b/src/L5Sharp/Common/Dimensions.cs new file mode 100644 index 00000000..41253bca --- /dev/null +++ b/src/L5Sharp/Common/Dimensions.cs @@ -0,0 +1,343 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace L5Sharp.Core; + +/// +/// Represents the dimensions of an array for a Logix type. +/// +/// +/// Logix Dimensions can have one, two, or three dimensions. +/// These dimensions are represented by the properties X, Y, and Z. +/// This class also provides helpful methods and properties for working with dimensions of an array. +/// +public sealed class Dimensions : IEquatable +{ + private Dimensions() + { + } + + /// + /// Creates a one dimensional instance with the provided values. + /// + /// The length of the first dimensional element + public Dimensions(ushort x) + { + X = x; + } + + /// + /// Creates a two dimensional instance with the provided values. + /// + /// The length of the first dimensional element + /// The length of the second dimensional element + /// + /// Thrown in the value of x is 0 and y is greater than 0. + /// + public Dimensions(ushort x, ushort y) : this(x) + { + if (x == 0 && y > 0) + throw new ArgumentException("X must be greater than zero to have two dimensions"); + + Y = y; + } + + + /// + /// Creates a three dimensional instance with the provided values. + /// + /// The length of the first dimensional element + /// The length of the second dimensional element + /// The length of the third dimensional element + /// + /// Thrown in the value of y is 0 and z is greater than 0. + /// + public Dimensions(ushort x, ushort y, ushort z) : this(x, y) + { + if (y == 0 && z > 0) + throw new ArgumentException("Y must be greater than zero to have three dimensions"); + + Z = z; + } + + /// + /// Gets the value of the X (or first) dimensional unit of the object. + /// + /// + /// An unsigned short that represents the length the first dimensional parameter. + /// + public ushort X { get; } + + /// + /// Gets the value of the Y or second dimensional unit of the object. + /// + /// + /// An unsigned short that represents the length the second dimensional parameter. + /// + public ushort Y { get; } + + /// + /// Gets the value of the Z or third dimensional unit of the object. + /// + /// + /// An unsigned short that represents the length the third dimensional parameter. + /// + public ushort Z { get; } + + /// + /// Gets the value of the total length of . + /// + /// + /// An integer that represents the combined length of all three dimensional parameters. + /// + public int Length => Z > 0 ? X * Y * Z : Y > 0 ? X * Y : X; + + /// + /// Indicates whether are empty or equals zero. + /// + public bool IsEmpty => Length == 0; + + /// + /// Indicates whether are multi-dimensional. + /// + /// + /// Multi-dimensional simply means that the dimensions have a value for Y or Z. + /// + public bool IsMultiDimensional => Y > 0; + + /// + /// Gets the value for the number or parameters or coordinates in the object. + /// + /// + /// An integer value 1, 2, or 3 that represents the number of dimensional parameters for the current object. + /// + /// + /// More plainly, this property indicates whether the current are one, two, or three + /// dimensional based on the values of X, Y, and Z. + /// + public int Rank => Z > 0 ? 3 : Y > 0 ? 2 : X > 0 ? 1 : 0; + + /// + /// Represents an empty object, or object with all three dimensional parameters equal + /// to zero. + /// + public static Dimensions Empty => new(); + + /// + /// Returns the dimension of a specified array type. + /// + /// The array instance for which to determine the dimensions. + /// + /// array is null. + /// array has a dimensional rank that is greater than + /// max value -or- array has more than three dimensions. + public static Dimensions FromArray(Array array) + { + if (array is null) + throw new ArgumentNullException(nameof(array)); + + if (array.Length == 0) return Empty; + + for (var i = 0; i < array.Rank; i++) + { + var length = array.GetLength(i); + + if (length > ushort.MaxValue) + throw new ArgumentOutOfRangeException(nameof(array), + $"Array length of {array.Length} is out of range. Length must be less than or equal to {ushort.MaxValue}"); + } + + return array.Rank switch + { + 1 => new Dimensions((ushort)array.GetLength(0)), + 2 => new Dimensions((ushort)array.GetLength(0), (ushort)array.GetLength(1)), + 3 => new Dimensions((ushort)array.GetLength(0), (ushort)array.GetLength(1), (ushort)array.GetLength(2)), + _ => throw new ArgumentOutOfRangeException($"An array with '{array.Rank} dimensional units is not supported.") + }; + } + + /// + /// Gets the set of indices for the object. + /// + /// + /// An containing all index strings that represent the indices of a + /// dimension array. If are empty, then an empty collection. + /// + /// + /// The indices are determined by the dimensions (x, y, z) of the object. + /// + public IEnumerable Indices() + { + var indices = new List(); + + for (ushort i = 0; i < X; i++) + { + if (Y == 0) + indices.Add(GenerateIndex(i)); + + for (ushort j = 0; j < Y; j++) + { + if (Z == 0) + indices.Add(GenerateIndex(i, j)); + + for (ushort k = 0; k < Z; k++) + indices.Add(GenerateIndex(i, j, k)); + } + } + + return indices; + } + + /// + /// Parses the provided string to a object. + /// + /// The value to parse. + /// + /// A new object that represents parsed dimensional value. + /// If value empty or 0; returns . + /// + /// value is null + /// value contains invalid characters. + /// + /// Valid dimensions must have only numbers and special characters "[ ,]". If more than 3 numbers are + /// found in the provided value, or the numbers are not parsable to a , + /// then exception will be thrown. + /// + /// 1 2 3 -or- [1,2] + /// + public static Dimensions Parse(string value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + if (value.IsEmpty()) return Empty; + + var numbers = Regex.Matches(value, @"\d+") + .Select(m => ushort.Parse(m.Value)) + .ToList(); + + return numbers.Count switch + { + 3 => new Dimensions(numbers[0], numbers[1], numbers[2]), + 2 => new Dimensions(numbers[0], numbers[1]), + 1 => new Dimensions(numbers[0]), + _ => throw new ArgumentOutOfRangeException(nameof(numbers.Count), + $"Value '{value}' has a invalid number of arguments. Expecting between 1 and 3 arguments.") + }; + } + + /// + /// Attempts to parse the provided string to a object. + /// + /// The value to parse. + /// When the method returns, the value of the parsed dimensions if successful; + /// otherwise, null + /// true if the parse was successful; otherwise, false. + /// value is null + /// + /// Valid dimensions must have only numbers and special characters "[ ,]". If more than 3 numbers are + /// found in the provided value, or the numbers are not parsable to a , + /// then exception will be thrown. + /// + /// 1 2 3 -or- [1,2,3] + /// + public static bool TryParse(string value, out Dimensions? dimensions) + { + if (string.IsNullOrEmpty(value)) + { + dimensions = null; + return false; + } + + var numbers = Regex.Matches(value, @"\d+") + .Select(m => ushort.Parse(m.Value)) + .ToList(); + + dimensions = numbers.Count switch + { + 3 => new Dimensions(numbers[0], numbers[1], numbers[2]), + 2 => new Dimensions(numbers[0], numbers[1]), + 1 => new Dimensions(numbers[0]), + _ => null + }; + + return dimensions is not null; + } + + /// + /// Creates a new instance of the current with the same value. + /// + /// A new object with the same value. + public Dimensions Copy() => + Z > 0 ? new Dimensions(X, Y, Z) : Y > 0 ? new Dimensions(X, Y) : X > 0 ? new Dimensions(X) : Empty; + + /// + /// Generates a string value that represents the in the L5X format. + /// + /// A string of numbers separated by a single space. + /// 1 2 3 + public override string ToString() => Z > 0 ? $"{X} {Y} {Z}" : Y > 0 ? $"{X} {Y}" : $"{X}"; + + /// + /// Generates a string value that represents the in the array bracket notation. + /// + /// A string of numbers separated by a comma and enclosed in brackets. + /// [1,2,3] + public string ToIndex() => Z > 0 ? $"[{X},{Y},{Z}]" : Y > 0 ? $"[{X},{Y}]" : X > 0 ? $"[{X}]" : "[]"; + + /// + /// Converts the provided to a value. + /// + /// The length value to convert. + /// + /// A Dimensions value representing the provided ushort length. + /// + public static implicit operator Dimensions(ushort length) => new(length); + + /// + /// Converts the provided to a value. + /// + /// The Dimensions value to convert. + /// + /// A int value representing the provided Dimensions . + /// + public static implicit operator int(Dimensions dimensions) => dimensions.Length; + + /// + public bool Equals(Dimensions? other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + return X == other.X && Y == other.Y && Z == other.Z; + } + + /// + public override bool Equals(object? obj) => Equals(obj as Dimensions); + + /// + public override int GetHashCode() => X.GetHashCode() ^ Y.GetHashCode() ^ Z.GetHashCode(); + + /// + /// Determines whether the objects are equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the objects are equal, otherwise, false. + public static bool operator ==(Dimensions left, Dimensions right) => Equals(left, right); + + /// + /// Determines whether the objects are not equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the objects are not equal, otherwise, false. + public static bool operator !=(Dimensions left, Dimensions right) => !Equals(left, right); + + private static string GenerateIndex(ushort x) => $"[{x}]"; + + private static string GenerateIndex(ushort x, ushort y) => $"[{x},{y}]"; + + private static string GenerateIndex(ushort x, ushort y, ushort z) => $"[{x},{y},{z}]"; +} \ No newline at end of file diff --git a/src/L5Sharp/Common/Instruction.cs b/src/L5Sharp/Common/Instruction.cs new file mode 100644 index 00000000..e8233f5f --- /dev/null +++ b/src/L5Sharp/Common/Instruction.cs @@ -0,0 +1,1735 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Text.RegularExpressions; +using JetBrains.Annotations; + +// ReSharper disable StringLiteralTypo +// ReSharper disable IdentifierTypo +// ReSharper disable InconsistentNaming +// ReSharper disable CommentTypo + +namespace L5Sharp.Core; + +/// +/// +/// +[PublicAPI] +public sealed class Instruction +{ + /// + /// Pattern for identifying any instruction and the contents of it's signature. This expression should + /// capture everything enclosed or between the instruction parentheses. This includes nested parenthesis. + /// This works on the assumption that the text has balanced opening/closing parentheses. + /// + public const string Pattern = @"[A-Za-z_]\w{1,39}\((?>\((?)|[^()]+|\)(?<-c>))*(?(c)(?!))\)"; + + /*/// + /// Pattern finds all text prior to opening parentheses, which is the instruction name or key that identifies + /// the instruction. + /// + private const string KeyPattern = @"[A-Za-z_]\w{1,39}(?=\()";*/ + + /// + /// Captures all content within parentheses, including outer parentheses and nested parentheses, assuming they + /// are balanced (number of opening equals number of closing). + /// + private const string SignaturePattern = @"\((?>\((?)|[^()]+|\)(?<-c>))*(?(c)(?!))\)"; + + /// + /// The regex pattern for Logix tag names without starting and ending anchors. + /// This pattern also includes a negative lookahead for removing text prior to parenthesis (i.e. instruction keys) + /// Use this pattern for tag names within text, such as longer + /// + private const string TagNamePattern = + @"(?!\w*\()[A-Za-z_][\w+:]{1,39}(?:(?:\[\d+\]|\[\d+,\d+\]|\[\d+,\d+,\d+\])?(?:\.[A-Za-z_]\w{1,39})?)+(?:\.[0-9][0-9]?)?"; + + /// + /// A regex pattern that finds all commas not contained in array brackets so that we can split the arguments + /// of an instruction signature into separate parsable values. + /// + private const string ArgumentSplitPattern = ",(?![^[]*])"; + + /// + /// Lazy list of all known instructions and their corresponding factory method function. + /// + private static Dictionary> _known = Factories().ToDictionary(x => x.Key, x => x.Value); + + /// + /// Creates a new with the provided string key and regex signature pattern. + /// + /// The key identifier of the instruction. + /// + /// + private Instruction(string key, string? signature = null, params Argument[] arguments) + { + if (string.IsNullOrEmpty(key)) + throw new ArgumentException("Instruction key cannot be null or empty.", nameof(key)); + + Key = key; + Signature = signature ?? $"{key}({DefaultArgs(arguments.Length)})"; + Arguments = arguments; + } + + /// + /// The unique identifier of the instruction type. + /// + /// + /// A containing the short hand instruction key identifier (e.g. XIC/OTE). + /// For an AddOnInstruction this is the name of the component. + /// + public string Key { get; } + + /// + /// The signature portion of the instruction instance. This represents the argument (parenthesis included) that are + /// supplied to the instruction block instance. + /// + /// + /// A containing the signature of the Instruction. + /// This simply joins the and ecloses them in parenthesis. + /// + public string Signature { get; } + + /// + /// The collection of values for the instruction instance. + /// + /// A of value objects. These could represent literal values, tag names, or expressions. + /// + public IEnumerable Arguments { get; } + + /// + /// The collection of operand names found in the signature of the instruction. + /// + /// + public IEnumerable Operadns + { + get + { + var input = Regex.Match(Signature, SignaturePattern).Value[1..^1]; + + return !string.IsNullOrEmpty(input) + ? Regex.Split(input, ArgumentSplitPattern) + : Enumerable.Empty().ToArray(); + } + } + + /// + /// The representation of the instruction instance. + /// + /// A instance that represents the instruction in Logix neutral text format. + public NeutralText Text => new($"{Key}({string.Join(',', Arguments.AsEnumerable())})"); + + /// + /// Indicates whether the instruction is conditional or evaluates a certain condition to be true or false, from which + /// it directs the control flow of a program. + /// + /// true if the instruction is conditional; Otherwise, false. + /// An example of a condition instruction is an . + public bool IsConditional => Key is nameof(CMP) or nameof(EQU) or nameof(GEQ) or nameof(GRT) or nameof(LEQ) + or nameof(LES) or nameof(LIM) or nameof(MEQ) or nameof(NEQ) or nameof(XIC) or nameof(XIO); + + /// + /// Indicates whether this Instruction is one that calls or references a Task component by name. + /// + public bool IsTaskCall => Key is nameof(EVENT); + + /// + /// Indicates whether this Instruction is one that calls or references a Routine component by name. + /// + public bool IsRoutineCall => Key is nameof(JSR) or nameof(JXR) or nameof(SFR) or nameof(SFP) or nameof(FOR); + + /// + /// Indicates whether the instruction argument cound matches the operand count. + /// + public bool IsValid => Key is nameof(JSR) or nameof(SBR) or nameof(RET) || Operadns.Count() == Arguments.Count(); + + /// + /// Creates a new with the provided key and optional arguments. + /// + /// A containing the unique name of the instruction. + /// A set of to initialize the intruction with. + /// A instance with the provided key and arguments. + /// + /// This factory method is the means through which to create unknown or other instruction that are not captured + /// in this classes static factory methods. If this is a known instruction, use the corresponding instruction + /// factory method so it can initialize the known signature. + /// + public static Instruction New(string key, params Argument[] args) => new(key, arguments: args); + + /// + /// Creates a new with the provided key, signature, and optional arguments. + /// + /// A containing the unique name of the instruction. + /// A containing the method signature or format. This should be in the + /// format 'key(arg1,arg2,...)'. + /// A set of to initialize the intruction with. + /// A instance with the provided key, signature, and arguments. + public static Instruction New(string key, string? signature = null, params Argument[] args) => + new(key, signature, args); + + /// + /// Parses the provided string neutral text into a instance. + /// + /// The neutral text to parse. + /// A object representing the parsed text. + /// text is null or empty. + public static Instruction Parse(string text) + { + if (string.IsNullOrEmpty(text)) + throw new ArgumentException("Instruction text can not be null or empty.", nameof(text)); + + if (!Regex.IsMatch(text, Pattern)) + throw new FormatException("Instruction text must be in the format of 'key(arg1,arg2,...)'."); + + var key = text[..text.IndexOf('(')]; + var signature = Regex.Match(text, SignaturePattern).Value[1..^1]; + var arguments = Regex.Split(signature, ArgumentSplitPattern).Select(Argument.Parse).ToArray(); + + return _known.TryGetValue(key, out var create) + ? create().Of(arguments) + : new Instruction(key, arguments: arguments); + } + + /// + /// Returns a collection of all known or defined instances. + /// + /// + /// A containing all known instances with no arguments. + /// + public static IEnumerable Keys() => _known.Keys.AsEnumerable(); + + /// + /// Retrieves teh argument for a specified operand name from the instruction. + /// + /// A containing the operand name to search for. + /// A representing the value passed + public Argument? GetArgument(string operand) + { + var index = Operadns.ToList().IndexOf(operand); + return index >= 0 ? Arguments.ElementAt(index) : default; + } + + /// + /// Retrieves teh argument for a specified operand name from the instruction. + /// + /// A zero based index number at which to retrieve an argument. + /// A representing the value passed + public Argument? GetArgument(int index) + { + return index >= 0 && index < Arguments.Count() ? Arguments.ElementAt(index) : default; + } + + /// + /// Retrieves all values from this instruction instance's arguments. + /// + /// A collection of values cotnained by the instruction. + public IEnumerable Tags() + { + var tags = new List(); + + foreach (var argument in Arguments) + { + if (argument.IsTag) tags.Add((TagName)argument); + if (!argument.IsExpression) continue; + tags.AddRange(((NeutralText)argument).Tags()); + } + + return tags; + } + + /// + /// Creates a of the same type with the updated argument values. + /// + /// The collection of arguments make up the instruction signature. + /// A new complete with the provided values. + public Instruction Append(params Argument[] arguments) => + new(Key, Signature, Arguments.Concat(arguments).ToArray()); + + + /// + /// Creates a of the same type with the updated argument values. + /// + /// The collection of arguments make up the instruction signature. + /// A new complete with the provided values. + public Instruction Of(params Argument[] arguments) => new(Key, Signature, arguments); + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) return true; + + return obj switch + { + Instruction other => Equals(Text, other.Text), + string text => Text.ToString().IsEquivalent(text), + _ => false + }; + } + + /// + public override int GetHashCode() => Text.GetHashCode(); + + /// + public override string ToString() => Text; + + /// + /// Determines the equality of two instances. + /// + /// The left to compare. + /// The right to compare. + /// true if the values are equal; Otherwise, false. + public static bool operator ==(Instruction? left, Instruction? right) => Equals(left, right); + + /// + /// Determines the equality of two instances. + /// + /// The left to compare. + /// The right to compare. + /// true if the values are not equal; Otherwise, false. + public static bool operator !=(Instruction? left, Instruction? right) => !Equals(left, right); + + #region Factories + + /// + /// Gets the ABL instruction definition instance. + /// + public static Instruction ABL(Argument channel, Argument serial_port_control, Argument character_count) => + new(nameof(ABL), "ABL(channel,serial_port_control,character_count)", channel, serial_port_control, + character_count); + + /// + /// Gets the ABS instruction definition instance. + /// + public static Instruction ABS(Argument source, Argument destination) => + new(nameof(ABS), "ABS(source,destination)", source, destination); + + /// + /// Gets the ACB instruction definition instance. + /// + public static Instruction ACB(Argument channel, Argument serial_port_control, Argument character_count) => + new(nameof(ACB), "ACB(channel,serial_port_control,character_count)", channel, serial_port_control, + character_count); + + /// + /// Gets the ACL instruction definition instance. + /// + public static Instruction + ACL(Argument channel, Argument clear_serial_port_read, Argument clear_serial_port_write) => new(nameof(ACL), + "ACL(channel,clear_serial_port_read,clear_serial_port_write)", channel, clear_serial_port_read, + clear_serial_port_write); + + /// + /// Gets the ACS instruction definition instance. + /// + public static Instruction ACS(Argument source, Argument destination) => + new(nameof(ACS), "ACS(source,destination)", source, destination); + + /// + /// Gets the ADD instruction definition instance. + /// + public static Instruction ADD(Argument source_A, Argument source_B, Argument destination) => + new(nameof(ADD), "ADD(source_A,source_B,destination)", source_A, source_B, destination); + + /// + /// Gets the AFI instruction definition instance. + /// + public static Instruction AFI() => new(nameof(AFI), "AFI()"); + + /// + /// Gets the AHL instruction definition instance. + /// + public static Instruction AHL(Argument channel, Argument ANDMask, Argument ORMask, Argument serial_port_control, + Argument channel_status) => new(nameof(AHL), + "AHL(channel,ANDMask,ORMask,serial_port_control,channel_status)", channel, ANDMask, ORMask, + serial_port_control, channel_status); + + /// + /// Gets the ALMA instruction definition instance. + /// + public static Instruction ALMA(Argument alma_tag, Argument @in, Argument program_acknowledge_all, + Argument program_disable, Argument program_enable) => new(nameof(ALMA), + "ALMA(alma_tag,in,program_acknowledge_all,program_disable,program_enable)", alma_tag, @in, + program_acknowledge_all, program_disable, program_enable); + + /// + /// Gets the ALMD instruction definition instance. + /// + public static Instruction ALMD(Argument almd_tag, Argument program_acknowledge, Argument program_reset, + Argument program_disable, Argument program_enable) => new(nameof(ALMD), + "ALMD(almd_tag,program_acknowledge,program_reset,program_disable,program_enable)", almd_tag, + program_acknowledge, program_reset, program_disable, program_enable); + + /// + /// Gets the AND instruction definition instance. + /// + public static Instruction AND(Argument source_A, Argument source_B, Argument destination) => + new(nameof(AND), "AND(source_A,source_B,destination)", source_A, source_B, destination); + + /// + /// Gets the ARD instruction definition instance. + /// + public static Instruction ARD(Argument channel, Argument destination, Argument serial_port_control, + Argument string_length, Argument characters_read) => new(nameof(ARD), + "ARD(channel,destination,serial_port_control,string_length,characters_read)", channel, destination, + serial_port_control, string_length, characters_read); + + /// + /// Gets the ARL instruction definition instance. + /// + public static Instruction ARL(Argument channel, Argument destination, Argument serial_port_control, + Argument string_length, Argument characters_read) => new(nameof(ARL), + "ARL(channel,destination,serial_port_control,string_length,characters_read)", channel, destination, + serial_port_control, string_length, characters_read); + + /// + /// Gets the ASN instruction definition instance. + /// + public static Instruction ASN(Argument source, Argument destination) => + new(nameof(ASN), "ASN(source,destination)", source, destination); + + /// + /// Gets the ATN instruction definition instance. + /// + public static Instruction ATN(Argument source, Argument destination) => + new(nameof(ATN), "ATN(source,destination)", source, destination); + + /// + /// Gets the AVC instruction definition instance. + /// + public static Instruction AVC(Argument avc_tag, Argument feedback_type, Argument feedback_reation_time, + Argument delay_type, Argument delay_time, Argument output_follows_actuate, Argument actuate, + Argument delay_enable, Argument feedback_1, Argument input_status, Argument output_status, Argument reset) => + new(nameof(AVC), + "AVC(avc_tag,feedback_type,feedback_reation_time,delay_type,delay_time,output_follows_actuate,actuate,delay_enable,feedback_1,input_status,output_status,reset)", + avc_tag, feedback_type, feedback_reation_time, delay_type, delay_time, output_follows_actuate, actuate, + delay_enable, feedback_1, input_status, output_status, reset); + + /// + /// Gets the AVE instruction definition instance. + /// + public static Instruction AVE(Argument array, Argument dim_to_vary, Argument destination, Argument control, + Argument length, Argument position) => new(nameof(AVE), + "AVE(array,dim_to_vary,destination,control,length,position)", array, dim_to_vary, destination, control, length, + position); + + /// + /// Gets the AWA instruction definition instance. + /// + public static Instruction AWA(Argument channel, Argument source, Argument serial_port_control, + Argument string_length, Argument characters_sent) => new(nameof(AWA), + "AWA(channel,source,serial_port_control,string_length,characters_sent)", channel, source, serial_port_control, + string_length, characters_sent); + + /// + /// Gets the AWT instruction definition instance. + /// + public static Instruction AWT(Argument channel, Argument source, Argument serial_port_control, + Argument string_length, Argument characters_sent) => new(nameof(AWT), + "AWT(channel,source,serial_port_control,string_length,characters_sent)", channel, source, serial_port_control, + string_length, characters_sent); + + /// + /// Gets the BRK instruction definition instance. + /// + public static Instruction BRK() => new(nameof(BRK), "BRK()"); + + /// + /// Gets the BSL instruction definition instance. + /// + public static Instruction BSL(Argument array, Argument control, Argument source_bit, Argument length) => + new(nameof(BSL), "BSL(array,control,source_bit,length)", array, control, source_bit, length); + + /// + /// Gets the BSR instruction definition instance. + /// + public static Instruction BSR(Argument array, Argument control, Argument source_bit, Argument length) => + new(nameof(BSR), "BSR(array,control,source_bit,length)", array, control, source_bit, length); + + /// + /// Gets the BTD instruction definition instance. + /// + public static Instruction BTD(Argument source, Argument source_bit, Argument destination, Argument destination_bit, + Argument length) => new(nameof(BTD), + "BTD(source,source_bit,destination,destination_bit,length)", source, source_bit, destination, destination_bit, + length); + + /// + /// Gets the CBCM instruction definition instance. + /// + public static Instruction CBCM(Argument cbcm_tag, Argument ack_type, Argument mode, Argument takeover_mode, + Argument enable, Argument safety_enable, Argument standard_enable, Argument arm_continuous, Argument start, + Argument stop_at_top, Argument press_in_motion, Argument motion_monitor_fault, Argument slide_zone, + Argument safety_enable_ack) => new(nameof(CBCM), + "CBCM(cbcm_tag,ack_type,mode,takeover_mode,enable,safety_enable,standard_enable,arm_continuous,start,stop_at_top,press_in_motion,motion_monitor_fault,slide_zone,safety_enable_ack)", + cbcm_tag, ack_type, mode, takeover_mode, enable, safety_enable, standard_enable, arm_continuous, start, + stop_at_top, press_in_motion, motion_monitor_fault, slide_zone, safety_enable_ack); + + /// + /// Gets the CBIM instruction definition instance. + /// + public static Instruction CBIM(Argument cbim_tag, Argument ack_type, Argument inch_time, Argument enable, + Argument safety_enable, Argument standard_enable, Argument start, Argument press_in_motion, + Argument motion_monitor_fault, Argument slide_zone, Argument safety_enable_ack) => new(nameof(CBIM), + "CBIM(cbim_tag,ack_type,inch_time,enable,safety_enable,standard_enable,start,press_in_motion,motion_monitor_fault,slide_zone,safety_enable_ack)", + cbim_tag, ack_type, inch_time, enable, safety_enable, standard_enable, start, press_in_motion, + motion_monitor_fault, slide_zone, safety_enable_ack); + + /// + /// Gets the CBSSM instruction definition instance. + /// + public static Instruction CBSSM(Argument cbssm_tag, Argument ack_type, Argument takeover_mode, Argument enable, + Argument safety_enable, Argument standard_enable, Argument start, Argument press_in_motion, + Argument motion_monitor_fault, Argument slide_zone, Argument saefty_enable_ack) => new(nameof(CBSSM), + "CBSSM(cbssm_tag,ack_type,takeover_mode,enable,safety_enable,standard_enable,start,press_in_motion,motion_monitor_fault,slide_zone,saefty_enable_ack)", + cbssm_tag, ack_type, takeover_mode, enable, safety_enable, standard_enable, start, press_in_motion, + motion_monitor_fault, slide_zone, saefty_enable_ack); + + /// + /// Gets the CLR instruction definition instance. + /// + public static Instruction CLR(Argument destination) => new(nameof(CLR), "CLR(destination)", destination); + + /// + /// Gets the CMP instruction definition instance. + /// + public static Instruction CMP(Argument expression) => new(nameof(CMP), "CMP(expression)", expression); + + /// + /// Gets the CONCAT instruction definition instance. + /// + public static Instruction CONCAT(Argument sourceA, Argument sourceB, Argument destination) => + new(nameof(CONCAT), "CONCAT(sourceA,sourceB,destination)", sourceA, sourceB, destination); + + /// + /// Gets the COP instruction definition instance. + /// + public static Instruction COP(Argument source, Argument destination, Argument length) => new(nameof(COP), + "COP(source,destination,length)", source, destination, length); + + /// + /// Gets the COS instruction definition instance. + /// + public static Instruction COS(Argument source, Argument destination) => + new(nameof(COS), "COS(source,destination)", source, destination); + + /// + /// Gets the CPM instruction definition instance. + /// + public static Instruction CPM(Argument cpm_tag, Argument cam_profile, Argument enable, Argument brake_cam, + Argument takeover_cam, Argument dynamic_cam, Argument input_status, Argument reverse, + Argument press_motion_status, Argument reset) => new(nameof(CPM), + "CPM(cpm_tag,cam_profile,enable,brake_cam,takeover_cam,dynamic_cam,input_status,reverse,press_motion_status,reset)", + cpm_tag, cam_profile, enable, brake_cam, takeover_cam, dynamic_cam, input_status, reverse, press_motion_status, + reset); + + /// + /// Gets the CPS instruction definition instance. + /// + public static Instruction CPS(Argument source, Argument destination, Argument length) => new(nameof(CPS), + "CPS(source,destination,length)", source, destination, length); + + /// + /// Gets the CPT instruction definition instance. + /// + public static Instruction CPT(Argument destination, Argument expression) => + new(nameof(CPT), "CPT(destination,expression)", destination, expression); + + /// + /// Gets the CROUT instruction definition instance. + /// + public static Instruction CROUT(Argument crout_tag, Argument feedback_type, Argument feedback_reaction_time, + Argument actuate, Argument feedback_1, Argument feedback_2, Argument input_status, Argument output_status, + Argument reset) => new(nameof(CROUT), + "CROUT(crout_tag,feedback_type,feedback_reaction_time,actuate,feedback_1,feedback_2,input_status,output_status,reset)", + crout_tag, feedback_type, feedback_reaction_time, actuate, feedback_1, feedback_2, input_status, output_status, + reset); + + /// + /// Gets the CSM instruction definition instance. + /// + public static Instruction CSM(Argument csm_tag, Argument mechanical_delay_timer, Argument max_pulse_period, + Argument motion_request, Argument channel_A, Argument channel_B, Argument input_status, Argument reset) => new( + nameof(CSM), + "CSM(csm_tag,mechanical_delay_timer,max_pulse_period,motion_request,channel_A,channel_B,input_status,reset)", + csm_tag, mechanical_delay_timer, max_pulse_period, motion_request, channel_A, channel_B, input_status, reset); + + /// + /// Gets the CTD instruction definition instance. + /// + public static Instruction CTD(Argument counter, Argument preset, Argument accum) => + new(nameof(CTD), "CTD(counter,preset,accum)", counter, preset, accum); + + /// + /// Gets the CTU instruction definition instance. + /// + public static Instruction CTU(Argument counter, Argument preset, Argument accum) => + new(nameof(CTU), "CTU(counter,preset,accum)", counter, preset, accum); + + /// + /// Gets the DCM instruction definition instance. + /// + public static Instruction DCM(Argument dcm_tag, Argument safety_function, Argument input_type, + Argument descrepancy_time, Argument channel_A, Argument channel_B, Argument input_status, Argument reset) => + new(nameof(DCM), + "DCM(dcm_tag,safety_function,input_type,descrepancy_time,channel_A,channel_B,input_status,reset)", dcm_tag, + safety_function, input_type, descrepancy_time, channel_A, channel_B, input_status, reset); + + /// + /// Gets the DCS instruction definition instance. + /// + public static Instruction DCS(Argument dcs_tag, Argument safety_function, Argument input_type, + Argument discrepancy_time, Argument restart_type, Argument cold_start_type, Argument channel_A, + Argument channel_B, Argument input_status, Argument reset) => new(nameof(DCS), + "DCS(dcs_tag,safety_function,input_type,discrepancy_time,restart_type,cold_start_type,channel_A,channel_B,input_status,reset)", + dcs_tag, safety_function, input_type, discrepancy_time, restart_type, cold_start_type, channel_A, channel_B, + input_status, reset); + + /// + /// Gets the DCSRT instruction definition instance. + /// + public static Instruction DCSRT(Argument dcsrt_tag, Argument safety_function, Argument input_type, + Argument discrepancy_time, Argument enable, Argument channel_A, Argument channel_B, Argument input_status, + Argument reset) => new(nameof(DCSRT), + "DCSRT(dcsrt_tag,safety_function,input_type,discrepancy_time,enable,channel_A,channel_B,input_status,reset)", + dcsrt_tag, safety_function, input_type, discrepancy_time, enable, channel_A, channel_B, input_status, reset); + + /// + /// Gets the DCST instruction definition instance. + /// + public static Instruction DCST(Argument dcst_tag, Argument safety_function, Argument input_type, + Argument discrepancy_time, Argument restart_type, Argument cold_start_type, Argument channel_A, + Argument channel_B, Argument test_request, Argument input_status, Argument reset) => new(nameof(DCST), + "DCST(dcst_tag,safety_function,input_type,discrepancy_time,restart_type,cold_start_type,channel_A,channel_B,test_request,input_status,reset)", + dcst_tag, safety_function, input_type, discrepancy_time, restart_type, cold_start_type, channel_A, channel_B, + test_request, input_status, reset); + + /// + /// Gets the DCSTM instruction definition instance. + /// + public static Instruction DCSTM(Argument dcstm_tag, Argument safety_function, Argument input_type, + Argument discrepancy_time, Argument restart_type, Argument cold_start_type, Argument test_type, + Argument test_time, Argument channel_A, Argument channel_B, Argument test_request, Argument mute, + Argument muting_lamp_status, Argument input_status, Argument reset) => new(nameof(DCSTM), + "DCSTM(dcstm_tag,safety_function,input_type,discrepancy_time,restart_type,cold_start_type,test_type,test_time,channel_A,channel_B,test_request,mute,muting_lamp_status,input_status,reset)", + dcstm_tag, safety_function, input_type, discrepancy_time, restart_type, cold_start_type, test_type, test_time, + channel_A, channel_B, test_request, mute, muting_lamp_status, input_status, reset); + + /// + /// Gets the DCSTL instruction definition instance. + /// + public static Instruction DCSTL(Argument dcstl_tag, Argument safety_function, Argument input_type, + Argument discrepancy_time, Argument restart_type, Argument cold_start_type, Argument channel_A, + Argument channel_B, Argument test_request, Argument unlock_request, Argument lock_feedback, + Argument hazard_stopped, Argument input_status, Argument reset) => new(nameof(DCSTL), + "DCSTL(dcstl_tag,safety_function,input_type,discrepancy_time,restart_type,cold_start_type,channel_A,channel_B,test_request,unlock_request,lock_feedback,hazard_stopped,input_status,reset)", + dcstl_tag, safety_function, input_type, discrepancy_time, restart_type, cold_start_type, channel_A, channel_B, + test_request, unlock_request, lock_feedback, hazard_stopped, input_status, reset); + + /// + /// Gets the DDT instruction definition instance. + /// + public static Instruction DDT(Argument source, Argument reference, Argument result, Argument cmp_control, + Argument length, Argument position, Argument result_control, Argument result_length, + Argument result_position) => new(nameof(DDT), + "DDT(source,reference,result,cmp_control,length,position,result_control,length,position)", source, reference, + result, cmp_control, length, position, result_control, result_length, result_position); + + /// + /// Gets the DEG instruction definition instance. + /// + public static Instruction DEG(Argument source, Argument destination) => + new(nameof(DEG), "DEG(source,destination)", source, destination); + + /// + /// Gets the DELETE instruction definition instance. + /// + public static Instruction DELETE(Argument source, Argument quantity, Argument start, Argument destination) => + new(nameof(DELETE), "DELETE(source,quantity,start,destination)", source, quantity, start, destination); + + /// + /// Gets the DIN instruction definition instance. + /// + public static Instruction DIN(Argument din_tag, Argument reset_type, Argument channel_A, Argument channel_B, + Argument circuit_reset, Argument fault_reset) => new(nameof(DIN), + "DIN(din_tag,reset_type,channel_A,channel_B,circuit_reset,fault_reset)", din_tag, reset_type, channel_A, + channel_B, circuit_reset, fault_reset); + + /// + /// Gets the DIV instruction definition instance. + /// + public static Instruction DIV(Argument source_A, Argument source_B, Argument destination) => + new(nameof(DIV), "DIV(source_A,source_B,destination)", source_A, source_B, destination); + + /// + /// Gets the DTOS instruction definition instance. + /// + public static Instruction DTOS(Argument source, Argument destination) => + new(nameof(DTOS), "DTOS(source,destination)", source, destination); + + /// + /// Gets the DTR instruction definition instance. + /// + public static Instruction DTR(Argument source, Argument mask, Argument reference) => + new(nameof(DTR), "DTR(source,mask,reference)", source, mask, reference); + + /// + /// Gets the ENPEN instruction definition instance. + /// + public static Instruction ENPEN(Argument enpen_tag, Argument reset_type, Argument channel_A, Argument channel_B, + Argument circuit_reset, Argument fault_reset) => new(nameof(ENPEN), + "ENPEN(enpen_tag,reset_type,channel_A,channel_B,circuit_reset,fault_reset)", enpen_tag, reset_type, channel_A, + channel_B, circuit_reset, fault_reset); + + /// + /// Gets the EOT instruction definition instance. + /// + public static Instruction EOT(Argument data_bit) => new(nameof(EOT), "EOT(data_bit)", data_bit); + + /// + /// Gets the EPMS instruction definition instance. + /// + public static Instruction EPMS(Argument epms_tag, Argument input_1, Argument input_2, Argument input_3, + Argument input_4, Argument input_5, Argument input_6, Argument input_7, Argument input_8, Argument input_status, + Argument lck, Argument reset) => new(nameof(EPMS), + "EPMS(epms_tag,input_1,input_2,input_3,input_4,input_5,input_6,input_7,input_8,input_status,lock,reset)", + epms_tag, input_1, input_2, input_3, input_4, input_5, input_6, input_7, input_8, input_status, lck, reset); + + /// + /// Gets the EQU instruction definition instance. + /// + public static Instruction EQU(Argument source_A, Argument source_B) => + new(nameof(EQU), "EQU(source_A,source_B)", source_A, source_B); + + /// + /// Gets the ESTOP instruction definition instance. + /// + public static Instruction ESTOP(Argument estop_tag, Argument reset_type, Argument channel_A, Argument channel_B, + Argument circuit_reset, Argument fault_reset) => new(nameof(ESTOP), + "ESTOP(estop_tag,reset_type,channel_A,channel_B,circuit_reset,fault_reset)", estop_tag, reset_type, channel_A, + channel_B, circuit_reset, fault_reset); + + /// + /// Gets the EVENT instruction definition instance. + /// + public static Instruction EVENT(Argument task) => new(nameof(EVENT), "EVENT(task)", task); + + /// + /// Gets the FAL instruction definition instance. + /// + public static Instruction FAL(Argument control, Argument length, Argument position, Argument mode, + Argument destination, Argument expression) => + new(nameof(FAL), "FAL(control,length,position,mode,destination,expression)", control, length, position, mode, + destination, expression); + + /// + /// Gets the FBC instruction definition instance. + /// + public static Instruction FBC(Argument source, Argument reference, Argument result, Argument cmp_control, + Argument length, Argument position, Argument result_control, Argument result_length, + Argument result_position) => new(nameof(FBC), + "FBC(source,reference,result,cmp_control,length,position,result_control,length,position)", source, reference, + result, cmp_control, length, position, result_control, result_length, result_position); + + /// + /// Gets the FFL instruction definition instance. + /// + public static Instruction FFL(Argument source, Argument FIFO, Argument control, Argument length, + Argument position) => + new(nameof(FFL), "FFL(source,FIFO,control,length,position)", source, FIFO, control, length, position); + + /// + /// Gets the FFU instruction definition instance. + /// + public static Instruction FFU(Argument FIFO, Argument destination, Argument control, Argument length, + Argument position) => + new(nameof(FFU), "FFU(FIFO,destination,control,length,position)", FIFO, destination, control, length, + position); + + /// + /// Gets the FIND instruction definition instance. + /// + public static Instruction FIND(Argument source, Argument search, Argument start, Argument result) => + new(nameof(FIND), "FIND(source,search,start,result)", source, search, start, result); + + /// + /// Gets the FLL instruction definition instance. + /// + public static Instruction FLL(Argument source, Argument destination, Argument length) => new(nameof(FLL), + "FLL(source,destination,length)", source, destination, length); + + /// + /// Gets the FOR instruction definition instance. + /// + public static Instruction FOR(Argument routine_name, Argument index, Argument initial_value, + Argument terminal_value, Argument step_size) => new(nameof(FOR), + "FOR(routine_name,index,initial_value,terminal_value,step_size)", routine_name, index, initial_value, + terminal_value, step_size); + + /// + /// Gets the FPMS instruction definition instance. + /// + public static Instruction FPMS(Argument fpms_tag, Argument input_1, Argument input_2, Argument input_3, + Argument input_4, Argument input_5, Argument fault_reset) => new(nameof(FPMS), + "FPMS(fpms_tag,input_1,input_2,input_3,input_4,input_5,fault_reset)", fpms_tag, input_1, input_2, input_3, + input_4, input_5, fault_reset); + + /// + /// Gets the FRD instruction definition instance. + /// + public static Instruction FRD(Argument source, Argument destination) => + new(nameof(FRD), "FRD(source,destination)", source, destination); + + /// + /// Gets the FSBM instruction definition instance. + /// + public static Instruction FSBM(Argument fsbm_tag, Argument restart_type, Argument S1_S2_time, Argument S2_LC_time, + Argument LC_S3_time, Argument S3_S4_time, Argument maximum_mute_time, Argument maximum_override_time, + Argument direction, Argument light_curtain, Argument sensor_1, Argument sensor_2, Argument sensor_3, + Argument sensor_4, Argument enable_mute, Argument @override, Argument input_status, Argument muting_lamp_status, + Argument reset) => new(nameof(FSBM), + "FSBM(fsbm_tag,restart_type,S1_S2_time,S2_LC_time,LC_S3_time,S3_S4_time,maximum_mute_time,maximum_override_time,direction,light_curtain,sensor_1,sensor_2,sensor_3,sensor_4,enable_mute,override,input_status,muting_lamp_status,reset)", + fsbm_tag, restart_type, S1_S2_time, S2_LC_time, LC_S3_time, S3_S4_time, maximum_mute_time, + maximum_override_time, direction, light_curtain, sensor_1, sensor_2, sensor_3, sensor_4, enable_mute, @override, + input_status, muting_lamp_status, reset); + + /// + /// Gets the FSC instruction definition instance. + /// + public static Instruction FSC(Argument control, Argument length, Argument position, Argument mode, + Argument expression) => + new(nameof(FSC), "FSC(control,length,position,mode,expression)", control, length, position, mode, expression); + + /// + /// Gets the GEQ instruction definition instance. + /// + public static Instruction GEQ(Argument source_A, Argument source_B) => + new(nameof(GEQ), "GEQ(source_A,source_B)", source_A, source_B); + + /// + /// Gets the GRT instruction definition instance. + /// + public static Instruction GRT(Argument source_A, Argument source_B) => + new(nameof(GRT), "GRT(source_A,source_B)", source_A, source_B); + + /// + /// Gets the GSV instruction definition instance. + /// + public static Instruction GSV(Argument class_name, Argument instance_name, Argument attribute_name, + Argument destination) => + new(nameof(GSV), "GSV(class_name,instance_name,attribute_name,destination)", class_name, instance_name, + attribute_name, destination); + + /// + /// Gets the INSERT instruction definition instance. + /// + public static Instruction INSERT(Argument sourceA, Argument sourceB, Argument start, Argument destination) => + new(nameof(INSERT), "INSERT(sourceA,sourceB,start,destination)", sourceA, sourceB, start, destination); + + /// + /// Gets the IOT instruction definition instance. + /// + public static Instruction IOT(Argument output_tag) => new(nameof(IOT), "IOT(output_tag)", output_tag); + + /// + /// Gets the JMP instruction definition instance. + /// + public static Instruction JMP(Argument label_name) => new(nameof(JMP), "JMP(label_name)", label_name); + + /// + /// Gets the JSR instruction definition instance. + /// + public static Instruction JSR(Argument routine_name, Argument number_of_inputs, params Argument[]? parameters) => + new(nameof(JSR), "JSR(routine_name,number_of_inputs,input_1,input_n,return_1,return_n)", + parameters is not null + ? new[] { routine_name, number_of_inputs }.Concat(parameters).ToArray() + : new[] { routine_name, number_of_inputs }); + + /// + /// Gets the JXR instruction definition instance. + /// + public static Instruction JXR(Argument external_routine_name, Argument external_routine_control, Argument parameter, + Argument return_parameter) => new(nameof(JXR), + "JXR(external_routine_name,external_routine_control,parameter,return_parameter)", external_routine_name, + external_routine_control, parameter, return_parameter); + + /// + /// Gets the LBL instruction definition instance. + /// + public static Instruction LBL(Argument label_name) => new(nameof(LBL), "LBL(label_name)", label_name); + + /// + /// Gets the LC instruction definition instance. + /// + public static Instruction LC(Argument lc_tag, Argument reset_type, Argument channel_A, Argument channel_B, + Argument input_filter_time, Argument mute_light_curtain, Argument circuit_reset, Argument fault_reset) => new( + nameof(LC), + "LC(lc_tag,reset_type,channel_A,channel_B,input_filter_time,mute_light_curtain,circuit_reset,fault_reset)", + lc_tag, reset_type, channel_A, channel_B, input_filter_time, mute_light_curtain, circuit_reset, fault_reset); + + /// + /// Gets the LEQ instruction definition instance. + /// + public static Instruction LEQ(Argument source_A, Argument source_B) => + new(nameof(LEQ), "LEQ(source_A,source_B)", source_A, source_B); + + /// + /// Gets the LES instruction definition instance. + /// + public static Instruction LES(Argument source_A, Argument source_B) => + new(nameof(LES), "LES(source_A,source_B)", source_A, source_B); + + /// + /// Gets the LFL instruction definition instance. + /// + public static Instruction LFL(Argument source, Argument LIFO, Argument control, Argument length, + Argument position) => + new(nameof(LFL), "LFL(source,LIFO,control,length,position)", source, LIFO, control, length, position); + + /// + /// Gets the LFU instruction definition instance. + /// + public static Instruction LFU(Argument LIFO, Argument destination, Argument control, Argument length, + Argument position) => + new(nameof(LFU), "LFU(LIFO,destination,control,length,position)", LIFO, destination, control, length, + position); + + /// + /// Gets the LIM instruction definition instance. + /// + public static Instruction LIM(Argument low_limit, Argument test, Argument high_limit) => new(nameof(LIM), + "LIM(low_limit,test,high_limit)", low_limit, test, high_limit); + + /// + /// Gets the LN instruction definition instance. + /// + public static Instruction LN(Argument source, Argument destination) => + new(nameof(LN), "LN(source,destination)", source, destination); + + /// + /// Gets the LOG instruction definition instance. + /// + public static Instruction LOG(Argument source, Argument destination) => + new(nameof(LOG), "LOG(source,destination)", source, destination); + + /// + /// Gets the LOWER instruction definition instance. + /// + public static Instruction LOWER(Argument source, Argument destination) => + new(nameof(LOWER), "LOWER(source,destination)", source, destination); + + /// + /// Gets the MAAT instruction definition instance. + /// + public static Instruction MAAT(Argument axis, Argument motion_control) => + new(nameof(MAAT), "MAAT(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MAFR instruction definition instance. + /// + public static Instruction MAFR(Argument axis, Argument motion_control) => + new(nameof(MAFR), "MAFR(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MAG instruction definition instance. + /// + public static Instruction MAG(Argument slave_axis, Argument master_axis, Argument motion_control, + Argument direction, Argument ratio, Argument slave_counts, Argument master_counts, Argument master_reference, + Argument ratio_format, Argument clutch, Argument accel_rate, Argument accel_units) => new(nameof(MAG), + "MAG(slave_axis,master_axis,motion_control,direction,ratio,slave_counts,master_counts,master_reference,ratio_format,clutch,accel_rate,accel_units)", + slave_axis, master_axis, motion_control, direction, ratio, slave_counts, master_counts, master_reference, + ratio_format, clutch, accel_rate, accel_units); + + /// + /// Gets the MAH instruction definition instance. + /// + public static Instruction MAH(Argument axis, Argument motion_control) => + new(nameof(MAH), "MAH(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MAHD instruction definition instance. + /// + public static Instruction MAHD(Argument axis, Argument motion_control, Argument diagnostic_test, + Argument observed_direction) => new(nameof(MAHD), + "MAHD(axis,motion_control,diagnostic_test,observed_direction)", axis, motion_control, diagnostic_test, + observed_direction); + + /// + /// Gets the MAJ instruction definition instance. + /// + public static Instruction MAJ(Argument axis, Argument motion_control, Argument direction, Argument speed, + Argument speed_units, Argument accel_rate, Argument accel_units, Argument decel_rate, Argument decel_units, + Argument profile, Argument merge, Argument merge_speed) => new(nameof(MAJ), + "MAJ(axis,motion_control,direction,speed,speed_units,accel_rate,accel_units,decel_rate,decel_units,profile,merge,merge_speed)", + axis, motion_control, direction, speed, speed_units, accel_rate, accel_units, decel_rate, decel_units, profile, + merge, merge_speed); + + /// + /// Gets the MAM instruction definition instance. + /// + public static Instruction MAM(Argument axis, Argument motion_control, Argument move_type, Argument position, + Argument speed, Argument speed_units, Argument accel_rate, Argument accel_units, Argument decel_rate, + Argument decel_units, Argument profile, Argument merge, Argument merge_speed) => new(nameof(MAM), + "MAM(axis,motion_control,move_type,position,speed,speed_units,accel_rate,accel_units,decel_rate,decel_units,profile,merge,merge_speed)", + axis, motion_control, move_type, position, speed, speed_units, accel_rate, accel_units, decel_rate, decel_units, + profile, merge, merge_speed); + + /// + /// Gets the MAOC instruction definition instance. + /// + public static Instruction MAOC(Argument axis, Argument execution_target, Argument motion_control, Argument output, + Argument input, Argument output_cam, Argument cam_start_position, Argument cam_end_position, + Argument output_compensation, Argument execution_mode, Argument execution_schedule, Argument axis_arm_position, + Argument cam_arm_position, Argument reference) => new(nameof(MAOC), + "MAOC(axis,execution_target,motion_control,output,input,output_cam,cam_start_position,cam_end_position,output_compensation,execution_mode,execution_schedule,axis_arm_position,cam_arm_position,reference)", + axis, execution_target, motion_control, output, input, output_cam, cam_start_position, cam_end_position, + output_compensation, execution_mode, execution_schedule, axis_arm_position, cam_arm_position, reference); + + /// + /// Gets the MAPC instruction definition instance. + /// + public static Instruction MAPC(Argument slave_axis, Argument master_axis, Argument motion_control, + Argument direction, Argument cam_profile, Argument slave_scaling, Argument master_scaling, + Argument execution_mode, Argument execution_schedule, Argument master_lock_position, Argument cam_lock_position, + Argument master_reference, Argument master_direction) => new(nameof(MAPC), + "MAPC(slave_axis,master_axis,motion_control,direction,cam_profile,slave_scaling,master_scaling,execution_mode,execution_schedule,master_lock_position,cam_lock_position,master_reference,master_direction)", + slave_axis, master_axis, motion_control, direction, cam_profile, slave_scaling, master_scaling, execution_mode, + execution_schedule, master_lock_position, cam_lock_position, master_reference, master_direction); + + /// + /// Gets the MAR instruction definition instance. + /// + public static Instruction MAR(Argument axis, Argument motion_control, Argument trigger_condition, + Argument windowed_registration, Argument minimum_position, Argument maximum_position) => new(nameof(MAR), + "MAR(axis,motion_control,trigger_condition,windowed_registration,minimum_position,maximum_position)", axis, + motion_control, trigger_condition, windowed_registration, minimum_position, maximum_position); + + /// + /// Gets the MAS instruction definition instance. + /// + public static Instruction MAS(Argument axis, Argument motion_control, Argument stop_type, Argument change_decel, + Argument decel_rate, Argument decel_units) => new(nameof(MAS), + "MAS(axis,motion_control,stop_type,change_decel,decel_rate,decel_units)", axis, motion_control, stop_type, + change_decel, decel_rate, decel_units); + + /// + /// Gets the MASD instruction definition instance. + /// + public static Instruction MASD(Argument axis, Argument motion_control) => + new(nameof(MASD), "MASD(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MASR instruction definition instance. + /// + public static Instruction MASR(Argument axis, Argument motion_control) => + new(nameof(MASR), "MASR(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MATC instruction definition instance. + /// + public static Instruction MATC(Argument axis, Argument motion_control, Argument direction, Argument cam_profile, + Argument distance_scaling, Argument time_scaling, Argument execution_mode, Argument execution_schedule) => new( + nameof(MATC), + "MATC(axis,motion_control,direction,cam_profile,distance_scaling,time_scaling,execution_mode,execution_schedule)", + axis, motion_control, direction, cam_profile, distance_scaling, time_scaling, execution_mode, + execution_schedule); + + /// + /// Gets the MAW instruction definition instance. + /// + public static Instruction MAW(Argument axis, Argument motion_control, Argument trigger_condition, + Argument position) => + new(nameof(MAW), "MAW(axis,motion_control,trigger_condition,position)", axis, motion_control, + trigger_condition, position); + + /// + /// Gets the MCCD instruction definition instance. + /// + public static Instruction MCCD(Argument coordinate_system, Argument motion_control, Argument motion_type, + Argument change_speed, Argument speed, Argument speed_units, Argument change_accel, Argument accel_rate, + Argument accel_units, Argument change_decel, Argument decel_rate, Argument decel_units, Argument scope) => new( + nameof(MCCD), + "MCCD(coordinate_system,motion_control,motion_type,change_speed,speed,speed_units,change_accel,accel_rate,accel_units,change_decel,decel_rate, decel_units,scope)", + coordinate_system, motion_control, motion_type, change_speed, speed, speed_units, change_accel, accel_rate, + accel_units, change_decel, decel_rate, decel_units, scope); + + /// + /// Gets the MCCM instruction definition instance. + /// + public static Instruction MCCM(Argument coordinate_system, Argument motion_control, Argument move_type, + Argument position, Argument circle_type, Argument radius, Argument direction, Argument speed, + Argument speed_units, Argument accel_rate, Argument accel_units, Argument decel_rate, Argument decel_units, + Argument profile, Argument termination_type, Argument merge, Argument merge_speed) => new(nameof(MCCM), + "MCCM(coordinate_system,motion_control,move_type,position,circle_type,via/center/radius,direction,speed,speed_units,accel_rate,accel_units,decel_rate,decel_units,profile,termination_type,merge,merge_speed)", + coordinate_system, motion_control, move_type, position, circle_type, radius, direction, speed, speed_units, + accel_rate, accel_units, decel_rate, decel_units, profile, termination_type, merge, merge_speed); + + /// + /// Gets the MCCP instruction definition instance. + /// + public static Instruction MCCP(Argument motion_control, Argument cam, Argument length, Argument start_slope, + Argument end_slope, Argument cam_profile) => new(nameof(MCCP), + "MCCP(motion_control,cam,length,start_slope,end_slope,cam_profile)", motion_control, cam, length, start_slope, + end_slope, cam_profile); + + /// + /// Gets the MCLM instruction definition instance. + /// + public static Instruction MCLM(Argument coordinate_system, Argument motion_control, Argument move_type, + Argument position, Argument speed, Argument speed_units, Argument accel_rate, Argument accel_units, + Argument decel_rate, Argument decel_units, Argument profile, Argument termination_type, Argument merge, + Argument merge_speed) => new(nameof(MCLM), + "MCLM(coordinate_system,motion_control,move_type,position,speed,speed_units,accel_rate,accel_units,decel_rate,decel_units,profile, termination_type,merge,merge_speed)", + coordinate_system, motion_control, move_type, position, speed, speed_units, accel_rate, accel_units, decel_rate, + decel_units, profile, termination_type, merge, merge_speed); + + /// + /// Gets the MCD instruction definition instance. + /// + public static Instruction MCD(Argument axis, Argument motion_control, Argument motion_type, Argument change_speed, + Argument speed, Argument change_accel, Argument accel_rate, Argument change_decel, Argument decel_rate, + Argument speed_units, Argument accel_units, Argument decel_units) => new(nameof(MCD), + "MCD(axis,motion_control,motion_type,change_speed,speed,change_accel,accel_rate,change_decel,decel_rate,speed_units,accel_units,decel_units)", + axis, motion_control, motion_type, change_speed, speed, change_accel, accel_rate, change_decel, decel_rate, + speed_units, accel_units, decel_units); + + /// + /// Gets the MCR instruction definition instance. + /// + public static Instruction MCR() => new(nameof(MCR), "MCR()"); + + /// + /// Gets the MCS instruction definition instance. + /// + public static Instruction MCS(Argument coordinate_system, Argument motion_control, Argument stop_type, + Argument change_decel, Argument decel_rate, Argument decel_units) => new(nameof(MCS), + "MCS(coordinate_system,motion_control,stop_type,change_decel,decel_rate, decel_units)", coordinate_system, + motion_control, stop_type, change_decel, decel_rate, decel_units); + + /// + /// Gets the MCSD instruction definition instance. + /// + public static Instruction MCSD(Argument coordinate_system, Argument motion_control) => + new(nameof(MCSD), "MCSD(coordinate_system,motion_control)", coordinate_system, motion_control); + + /// + /// Gets the MCSR instruction definition instance. + /// + public static Instruction MCSR(Argument coordinate_system, Argument motion_control) => + new(nameof(MCSR), "MCSR(coordinate_system,motion_control)", coordinate_system, motion_control); + + /// + /// Gets the MCSV instruction definition instance. + /// + public static Instruction MCSV(Argument motion_control, Argument cam_profile, Argument master_value, + Argument slave_value, Argument slope_value, Argument slope_derivative) => new(nameof(MCSV), + "MCSV(motion_control,cam_profile,master_value,slave_value,slope_value,slope_derivative)", motion_control, + cam_profile, master_value, slave_value, slope_value, slope_derivative); + + /// + /// Gets the MCT instruction definition instance. + /// + public static Instruction MCT(Argument source_system, Argument target_system, Argument motion_control, + Argument orientation, Argument translation) => new(nameof(MCT), + "MCT(source_system,target_system,motion_control,orientation,translation)", source_system, target_system, + motion_control, orientation, translation); + + /// + /// Gets the MCTP instruction definition instance. + /// + public static Instruction MCTP(Argument source_system, Argument target_system, Argument motion_control, + Argument orientation, Argument translation, Argument transform_direction, Argument reference_position, + Argument transform_position) => new(nameof(MCTP), + "MCTP(source_system,target_system,motion_control,orientation,translation,transform_direction,reference_position,transform_position)", + source_system, target_system, motion_control, orientation, translation, transform_direction, reference_position, + transform_position); + + /// + /// Gets the MDF instruction definition instance. + /// + public static Instruction MDF(Argument axis, Argument motion_control) => + new(nameof(MDF), "MDF(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MDO instruction definition instance. + /// + public static Instruction MDO(Argument axis, Argument motion_control, Argument drive_output, + Argument drive_units) => + new(nameof(MDO), "MDO(axis,motion_control,drive_output,drive_units)", axis, motion_control, drive_output, + drive_units); + + /// + /// Gets the MDOC instruction definition instance. + /// + public static Instruction MDOC(Argument axis, Argument execution_target, Argument motion_control, + Argument disarm_type) => + new(nameof(MDOC), "MDOC(axis,execution_target,motion_control,disarm_type)", axis, execution_target, + motion_control, disarm_type); + + /// + /// Gets the MDR instruction definition instance. + /// + public static Instruction MDR(Argument axis, Argument motion_control) => + new(nameof(MDR), "MDR(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MDW instruction definition instance. + /// + public static Instruction MDW(Argument axis, Argument motion_control) => + new(nameof(MDW), "MDW(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MEQ instruction definition instance. + /// + public static Instruction MEQ(Argument source, Argument mask, Argument compare) => + new(nameof(MEQ), "MEQ(source,mask,compare)", source, mask, compare); + + /// + /// Gets the MGS instruction definition instance. + /// + public static Instruction MGS(Argument group, Argument motion_control, Argument stop_mode) => + new(nameof(MGS), "MGS(group,motion_control,stop_mode)", group, motion_control, stop_mode); + + /// + /// Gets the MGSD instruction definition instance. + /// + public static Instruction MGSD(Argument group, Argument motion_control) => + new(nameof(MGSD), "MGSD(group,motion_control)", group, motion_control); + + /// + /// Gets the MGSP instruction definition instance. + /// + public static Instruction MGSP(Argument group, Argument motion_control) => + new(nameof(MGSP), "MGSP(group,motion_control)", group, motion_control); + + /// + /// Gets the MGSR instruction definition instance. + /// + public static Instruction MGSR(Argument group, Argument motion_control) => + new(nameof(MGSR), "MGSR(group,motion_control)", group, motion_control); + + /// + /// Gets the MID instruction definition instance. + /// + public static Instruction MID(Argument source, Argument quantity, Argument start, Argument destination) => + new(nameof(MID), "MID(source,quantity,start,destination)", source, quantity, start, destination); + + /// + /// Gets the MMVC instruction definition instance. + /// + public static Instruction MMVC(Argument mmvc_tag, Argument enable, Argument keyswitch, Argument bottom, + Argument flywheel_stopped, Argument safety_enable, Argument actuate, Argument input_status, + Argument output_status, Argument reset) => new(nameof(MMVC), + "MMVC(mmvc_tag,enable,keyswitch,bottom,flywheel_stopped,safety_enable,actuate,input_status,output_status,reset)", + mmvc_tag, enable, keyswitch, bottom, flywheel_stopped, safety_enable, actuate, input_status, output_status, + reset); + + /// + /// Gets the MOD instruction definition instance. + /// + public static Instruction MOD(Argument source_A, Argument source_B, Argument destination) => + new(nameof(MOD), "MOD(source_A,source_B,destination)", source_A, source_B, destination); + + /// + /// Gets the MOV instruction definition instance. + /// + public static Instruction MOV(Argument source, Argument destination) => + new(nameof(MOV), "MOV(source,destination)", source, destination); + + /// + /// Gets the MRAT instruction definition instance. + /// + public static Instruction MRAT(Argument axis, Argument motion_control) => + new(nameof(MRAT), "MRAT(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MRHD instruction definition instance. + /// + public static Instruction MRHD(Argument axis, Argument motion_control, Argument diagnostic_test) => + new(nameof(MRHD), "MRHD(axis,motion_control,diagnostic_test)", axis, motion_control, diagnostic_test); + + /// + /// Gets the MRP instruction definition instance. + /// + public static Instruction MRP(Argument axis, Argument motion_control, Argument type, Argument position_select, + Argument position) => + new(nameof(MRP), "MRP(axis,motion_control,type,position_select,position)", axis, motion_control, type, + position_select, position); + + /// + /// Gets the MSF instruction definition instance. + /// + public static Instruction MSF(Argument axis, Argument motion_control) => + new(nameof(MSF), "MSF(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MSG instruction definition instance. + /// + public static Instruction MSG(Argument message_control) => + new(nameof(MSG), "MSG(message_control)", message_control); + + /// + /// Gets the MSO instruction definition instance. + /// + public static Instruction MSO(Argument axis, Argument motion_control) => + new(nameof(MSO), "MSO(axis,motion_control)", axis, motion_control); + + /// + /// Gets the MUL instruction definition instance. + /// + public static Instruction MUL(Argument source_A, Argument source_B, Argument destination) => + new(nameof(MUL), "MUL(source_A,source_B,destination)", source_A, source_B, destination); + + /// + /// Gets the MVC instruction definition instance. + /// + public static Instruction MVC(Argument mvc_tag, Argument feedback_type, Argument feedback_reaction_time, + Argument actuate, Argument feedback_1, Argument feedback_2, Argument input_status, Argument output_status, + Argument reset) => new(nameof(MVC), + "MVC(mvc_tag,feedback_type,feedback_reaction_time,actuate,feedback_1,feedback_2,input_status,output_status,reset)", + mvc_tag, feedback_type, feedback_reaction_time, actuate, feedback_1, feedback_2, input_status, output_status, + reset); + + /// + /// Gets the MVM instruction definition instance. + /// + public static Instruction MVM(Argument source, Argument mask, Argument destination) => + new(nameof(MVM), "MVM(source,mask,destination)", source, mask, destination); + + /// + /// Gets the NEG instruction definition instance. + /// + public static Instruction NEG(Argument source, Argument destination) => + new(nameof(NEG), "NEG(source,destination)", source, destination); + + /// + /// Gets the NEQ instruction definition instance. + /// + public static Instruction NEQ(Argument source_A, Argument source_B) => + new(nameof(NEQ), "NEQ(source_A,source_B)", source_A, source_B); + + /// + /// Gets the NOP instruction definition instance. + /// + public static Instruction NOP() => new(nameof(NOP), "NOP()"); + + /// + /// Gets the NOT instruction definition instance. + /// + public static Instruction NOT(Argument source, Argument destination) => + new(nameof(NOT), "NOT(source,destination)", source, destination); + + /// + /// Gets the ONS instruction definition instance. + /// + public static Instruction ONS(Argument storage_bit) => new(nameof(ONS), "ONS(storage_bit)", storage_bit); + + /// + /// Gets the OR instruction definition instance. + /// + public static Instruction OR(Argument source_A, Argument source_B, Argument destination) => new(nameof(OR), + "OR(source_A,source_B,destination)", source_A, source_B, destination); + + /// + /// Gets the OSF instruction definition instance. + /// + public static Instruction OSF(Argument storage_bit, Argument output_bit) => + new(nameof(OSF), "OSF(storage_bit,output_bit)", storage_bit, output_bit); + + /// + /// Gets the OSR instruction definition instance. + /// + public static Instruction OSR(Argument storage_bit, Argument output_bit) => + new(nameof(OSR), "OSR(storage_bit,output_bit)", storage_bit, output_bit); + + /// + /// Gets the OTE instruction definition instance. + /// + public static Instruction OTE(Argument data_bit) => new(nameof(OTE), "OTE(data_bit)", data_bit); + + /// + /// Gets the OTL instruction definition instance. + /// + public static Instruction OTL(Argument data_bit) => new(nameof(OTL), "OTL(data_bit)", data_bit); + + /// + /// Gets the OTU instruction definition instance. + /// + public static Instruction OTU(Argument data_bit) => new(nameof(OTU), "OTU(data_bit)", data_bit); + + /// + /// Gets the PATT instruction definition instance. + /// + public static Instruction PATT(Argument phase_name, Argument result) => + new(nameof(PATT), "PATT(phase_name,result)", phase_name, result); + + /// + /// Gets the PCLF instruction definition instance. + /// + public static Instruction PCLF(Argument phase_name) => new(nameof(PCLF), "PCLF(phase_name)", phase_name); + + /// + /// Gets the PCMD instruction definition instance. + /// + public static Instruction PCMD(Argument phase_name, Argument command, Argument result) => + new(nameof(PCMD), "PCMD(phase_name,command,result)", phase_name, command, result); + + /// + /// Gets the PDET instruction definition instance. + /// + public static Instruction PDET(Argument phase_name) => new(nameof(PDET), "PDET(phase_name)", phase_name); + + /// + /// Gets the PFL instruction definition instance. + /// + public static Instruction PFL(Argument source) => new(nameof(PFL), "PFL(source)", source); + + /// + /// Gets the PID instruction definition instance. + /// + public static Instruction PID(Argument PID, Argument process_variable, Argument tieback, Argument control_variable, + Argument pid_master_loop, Argument inhold_bit, Argument inhold_value) => new(nameof(PID), + "PID(PID,process_variable,tieback,control_variable,pid_master_loop,inhold_bit,inhold_value)", PID, + process_variable, tieback, control_variable, pid_master_loop, inhold_bit, inhold_value); + + /// + /// Gets the POVR instruction definition instance. + /// + public static Instruction POVR(Argument phase_name, Argument command, Argument result) => + new(nameof(POVR), "POVR(phase_name,command,result)", phase_name, command, result); + + /// + /// Gets the PPD instruction definition instance. + /// + public static Instruction PPD() => new(nameof(PPD), "PPD()"); + + /// + /// Gets the PRNP instruction definition instance. + /// + public static Instruction PRNP() => new(nameof(PRNP), "PRNP()"); + + /// + /// Gets the PSC instruction definition instance. + /// + public static Instruction PSC() => new(nameof(PSC), "PSC()"); + + /// + /// Gets the PXRQ instruction definition instance. + /// + public static Instruction PXRQ(Argument phase_instruction, Argument external_request, Argument data_value) => + new(nameof(PXRQ), "PXRQ(phase_instruction,external_request,data_value)", phase_instruction, external_request, + data_value); + + /// + /// Gets the RAD instruction definition instance. + /// + public static Instruction RAD(Argument source, Argument destination) => + new(nameof(RAD), "RAD(source,destination)", source, destination); + + /// + /// Gets the RES instruction definition instance. + /// + public static Instruction RES(Argument structure) => new(nameof(RES), "RES(structure)", structure); + + /// + /// Gets the RET instruction definition instance. + /// + public static Instruction RET(params Argument[] outputs) => new(nameof(RET), "RET(return_1,return_n)", outputs); + + /// + /// Gets the RIN instruction definition instance. + /// + public static Instruction RIN(Argument rin_tag, Argument reset_type, Argument channel_A, Argument channel_B, + Argument circuit_reset, Argument fault_reset) => new(nameof(RIN), + "RIN(rin_tag,reset_type,channel_A,channel_B,circuit_reset,fault_reset)", rin_tag, reset_type, channel_A, + channel_B, circuit_reset, fault_reset); + + /// + /// Gets the ROUT instruction definition instance. + /// + public static Instruction ROUT(Argument rout_tag, Argument feedback_type, Argument enable, Argument feedback_1, + Argument feedback_2, Argument fault_reset) => new(nameof(ROUT), + "ROUT(rout_tag,feedback_type,enable,feedback_1,feedback_2,fault_reset)", rout_tag, feedback_type, enable, + feedback_1, feedback_2, fault_reset); + + /// + /// Gets the RTO instruction definition instance. + /// + public static Instruction RTO(Argument timer, Argument preset, Argument accum) => + new(nameof(RTO), "RTO(timer,preset,accum)", timer, preset, accum); + + /// + /// Gets the RTOS instruction definition instance. + /// + public static Instruction RTOS(Argument source, Argument destination) => + new(nameof(RTOS), "RTOS(source,destination)", source, destination); + + /// + /// Gets the SBR instruction definition instance. + /// + public static Instruction SBR(params Argument[] inputs) => new(nameof(SBR), "SBR(input_1,input_n)", inputs); + + /// + /// Gets the SFP instruction definition instance. + /// + public static Instruction SFP(Argument SFC_routine_name, Argument target_state) => + new(nameof(SFP), "SFP(SFC_routine_name,target_state)", SFC_routine_name, target_state); + + /// + /// Gets the SFR instruction definition instance. + /// + public static Instruction SFR(Argument SFC_routine_name, Argument step_name) => new(nameof(SFR), + "SFR(SFC_routine_name,step_name)", SFC_routine_name, step_name); + + /// + /// Gets the SIN instruction definition instance. + /// + public static Instruction SIN(Argument source, Argument destination) => + new(nameof(SIN), "SIN(source,destination)", source, destination); + + /// + /// Gets the SIZE instruction definition instance. + /// + public static Instruction SIZE(Argument souce, Argument dimension_to_vary, Argument size) => + new(nameof(SIZE), "SIZE(souce,dimension_to_vary,size)", souce, dimension_to_vary, size); + + /// + /// Gets the SMAT instruction definition instance. + /// + public static Instruction SMAT(Argument smat_tag, Argument restart_type, Argument short_circuit_detect_delay_time, + Argument channel_A, Argument channel_B, Argument input_status, Argument reset) => new(nameof(SMAT), + "SMAT(smat_tag,restart_type,short_circuit_detect_delay_time,channel_A,channel_B,input_status,reset)", smat_tag, + restart_type, short_circuit_detect_delay_time, channel_A, channel_B, input_status, reset); + + /// + /// Gets the SQI instruction definition instance. + /// + public static Instruction SQI(Argument array, Argument mask, Argument source, Argument control, Argument length, + Argument position) => + new(nameof(SQI), "SQI(array,mask,source,control,length,position)", array, mask, source, control, length, + position); + + /// + /// Gets the SQL instruction definition instance. + /// + public static Instruction SQL(Argument array, Argument source, Argument control, Argument length, + Argument position) => + new(nameof(SQL), "SQL(array,source,control,length,position)", array, source, control, length, position); + + /// + /// Gets the SQO instruction definition instance. + /// + public static Instruction SQO(Argument array, Argument mask, Argument destination, Argument control, + Argument length, Argument position) => + new(nameof(SQO), "SQO(array,mask,destination,control,length,position)", array, mask, destination, control, + length, position); + + /// + /// Gets the SQR instruction definition instance. + /// + public static Instruction SQR(Argument source, Argument destination) => + new(nameof(SQR), "SQR(source,destination)", source, destination); + + /// + /// Gets the SRT instruction definition instance. + /// + public static Instruction SRT(Argument array, Argument dim_to_vary, Argument control, Argument length, + Argument position) => + new(nameof(SRT), "SRT(array,dim_to_vary,control,length,position)", array, dim_to_vary, control, length, + position); + + /// + /// Gets the SSV instruction definition instance. + /// + public static Instruction SSV(Argument class_name, Argument instance_name, Argument attribute_name, + Argument source) => + new(nameof(SSV), "SSV(class_name,instance_name,attribute_name,source)", class_name, instance_name, + attribute_name, source); + + /// + /// Gets the STD instruction definition instance. + /// + public static Instruction STD(Argument array, Argument dim_to_vary, Argument destination, Argument control, + Argument length, Argument position) => new(nameof(STD), + "STD(array,dim_to_vary,destination,control,length,position)", array, dim_to_vary, destination, control, length, + position); + + /// + /// Gets the STOD instruction definition instance. + /// + public static Instruction STOD(Argument source, Argument destination) => + new(nameof(STOD), "STOD(source,destination)", source, destination); + + /// + /// Gets the STOR instruction definition instance. + /// + public static Instruction STOR(Argument source, Argument destination) => + new(nameof(STOR), "STOR(source,destination)", source, destination); + + /// + /// Gets the SUB instruction definition instance. + /// + public static Instruction SUB(Argument source_A, Argument source_B, Argument destination) => + new(nameof(SUB), "SUB(source_A,source_B,destination)", source_A, source_B, destination); + + /// + /// Gets the SWPB instruction definition instance. + /// + public static Instruction SWPB(Argument source, Argument order_mode, Argument destination) => + new(nameof(SWPB), "SWPB(source,order_mode,destination)", source, order_mode, destination); + + /// + /// Gets the TAN instruction definition instance. + /// + public static Instruction TAN(Argument source, Argument destination) => + new(nameof(TAN), "TAN(source,destination)", source, destination); + + /// + /// Gets the THRS instruction definition instance. + /// + public static Instruction THRS(Argument thrs_tag, Argument active_pin_type, Argument active_pin, + Argument right_button_normally_open, Argument right_button_normally_closed, Argument left_button_normally_open, + Argument left_button_normally_closed, Argument fault_reset) => new(nameof(THRS), + "THRS(thrs_tag,active_pin_type,active_pin,right_button_normally_open,right_button_normally_closed,left_button_normally_open,left_button_normally_closed,fault_reset)", + thrs_tag, active_pin_type, active_pin, right_button_normally_open, right_button_normally_closed, + left_button_normally_open, left_button_normally_closed, fault_reset); + + /// + /// Gets the THRSE instruction definition instance. + /// + public static Instruction THRSE(Argument thrse_tag, Argument discprepancy_time, Argument enable, + Argument disconnected, Argument right_button_normally_open, Argument right_button_normally_closed, + Argument left_button_normally_open, Argument left_button_normally_closed, Argument input_status, + Argument resest) => new(nameof(THRSE), + "THRSE(thrse_tag,discprepancy_time,enable,disconnected,right_button_normally_open,right_button_normally_closed,left_button_normally_open,left_button_normally_closed,input_status,resest)", + thrse_tag, discprepancy_time, enable, disconnected, right_button_normally_open, right_button_normally_closed, + left_button_normally_open, left_button_normally_closed, input_status, resest); + + /// + /// Gets the TND instruction definition instance. + /// + public static Instruction TND() => new(nameof(TND), "TND()"); + + /// + /// Gets the TOD instruction definition instance. + /// + public static Instruction TOD(Argument source, Argument destination) => + new(nameof(TOD), "TOD(source,destination)", source, destination); + + /// + /// Gets the TOF instruction definition instance. + /// + public static Instruction TOF(Argument timer, Argument preset, Argument accum) => + new(nameof(TOF), "TOF(timer,preset,accum)", timer, preset, accum); + + /// + /// Gets the TON instruction definition instance. + /// + public static Instruction TON(Argument timer, Argument preset, Argument accum) => + new(nameof(TON), "TON(timer,preset,accum)", timer, preset, accum); + + /// + /// Gets the TRN instruction definition instance. + /// + public static Instruction TRN(Argument source, Argument destination) => + new(nameof(TRN), "TRN(source,destination)", source, destination); + + /// + /// Gets the TSAM instruction definition instance. + /// + public static Instruction TSAM(Argument tsam_tag, Argument restart_type, Argument S1_S2_time, Argument S2_LC_time, + Argument maximum_mute_time, Argument maximum_override_time, Argument light_curtain, Argument sensor_1, + Argument sensor_2, Argument enable_mute, Argument @override, Argument input_status, Argument muting_lamp_status, + Argument reset) => new(nameof(TSAM), + "TSAM(tsam_tag,restart_type,S1_S2_time,S2_LC_time,maximum_mute_time,maximum_override_time,light_curtain,sensor_1,sensor_2,enable_mute,override,input_status,muting_lamp_status,reset)", + tsam_tag, restart_type, S1_S2_time, S2_LC_time, maximum_mute_time, maximum_override_time, light_curtain, + sensor_1, sensor_2, enable_mute, @override, input_status, muting_lamp_status, reset); + + /// + /// Gets the TSSM instruction definition instance. + /// + public static Instruction TSSM(Argument tssm_tag, Argument restart_type, Argument S1_S2_discrepancy_time, + Argument S1_S2_LC_minimum_time, Argument S1_S2_LC_maximum_time, Argument maximum_mute_time, + Argument maximum_override_time, Argument light_curtain, Argument sensor_1, Argument sensor_2, + Argument enable_mute, Argument @override, Argument input_status, Argument muting_lamp_status, Argument reset) => + new(nameof(TSSM), + "TSSM(tssm_tag,restart_type,S1_S2_discrepancy_time,S1_S2_LC_minimum_time,S1_S2_LC_maximum_time,maximum_mute_time,maximum_override_time,light_curtain,sensor_1,sensor_2,enable_mute,override,input_status,muting_lamp_status,reset)", + tssm_tag, restart_type, S1_S2_discrepancy_time, S1_S2_LC_minimum_time, S1_S2_LC_maximum_time, + maximum_mute_time, maximum_override_time, light_curtain, sensor_1, sensor_2, enable_mute, @override, + input_status, muting_lamp_status, reset); + + /// + /// Gets the UID instruction definition instance. + /// + public static Instruction UID() => new(nameof(UID), "UID()"); + + /// + /// Gets the UIE instruction definition instance. + /// + public static Instruction UIE() => new(nameof(UIE), "UIE()"); + + /// + /// Gets the UPPER instruction definition instance. + /// + public static Instruction UPPER(Argument source, Argument destination) => + new(nameof(UPPER), "UPPER(source,destination)", source, destination); + + /// + /// Creates a new XIC instruction instance with the predeinfed signature and provided instruction arguments. + /// + /// A new with initialized key, signature, and arguments. + /// + /// Note that this instruction method signature was extracted from the Rockwell L5X documentation. + /// Each instruction will take the set of matching the instruction signature. + /// It is up to the caller to know whether these can be immediate value arguments or tag name reference arguments. + /// + public static Instruction XIC(Argument data_bit) => new(nameof(XIC), "XIC(data_bit)", data_bit); + + /// + /// Gets the XIO instruction definition instance. + /// + public static Instruction XIO(Argument data_bit) => new(nameof(XIO), "XIO(data_bit)", data_bit); + + /// + /// Gets the XOR instruction definition instance. + /// + public static Instruction XOR(Argument source_A, Argument source_B, Argument destination) => + new(nameof(XOR), "XOR(source_A,source_B,destination)", source_A, source_B, destination); + + /// + /// Gets the XPY instruction definition instance. + /// + public static Instruction XPY(Argument source_A, Argument source_B, Argument destination) => + new(nameof(XPY), "XPY(source_A,source_B,destination)", source_A, source_B, destination); + + #endregion + + /// + /// Creates some default arg names for a provided number of arguments. + /// + private static string DefaultArgs(int number) + { + return string.Join(',', Enumerable.Range(0, number).Select(i => $"arg{i}")); + } + + /// + /// Indexes all instruction factory methods in the class and creates a function returning the instruction + /// mathcing the specified key or method name. The method is passed null argumnts and therefore will be a default + /// instruction instance. Callers can then use to pass argument array. + /// + private static IEnumerable>> Factories() + { + var methods = typeof(Instruction).GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(m => m.ReturnType == typeof(Instruction) && m.Name.All(char.IsUpper)); + + foreach (var method in methods) + { + var arguments = method.GetParameters() + .Select(p => Expression.TypeAs(Expression.Constant(null), p.ParameterType)); + var function = Expression.Call(method, arguments); + var lambda = Expression.Lambda>(function); + yield return new KeyValuePair>(method.Name, lambda.Compile()); + } + } +} \ No newline at end of file diff --git a/src/L5Sharp/Common/NeutralText.cs b/src/L5Sharp/Common/NeutralText.cs new file mode 100644 index 00000000..a3c56a31 --- /dev/null +++ b/src/L5Sharp/Common/NeutralText.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; + +namespace L5Sharp.Core; + +/// +/// A thin wrapper around the textual representation of logic notation called neutral text. This could represent a +/// single instruction signature, a rung of logic (combination of instructions), or a line of structured text. The +/// purpose of this class is to provide a way to parse the text into strongly typed objects that are easier to work with. +/// +/// +/// Neutral text can represent a single instruction or a full rung (collection of instructions). +/// Each instruction contains sets of tag names and values known as arguments or operands. +/// This class provides functions for extracting the textual information into strongly type classes that are easier +/// to work with. +/// +/// +/// +/// +public sealed class NeutralText +{ + private readonly string _text; + + /// + /// Creates a new object with the provided text input. + /// + /// A string input that represents a neutral text format. + /// When text is null. + /// When text is null. + public NeutralText(string text) + { + _text = text ?? throw new ArgumentNullException(nameof(text)); + } + + /// + /// Indicates whether the current neutral text value has balanced brackets and parentheses. + /// + /// true if the text has balanced brackets and parentheses; otherwise, false. + public bool IsBalanced => TextIsBalanced(_text, '[', ']') && TextIsBalanced(_text, '(', ')'); + + /// + /// Indicates whether the current neutral text value is an empty string. + /// + /// true if the text empty; otherwise false. + public bool IsEmpty => _text.IsEquivalent(string.Empty); + + /// + /// Represents a new empty instance of the . + /// + /// An empty object. + public static NeutralText Empty => new(string.Empty); + + /// + /// Returns a value indicating whether a specified instruction key occurs within this neutral text. + /// + /// The instruction name to seek. + /// true if this text contains the instruction key; otherwise, false.. + public bool Contains(string value) => _text.Contains(value); + + /// + /// Runs the provided regex pattern against the neutral text and indicates whether the patterns is matched. + /// + /// The regex pattern to test against. + /// true if regex is a match against this neutral text value. + public bool HasPattern(string regex) => Regex.IsMatch(_text, regex); + + /// + /// Returns a collection of objects that were found in the current neutral text value. + /// + /// An containing objects found in the text. + public IEnumerable Instructions() => + Regex.Matches(_text, Instruction.Pattern).Select(m => Instruction.Parse(m.Value)); + + /// + /// + /// + /// + /// + public IEnumerable Instructions(string key) => + Regex.Matches(_text, Instruction.Pattern).Select(m => Instruction.Parse(m.Value)).Where(i => i.Key == key); + + /// + /// Returns a collection of objects that were found in the current neutral text value. + /// + /// An containing objects found in the text. + public IEnumerable Instructions(Instruction instruction) => + Regex.Matches(_text, Instruction.Pattern).Select(m => Instruction.Parse(m.Value)).Where(i => i == instruction); + + /// + /// Gets a collection of keywords found in the current neutral text. + /// + /// A containing values found. + public IEnumerable Keywords() => Keyword.All().Where(k => _text.Contains(k.Value)); + + /// + /// Gets a collection of tag names found in the current neutral text. + /// + /// A of values that were in from the current text. + /// + public IEnumerable Tags() => Regex.Matches(_text, TagName.SearchPattern).Select(t => new TagName(t.Value)); + + /// + /// Gets a collection of tag names found in the current neutral text that are operands or arguments to a specific instruction. + /// + /// The instruction for which to find tags as arguments to. + /// A containing tag names found in the specified instruction. + public IEnumerable TagsIn(string instruction) => Instructions(instruction).SelectMany(i => i.Text.Tags()); + + /// + public override string ToString() => _text; + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) return true; + + return obj switch + { + NeutralText other => _text.IsEquivalent(other._text), + string other => _text.IsEquivalent(other), + _ => false + }; + } + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(_text); + + /// + /// Determines if the provided objects are equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are equal; otherwise, false. + public static bool operator ==(NeutralText? left, NeutralText? right) => Equals(left, right); + + /// + /// Determines if the provided objects are not equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are not equal; otherwise, false. + public static bool operator !=(NeutralText? left, NeutralText? right) => !Equals(left, right); + + /// + /// Converts a NeutralText object to a string object. + /// + /// the NeutralText instance to convert. + /// A string that represents the value of the NeutralText. + public static implicit operator string(NeutralText text) => text._text; + + /// + /// Converts a string object to a NeutralText object. + /// + /// the string instance to convert. + /// A NeutralText that represents the value of the string. + public static implicit operator NeutralText(string text) => new(text); + + private bool TextIsBalanced(string value, char opening, char closing) + { + var characters = new Stack(); + + foreach (var c in value) + { + if (Equals(c, opening)) + characters.Push(c); + + if (!Equals(c, closing)) continue; + + if (!characters.TryPop(out _)) + return false; + } + + return characters.Count == 0; + } +} \ No newline at end of file diff --git a/src/L5Sharp/Common/ProductType.cs b/src/L5Sharp/Common/ProductType.cs new file mode 100644 index 00000000..f0da89e5 --- /dev/null +++ b/src/L5Sharp/Common/ProductType.cs @@ -0,0 +1,116 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// An entity that represents the type of product of a given . +/// +/// +/// This object is a simple entity type wrapper that groups the product type id and name. +/// Product types are defined by Rockwell and assigned unique Id and name. +/// Some known/common types that are available as static members of this class +/// include , , , and . +/// +public class ProductType +{ + /// + /// Creates a new entity with the provided id and name. + /// + /// The unique Id of the ProductType. + /// The name of the ProductType. Will default to empty if not provided. + public ProductType(ushort id, string? name = null) + { + Id = id; + Name = name ?? string.Empty; + } + + /// + /// Gets the value that uniquely identifies the . + /// + public ushort Id { get; } + + /// + /// Gets the value that represents the name. + /// + public string Name { get; } + + /// + /// Represents an Unknown with no id or name. + /// + public static ProductType Unknown => new(0); + + /// + /// Gets the General Purpose Discrete I/O ProductType instance (id=7). + /// + public static ProductType Discrete => new(7, "General Purpose Discrete I/O"); + + /// + /// Gets the General Purpose Analog I/O ProductType instance (id=10). + /// + public static ProductType Analog => new(10, "General Purpose Analog I/O"); + + /// + /// Gets the General Purpose Discrete ProductType instance (id=14). + /// + public static ProductType Controller => new(14, "Programmable Logic Controller"); + + /// + /// Gets the General Purpose Discrete ProductType instance (id=12). + /// + public static ProductType Communications => new(12, "Communications Adapter"); + + /// + /// Converts a object to a that represents the Id. + /// + /// The object to convert. + /// A representing the value of the ProductType Id. + public static implicit operator ushort(ProductType productType) => productType.Id; + + /// + /// Converts a value to a object that represents the Id. + /// + /// The value to convert. + /// A with the Id of the converted value. + public static implicit operator ProductType(ushort productTypeId) => new(productTypeId); + + /// + /// Creates a new using the provided product Id. + /// + /// The unique valid that identifies the Product. + /// A new object with the provided Id. + public static ProductType Parse(string productId) => + ushort.TryParse(productId, out var id) ? new ProductType(id) : Unknown; + + /// + public override string ToString() => Name; + + /// + public override bool Equals(object? obj) + { + return obj switch + { + ProductType other => Id.Equals(other.Id), + ValueType other => Id.Equals(other), + _ => false + }; + } + + /// + public override int GetHashCode() => Id.GetHashCode(); + + /// + /// Determines if the provided objects are equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are equal; otherwise, false. + public static bool operator ==(ProductType? left, ProductType? right) => Equals(left, right); + + /// + /// Determines if the provided objects are not equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are not equal; otherwise, false. + public static bool operator !=(ProductType? left, ProductType? right) => !Equals(left, right); +} \ No newline at end of file diff --git a/src/L5Sharp/Common/Revision.cs b/src/L5Sharp/Common/Revision.cs new file mode 100644 index 00000000..4fceda57 --- /dev/null +++ b/src/L5Sharp/Common/Revision.cs @@ -0,0 +1,165 @@ +using System; +using System.Globalization; +using System.Text.RegularExpressions; + +namespace L5Sharp.Core; + +/// +/// Represents a revision number that is expressed by as {Major}.{Minor}. +/// +public sealed class Revision : IComparable +{ + private const string RevisionSeparator = "."; + private readonly string _value; + + /// + /// Creates a new default with value 1.0. + /// + public Revision() + { + _value = "1.0"; + } + + /// + /// + /// + /// + public Revision(string value) + { + if (string.IsNullOrEmpty(value)) + throw new ArgumentException("Revision value can not be null or empty."); + + if (!Regex.IsMatch(value, @"^[0-9]+\.[0-9]+$")) + throw new FormatException( + $"Value '{value}' is invalid revision format. Revision format must be Major.Minor."); + + _value = value; + } + + /// + /// Creates a new with the specified major and minor values. + /// + /// The value of the major revision. + /// The value of the minor revision. Will default to 0 if not provided. + public Revision(ushort major, ushort minor = 0) + { + _value = $"{major}.{minor}"; + } + + /// + /// Creates a new with the specified value. + /// + /// The double value representing the revision number. + public Revision(double value) + { + _value = value.ToString(CultureInfo.InvariantCulture); + } + + /// + /// The major revision of the value. + /// + public string Major => _value.Split(RevisionSeparator)[0]; + + /// + /// The minor revision of the value. + /// + public string Minor => _value.Split(RevisionSeparator)[1]; + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + Revision other => string.Compare(_value, other._value, StringComparison.Ordinal), + string value => string.Compare(_value, value, StringComparison.Ordinal), + _ => throw new ArgumentException($"Can not compare {obj.GetType()} with Revision value.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + Revision other => string.Equals(_value, other._value, StringComparison.Ordinal), + string value => string.Equals(_value, value, StringComparison.Ordinal), + _ => false + }; + } + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + public override string ToString() => _value; + + /// + /// Determines if the provided objects are equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are equal; otherwise, false. + public static bool operator ==(Revision left, Revision right) => Equals(left, right); + + /// + /// Determines if the provided objects are not equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are not equal; otherwise, false. + public static bool operator !=(Revision left, Revision right) => !Equals(left, right); + + /// + /// Compares two objects and determines if left is greater than right. + /// + /// A to compare. + /// A to compare. + /// true if left is greater than right, otherwise, false. + public static bool operator >(Revision left, Revision right) => left.CompareTo(right) > 0; + + /// + /// Compares two objects and determines if left is less than right. + /// + /// A to compare. + /// A to compare. + /// true if left is less than right, otherwise, false. + public static bool operator <(Revision left, Revision right) => left.CompareTo(right) < 0; + + /// + /// Compares two objects and determines if left is greater than or equal to right. + /// + /// A to compare. + /// A to compare. + /// true if left is greater than or equal to right, otherwise, false. + public static bool operator >=(Revision left, Revision right) => left.CompareTo(right) >= 0; + + /// + /// Compares two objects and determines if left is less than or equal to right. + /// + /// A to compare. + /// A to compare. + /// true if left is less or equal to than right, otherwise, false. + public static bool operator <=(Revision left, Revision right) => left.CompareTo(right) <= 0; + + /// + /// Converts a to a . + /// + /// The revision value to convert. + /// A new value representing a major and minor revision. + public static implicit operator string(Revision revision) => revision.ToString(); + + /// + /// Converts a to a . + /// + /// The revision value to convert. + /// A new value representing a major and minor revision. + public static implicit operator Revision(string revision) => new(revision); + + /// + /// Converts a to a . + /// + /// The revision value to convert. + /// A new value representing a major and minor revision. + public static implicit operator Revision(double revision) => new(revision); +} \ No newline at end of file diff --git a/src/L5Sharp/Common/ScanRate.cs b/src/L5Sharp/Common/ScanRate.cs new file mode 100644 index 00000000..9522dc4c --- /dev/null +++ b/src/L5Sharp/Common/ScanRate.cs @@ -0,0 +1,90 @@ +using System; +using System.Globalization; + +namespace L5Sharp.Core; + +/// +/// A configurable property of a that controls the rate at which the task will be evaluated or scanned. +/// +/// +/// is a simple float value that must be between 0.1 and 2,000,000.0ms. +/// Attempting to set the to a value outside that range will result in an +/// . +/// This parameter will control the rate at which the component is scanned. +/// +public readonly struct ScanRate : IEquatable +{ + private readonly float _rate; + + /// + /// Creates a new instance of with the specified rate value. + /// + /// The scan rate value in milliseconds. Valid range is between 0.1 and 2M + /// Throw when the provided rate is outside the specified range + public ScanRate(float rate) + { + if (rate is < 0.1f or > 2000000.0f) + throw new ArgumentOutOfRangeException(nameof(rate), + "Rate must be value between 0.1 and 2,000,000.0 ms"); + + _rate = rate; + } + + /// + /// Converts a to a . + /// + /// The value to convert. + /// A value. + public static implicit operator float(ScanRate rate) => rate._rate; + + /// + /// Converts a to a . + /// + /// The value to convert. + /// A value. + public static implicit operator ScanRate(float rate) => new(rate); + + /// + /// Parses a string value into a . + /// + /// The string to parse. + /// A value if the parse was successful; otherwise; the default value. + public static ScanRate Parse(string str) => + float.TryParse(str, out var result) ? new ScanRate(result) : default; + + /// + public override string ToString() => _rate.ToString(CultureInfo.CurrentCulture); + + /// + public bool Equals(ScanRate other) => _rate.Equals(other._rate); + + /// + public override bool Equals(object? obj) + { + return obj switch + { + ScanRate other => _rate.Equals(other._rate), + ValueType value => _rate.Equals(value), + _ => false + }; + } + + /// + public override int GetHashCode() => _rate.GetHashCode(); + + /// + /// Determines if the provided objects are equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are equal; otherwise, false. + public static bool operator ==(ScanRate left, ScanRate right) => Equals(left, right); + + /// + /// Determines if the provided objects are not equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are not equal; otherwise, false. + public static bool operator !=(ScanRate left, ScanRate right) => !Equals(left, right); +} \ No newline at end of file diff --git a/src/L5Sharp/Common/TagMap.cs b/src/L5Sharp/Common/TagMap.cs new file mode 100644 index 00000000..db52c29e --- /dev/null +++ b/src/L5Sharp/Common/TagMap.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace L5Sharp.Core; + +public class TagMap : IEnumerable> +{ + private readonly List> _map = new(); + + /// + /// Represents a mapping class for tags. + /// + public TagMap() + { + } + + /// + /// + /// + /// + /// + /// + /// + public void Map(TagName from, TagName to) + { + if (from is null) throw new ArgumentNullException(nameof(from)); + if (to is null) throw new ArgumentNullException(nameof(to)); + if (from.IsEmpty) throw new ArgumentException("Can not create mapping for empty tag."); + if (to.IsEmpty) throw new ArgumentException("Can not create mapping for empty tag."); + _map.Add(new KeyValuePair(from, to)); + } + + /// + public IEnumerator> GetEnumerator() => _map.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} \ No newline at end of file diff --git a/src/L5Sharp/Common/TagName.cs b/src/L5Sharp/Common/TagName.cs new file mode 100644 index 00000000..5a01be04 --- /dev/null +++ b/src/L5Sharp/Common/TagName.cs @@ -0,0 +1,465 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace L5Sharp.Core; + +/// +/// A string wrapper representing a Logix TagName. +/// +/// +/// This value type class make working with string tag name easier by providing +/// methods fo analyzing and breaking the tag name into constituent parts (members). +/// +public sealed class TagName : IComparable, IEquatable +{ + private readonly string _tagName; + + private const char MemberSeparator = '.'; + private const char ArrayOpenSeparator = '['; + private const char ArrayCloseSeparator = ']'; + + /// + /// A regex pattern for a Logix tag name with starting and ending anchors. + /// Use this pattern to match a string and ensure it is only a tag name an nothing else. + /// + public const string AnchorPattern = + @"^[A-Za-z_][\w+:]{1,39}(?:(?:\[\d+\]|\[\d+,\d+\]|\[\d+,\d+,\d+\])?(?:\.[A-Za-z_]\w{1,39})?)+(?:\.[0-9][0-9]?)?$"; + + /// + /// The regex pattern for Logix tag names without starting and ending anchors. + /// This pattern also includes a negative lookahead for removing text prior to parenthesis (i.e. instruction keys) + /// Use this pattern for tag names within text, such as longer + /// + public const string SearchPattern = + @"(?!\w*\()[A-Za-z_][\w+:]{1,39}(?:(?:\[\d+\]|\[\d+,\d+\]|\[\d+,\d+,\d+\])?(?:\.[A-Za-z_]\w{1,39})?)+(?:\.[0-9][0-9]?)?"; + + /// + /// Creates a new object with the provided string tag name. + /// + /// The string that represents the tag name value. + /// tagName is null. + public TagName(string tagName) + { + _tagName = tagName ?? throw new ArgumentNullException(nameof(tagName)); + } + + /// + /// Gets the root portion of the string. + /// + /// + /// + /// The root portion of a given tag name is simply the beginning part of the tag name up to the first + /// member separator character '.' or '['. For Module defined tags, this includes the colon separator. + /// + /// + /// This value can be swapped out easily using to return a new with the + /// newly specified root tag name value. + /// + /// + /// + /// + public string Root => GetRoot(_tagName); + + /// + /// Gets the operand portion of the value. + /// + /// + /// The Operand of a tag name represents the part of the name after . This value will always be + /// the full tag name value without the leading root name. The operand will include the leading '.' character. + /// + /// + public string Operand => !Root.IsEmpty() ? _tagName.Remove(0, Root.Length) : string.Empty; + + /// + /// Gets the member path of the tag name value. + /// + /// + /// The Path of a tag name represents a name relative to . The value will always be the full tag name + /// without the leading root name. This is similar to , except that is also removes any + /// leading member separator character ('.'). + /// + /// + public string Path => Operand.StartsWith(".") ? Operand.Remove(0, 1) : Operand; + + /// + /// Gets the member name, or the last member of , of the tag name value. + /// + /// + /// The Member of a tag name represents the last member name of the string. This is the string after the final + /// member separator character. + /// + public string Member => GetMember(_tagName); + + /// + /// Returns a collection of string names representing each individual member of the full tag name value. + /// + /// + /// Each member of a tag name can be represented by a string, array bracket, or bit index value. + /// For example, MyTag[1].MemberName.5 has 4 members. + /// + //public IEnumerable Members => Regex.Matches(_tagName, MembersPattern).Select(m => m.Value); + public IEnumerable Members => GetMembers(_tagName); + + /// + /// A zero-based number representing the depth of the tag name value. In other words, the number of members + /// between this tag name and the root. + /// + /// + /// This value represents the number of members between the root tag and last member name (i.e. one less than + /// the number of members in the tag name). This is helpful for filtering tag descendents. Note that array + /// indices are also considered a member. For example, 'MyTag[1].Value' has a depth of 2 since '[1]' and 'Value' + /// are descendent member names of the root tag 'MyTag' member. + /// + public int Depth => GetDepth(_tagName); + + /// + /// Gets a value indicating whether the current value is empty. + /// + public bool IsEmpty => _tagName.IsEmpty(); + + /// + /// Gets a value indicating whether the current is a valid representation of a tag name. + /// + public bool IsQualified => IsQualifiedTagName(_tagName); + + /// + /// Determines if the provided string value is a valid tag name. + /// + /// The to test. + /// true if the value is a valid qualified tag name; Otherwise, false. + public static bool IsTag(string value) => IsQualifiedTagName(value); + + /// + /// Gets the static empty value. + /// + public static TagName Empty => new(string.Empty); + + /// + /// Concatenates two strings to produce a new value. This method will also insert the '.' + /// member separator character if not found at the beginning of right. + /// + /// The first or left side of the tag name to concatenate. + /// The second or right side of the tag name to concatenate. + /// A representing the combination of left and right. + /// + /// This method would be more performant than , assuming there are just + /// two strings to join together, as it does not iterate a collection and build a string with a string builder + /// class. This method simply joins to strings using a string format syntax. + /// + public static TagName Concat(string left, string right) + { + if (string.IsNullOrEmpty(right)) return left; + + if (right[0] == ArrayOpenSeparator || right[0] == MemberSeparator) + return new TagName($"{left}{right}"); + + return new TagName($"{left}{MemberSeparator}{right}"); + } + + /// + /// Combines a series of strings into a single value, inserting member separator + /// characters as needed. + /// + /// The series of strings that, in order, comprise the full tag name value. + /// A new value that represents the combination of all provided member names. + /// If any provided member does not match the member pattern format. + public static TagName Combine(params string[] members) => new(ConcatenateMembers(members.AsEnumerable())); + + /// + /// Combines a collection of member names into a single value. + /// + /// The collection of strings that represent the member names of the tag name value. + /// A new A new value that is the combination of all provided member names. + /// If a provided name does not match the member pattern format. + public static TagName Combine(IEnumerable members) => new(ConcatenateMembers(members)); + + /// + /// Converts a to a value. + /// + /// The value to convert. + /// A new value representing the value of the tag name. + public static implicit operator string(TagName tagName) => tagName is not null + ? tagName._tagName + : throw new ArgumentNullException(nameof(tagName)); + + /// + /// Converts a to a value. + /// + /// The value to convert. + /// A new value representing the value of the tag name. + public static implicit operator TagName(string tagName) => new(tagName); + + /// + public int CompareTo(TagName? other) + { + return ReferenceEquals(this, other) ? 0 + : ReferenceEquals(null, other) ? 1 + : StringComparer.OrdinalIgnoreCase.Compare(_tagName, other._tagName); + } + + /// + /// Determines if the provided tagName is contained within the current value. + /// + /// The tag name to evaluate as a sub path or contained tag name path. + /// true if tagName is contained within the current value; otherwise, false. + /// tagName is null. + public bool Contains(TagName tagName) + { + if (tagName is null) + throw new ArgumentNullException(nameof(tagName)); + + return _tagName.Contains(tagName); + } + + /// + /// Determines whether the specified objects are equal using the specified . + /// + /// A tag name object to compare. + /// A tag name object to compare. + /// The equality comparer to use for comparison. + /// true if the tag name are equal according too the provided comparer; otherwise, false. + /// Use the prebuilt class for several predefined comparer objects. + public static bool Equals(TagName first, TagName second, IEqualityComparer comparer) => + comparer.Equals(first, second); + + /// + public bool Equals(TagName? other) + { + if (ReferenceEquals(null, other)) return false; + return ReferenceEquals(this, other) || StringComparer.OrdinalIgnoreCase.Equals(_tagName, other._tagName); + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + TagName other => StringComparer.OrdinalIgnoreCase.Equals(_tagName, other._tagName), + string other => StringComparer.OrdinalIgnoreCase.Equals(_tagName, other), + _ => false + }; + } + + /// + public override int GetHashCode() => StringComparer.OrdinalIgnoreCase.GetHashCode(_tagName); + + /// + /// Returns a new tag name with the root value replaced with the provided string tag. + /// + /// The new root tag name value to replace. + /// A new with the new root tag value. + /// Note that this doesn't change the current tag name value, rather, returns a new object with the changed + /// value. This ensures the immutability of . + public TagName Rename(string tag) => Combine(tag, Operand); + + /// + public override string ToString() => _tagName; + + /// + /// Determines if the provided objects are equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are equal; otherwise, false. + public static bool operator ==(TagName? left, TagName? right) => Equals(left, right); + + /// + /// Determines if the provided objects are not equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are not equal; otherwise, false. + public static bool operator !=(TagName? left, TagName? right) => !Equals(left, right); + + /// + /// Gets the first member of the tag name, or the portion of the string up to the first/next member separator. + /// We are no longer using regex to make this as efficient as possible since there could realistically be millions + /// of tag names this can get called on. + /// + private static string GetRoot(string tagName) + { + if (tagName.IsEmpty() || tagName.StartsWith(MemberSeparator)) return string.Empty; + if (tagName.StartsWith(ArrayOpenSeparator)) + { + return tagName[..tagName.IndexOf(ArrayCloseSeparator)]; + } + + var index = tagName.IndexOfAny(new[] {MemberSeparator, ArrayOpenSeparator}); + return index > 0 ? tagName[..index] : tagName; + } + + /// + /// Gets the last member of the tag name, or the portion of the string from the end to the last member separator. + /// We are no longer using regex to make this as efficient as possible since there could realistically be millions + /// of tag names this can get called on. + /// + private static string GetMember(string tagName) + { + var index = tagName.LastIndexOfAny(new[] {MemberSeparator, ArrayOpenSeparator}); + var length = tagName.Length - index; + return index >= 0 ? tagName.Substring(index, length).TrimStart(MemberSeparator) : tagName; + } + + /// + /// Gets each member by iterating the tag name string. + /// We are no longer using regex to make this as efficient as possible since there could realistically be millions + /// of tag names this can get called on. + /// + private static IEnumerable GetMembers(string tagName) => + NormalizeDelimiter(tagName).Split(MemberSeparator, StringSplitOptions.RemoveEmptyEntries); + + /// + /// Gets the zero-based depth or number of members between this member and the root. + /// We are no longer using regex to make this as efficient as possible since there could realistically be millions + /// of tag names this can get called on. + /// + private static int GetDepth(string tagName) => tagName.Count(c => c is MemberSeparator or ArrayOpenSeparator); + + /// + /// Handles combining an enumerable containing string member names into a single value. + /// + private static string ConcatenateMembers(IEnumerable members) + { + var builder = new StringBuilder(); + + foreach (var member in members) + { + if (!(member.StartsWith(ArrayOpenSeparator) || member.StartsWith(MemberSeparator)) && builder.Length > 1) + builder.Append(MemberSeparator); + + builder.Append(member); + } + + return builder.ToString(); + } + + private static bool IsQualifiedTagName(string value) + { + if (value.IsEmpty()) return false; + + var normalized = NormalizeDelimiter(value); + var members = normalized.Split(MemberSeparator).ToList(); + + for (var i = 0; i < members.Count; i++) + { + switch (i) + { + case 0 when !IsValidRoot(members[i]): + case > 0 when members[i].StartsWith(ArrayOpenSeparator) && !IsValidIndex(members[i]): + case > 0 when char.IsLetter(members[i][0]) && !IsValidMember(members[i]): + return false; + } + + if (i == members.Count - 1 && char.IsDigit(members[i][0]) && !IsValidBit(members[i])) return false; + } + + return true; + + bool IsValidRoot(string member) => Regex.IsMatch(member, @"^[A-Za-a_][\w:]{0,39}$"); + + bool IsValidMember(string member) => Regex.IsMatch(member, @"^[A-Za-a_][\w]{0,39}$"); + + bool IsValidIndex(string member) => Regex.IsMatch(member, @"^\[[0-9]+(?:\,[0-9]+)?(?:\,[0-9]+)?\]$"); + + bool IsValidBit(string member) => + member.All(char.IsDigit) && int.TryParse(member, out var bit) && bit is >= 0 and <= 63; + } + + private static string NormalizeDelimiter(string value) + { + var builder = new StringBuilder(); + + foreach (var character in value) + { + if (character == ArrayOpenSeparator) + builder.Append(MemberSeparator); + builder.Append(character); + } + + return builder.ToString(); + } +} + +/// +/// A for the object. +/// +public class TagNameComparer : IEqualityComparer +{ + private TagNameComparer() + { + } + + /// + /// An that compares the full qualified value. + /// + public static TagNameComparer Qualified { get; } = new(); + + /// + /// An that compares the property of the + /// value. + /// + public static TagNameComparer Root { get; } = new RootTagNameComparer(); + + /// + /// An that compares the property of the + /// value. + /// + public static TagNameComparer Path { get; } = new PathTagNameComparer(); + + /// + /// An that compares the property of the + /// value. + /// + public static TagNameComparer Member { get; } = new MemberTagNameComparer(); + + /// + public virtual bool Equals(TagName? x, TagName? y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) return false; + return string.Equals(x.ToString(), y.ToString(), StringComparison.OrdinalIgnoreCase); + } + + /// + public virtual int GetHashCode(TagName obj) => obj.GetHashCode(); + + + private class RootTagNameComparer : TagNameComparer + { + public override bool Equals(TagName? x, TagName? y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) return false; + return string.Equals(x.Root, y.Root, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode(TagName obj) => StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Root); + } + + private class PathTagNameComparer : TagNameComparer + { + public override bool Equals(TagName? x, TagName? y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) return false; + return string.Equals(x.Path, y.Path, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode(TagName obj) => StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Path); + } + + private class MemberTagNameComparer : TagNameComparer + { + public override bool Equals(TagName? x, TagName? y) + { + if (ReferenceEquals(x, y)) return true; + if (ReferenceEquals(x, null) || ReferenceEquals(y, null)) return false; + return string.Equals(x.Member, y.Member, StringComparison.OrdinalIgnoreCase); + } + + public override int GetHashCode(TagName obj) => StringComparer.OrdinalIgnoreCase.GetHashCode(obj.Member); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Common/TaskPriority.cs b/src/L5Sharp/Common/TaskPriority.cs new file mode 100644 index 00000000..8a904cc1 --- /dev/null +++ b/src/L5Sharp/Common/TaskPriority.cs @@ -0,0 +1,81 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// A configurable property of a that controls the order in which the Logix Controller +/// will scan the given Task. +/// +/// +/// is a simple byte value that must be between 1 and 15. +/// Attempting to set the to a value outside that range will result in an +/// . +/// This parameter will control the scan order of task components as related to other tasks. +/// +public readonly struct TaskPriority : IEquatable +{ + private readonly byte _priority; + + /// + /// Creates a new instance of with the provided value. + /// + /// The value of the priority. Must be a value between 1 and 15. + /// priority is less than 1 -or- greater than 15. + public TaskPriority(byte priority) + { + if (priority is < 1 or > 15) + throw new ArgumentOutOfRangeException(nameof(priority), "Priority must be value between 1 and 15"); + + _priority = priority; + } + + /// + /// Converts a to a . + /// + /// The value to convert. + /// A value. + public static implicit operator byte(TaskPriority priority) => priority._priority; + + /// + /// Converts a to a . + /// + /// The value to convert. + /// A value. + public static implicit operator TaskPriority(byte priority) => new(priority); + + /// + /// Parses a string value into a . + /// + /// The string to parse. + /// A value if the parse was successful; otherwise; the default value. + public static TaskPriority Parse(string str) => + byte.TryParse(str, out var result) ? new TaskPriority(result) : default; + + /// + public bool Equals(TaskPriority other) => _priority == other._priority; + + /// + public override string ToString() => _priority.ToString(); + + /// + public override bool Equals(object? obj) => obj is TaskPriority other && Equals(other); + + /// + public override int GetHashCode() => _priority.GetHashCode(); + + /// + /// Determines if the provided objects are equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are equal; otherwise, false. + public static bool operator ==(TaskPriority left, TaskPriority right) => Equals(left, right); + + /// + /// Determines if the provided objects are not equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are not equal; otherwise, false. + public static bool operator !=(TaskPriority left, TaskPriority right) => !Equals(left, right); +} \ No newline at end of file diff --git a/src/L5Sharp/Common/Vendor.cs b/src/L5Sharp/Common/Vendor.cs new file mode 100644 index 00000000..80e55bc8 --- /dev/null +++ b/src/L5Sharp/Common/Vendor.cs @@ -0,0 +1,104 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// An entity that represents the vendor of a given . +/// +/// +/// This object is a simple entity type wrapper that groups the vendor id and name. +/// Vendor's are defined by Rockwell and assigned unique Id and name. +/// Use as it is the most common vendor for compatible devices. +/// +public class Vendor +{ + /// + /// Creates a new value with the provided id and name. + /// + /// The unique Id of the Vendor. + /// The name of the Vendor. Will default to empty if not provided. + public Vendor(ushort id, string? name = null) + { + Id = id; + Name = name ?? string.Empty; + } + + /// + /// Gets the value that uniquely identifies the . + /// + /// + /// This value is exported in the L5X and is used for Vendor name lookups. + /// + public ushort Id { get; } + + /// + /// Gets the value that represents the name. + /// + public string Name { get; } + + /// + /// Represents an Unknown with no id or name. + /// + public static Vendor Unknown => new(0); + + /// + /// Gets the Rockwell Automation Vendor instance (id=1). + /// + public static Vendor Rockwell => new(1, "Rockwell Automation/Allen-Bradley"); + + /// + /// Converts a object to a that represents the Id. + /// + /// The object to convert. + /// A representing the value of the Vendor Id. + public static implicit operator ushort(Vendor vendor) => vendor.Id; + + /// + /// Converts a value to a object that represents the Id. + /// + /// The value to convert. + /// A with the Id of the converted value. + public static implicit operator Vendor(ushort vendorId) => new(vendorId); + + /// + /// Creates a new using the provided vendor Id. + /// + /// The unique valid that identifies the Vendor. + /// A new object with the provided Id. + public static Vendor Parse(string vendorId) => ushort.TryParse(vendorId, out var id) ? new Vendor(id) : Unknown; + + /// + public override string ToString() => Name; + + /// + public override bool Equals(object? obj) + { + return obj switch + { + Vendor other => Id.Equals(other.Id), + int other => ((int)Id).Equals(other), + ushort other => Id.Equals(other), + ValueType other => Id.Equals(other), + _ => false + }; + } + + /// + public override int GetHashCode() => Id.GetHashCode(); + + /// + /// Determines if the provided objects are equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are equal; otherwise, false. + public static bool operator ==(Vendor? left, Vendor? right) => Equals(left, right); + + /// + /// Determines if the provided objects are not equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are not equal; otherwise, false. + public static bool operator !=(Vendor? left, Vendor? right) => !Equals(left, right); +} \ No newline at end of file diff --git a/src/L5Sharp/Common/Watchdog.cs b/src/L5Sharp/Common/Watchdog.cs new file mode 100644 index 00000000..0a602ab3 --- /dev/null +++ b/src/L5Sharp/Common/Watchdog.cs @@ -0,0 +1,90 @@ +using System; +using System.Globalization; + +namespace L5Sharp.Core; + +/// +/// A configurable property of a that specified how long a task can rung before triggering a major fault. +/// +/// +/// is a simple float value that must be between 0.1 and 2,000,000.0 ms. +/// Attempting to set the to a value outside that range will result in an +/// . +/// This parameter will control how long a task can rung before triggering a major fault. +/// +public readonly struct Watchdog : IEquatable +{ + private readonly float _watchdog; + + /// + /// Creates a new instance of Watchdog with the specified value. + /// + /// The value of the watchdog in milliseconds. + /// Must be a value between 0.1 and 2M. + /// Throw when the provided value is not within the + /// specified range. + public Watchdog(float watchdog) + { + if (watchdog is < 0.1f or > 2000000.0f) + throw new ArgumentOutOfRangeException(nameof(watchdog), + "Watchdog must be value between 0.1 and 2,000,000.0 ms"); + + _watchdog = watchdog; + } + + /// + /// + /// + /// A new Watchdog value. + public static Watchdog Default() => new(500); + + /// + /// Converts a to a . + /// + /// The value to convert. + /// A value. + public static implicit operator float(Watchdog watchdog) => watchdog._watchdog; + + /// + /// Converts a to a . + /// + /// The value to convert. + /// A value. + public static implicit operator Watchdog(float watchdog) => new(watchdog); + + /// + /// Parses a string value into a . + /// + /// The string to parse. + /// A value if the parse was successful; otherwise; the default value. + public static Watchdog Parse(string str) => + float.TryParse(str, out var result) ? new Watchdog(result) : default; + + /// + public override string ToString() => _watchdog.ToString(CultureInfo.CurrentCulture); + + /// + public bool Equals(Watchdog other) => _watchdog.Equals(other._watchdog); + + /// + public override bool Equals(object? obj) => obj is Watchdog other && Equals(other); + + /// + public override int GetHashCode() => _watchdog.GetHashCode(); + + /// + /// Determines if the provided objects are equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are equal; otherwise, false. + public static bool operator ==(Watchdog left, Watchdog right) => Equals(left, right); + + /// + /// Determines if the provided objects are not equal. + /// + /// An object to compare. + /// An object to compare. + /// true if the provided objects are not equal; otherwise, false. + public static bool operator !=(Watchdog left, Watchdog right) => !Equals(left, right); +} \ No newline at end of file diff --git a/src/L5Sharp/Components/AddOnInstruction.cs b/src/L5Sharp/Components/AddOnInstruction.cs new file mode 100644 index 00000000..7768b1bc --- /dev/null +++ b/src/L5Sharp/Components/AddOnInstruction.cs @@ -0,0 +1,269 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix AddOnInstruction component. Contains the properties that comprise the L5X +/// AddOnInstructionDefinition element. +/// +/// +[L5XType(L5XName.AddOnInstructionDefinition)] +public class AddOnInstruction : LogixComponent +{ + /// + /// Creates a new with default values. + /// + public AddOnInstruction() : base(new XElement(L5XName.AddOnInstructionDefinition)) + { + Revision = new Revision(); + ExecutePreScan = false; + ExecutePostScan = false; + ExecuteEnableInFalse = false; + CreatedDate = DateTime.Now; + CreatedBy = Environment.UserName; + EditedDate = DateTime.Now; + EditedBy = Environment.UserName; + IsEncrypted = false; + Parameters = new LogixContainer(); + LocalTags = new LogixContainer(L5XName.LocalTags, L5XName.LocalTag); + Routines = new LogixContainer(); + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public AddOnInstruction(XElement element) : base(element) + { + } + + /// + /// The revision of the instruction. + /// + /// A representing the version of the instruction. + /// + /// Specify the revision of the Add-On Instruction, in the form of MajorRevision.MinorRevision. + /// Each revision number can be 1...65,535. + /// If there is no period, the number is treated as a major revision only + /// + public Revision? Revision + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Additional text indicating or identifying the revision of the instruction. + /// + /// A containing text of the revision extension. + public string? RevisionExtension + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Additional text describing release information or changes with the current revision(s). + /// + /// A containing the text of the revision note. + public string? RevisionNote + { + get => GetProperty(); + set => SetProperty(value); + } + + /// + /// The vendor or creator of the instruction. + /// + /// A value representing the name of the vendor. + public string? Vendor + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Indicates that the instruction has and executes a pre scan routine. + /// + /// true if the instruction executes a pre scan routine; otherwise, false. + public bool ExecutePreScan + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Indicates that the instruction has and executes a post scan routine. + /// + /// true if the instruction executes a post scan routine; otherwise, false. + public bool ExecutePostScan + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Indicates that the instruction has and executes a enable in false routine. + /// + /// A - true if the instruction executes a enable in false routine; otherwise, false. + public bool ExecuteEnableInFalse + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The date and time that the instruction was created. + /// + /// A representing the creation date and time. + public DateTime? CreatedDate + { + get => GetDateTime(); + set => SetDateTime(value); + } + + /// + /// The name of the user that created the instruction. + /// + /// A representing the name of the user. + public string? CreatedBy + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The date and time that the instruction was last edited. + /// + /// A representing the edit date and time. + public DateTime? EditedDate + { + get => GetDateTime(); + set => SetDateTime(value); + } + + /// + /// The name of the user that last edited the instruction. + /// + /// A representing the name of the user. + public string? EditedBy + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the revision of the application last used to edit the Add-On Instruction. + /// The default is the currently open version of the application. + /// + /// A representing the version of the instruction. + public Revision? SoftwareRevision + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The help text specific to the Add-On Instruction. + /// + /// A containing the help text. + public string? AdditionalHelpText + { + get => GetProperty(); + set => SetProperty(value); + } + + /// + /// Indicates whether the Add-On Instruction is protected with license-based Source Protection and locked + /// + /// true if the instruction is encrypted; otherwise, false. + public bool IsEncrypted + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The collection of that make up the structure of the instruction component. + /// + public LogixContainer Parameters + { + get => GetContainer(); + set => SetContainer(value); + } + + /// + /// The collection of local objects used within the AoiBlock logic. + /// + public LogixContainer LocalTags + { + get => GetContainer(); + set => SetContainer(value); + } + + /// + /// The collection of local objects that contain the logic for the instruction. + /// + public LogixContainer Routines + { + get => GetContainer(); + set => SetContainer(value); + } + + #region Extensions + + /// + /// Returns the AoiBlock instruction logic with the parameters tag names replaced with the argument tag names of the + /// provided instruction instance. + /// + /// The instruction instance for which to generate the underlying logic. + /// + /// A containing representing all the instruction's + /// logic, with each instruction parameter tag name replaced with the arguments from the provided text. + /// + /// + /// This is helpful when trying to perform deep analysis on logic. By "flattening" the logic we can + /// reason or evaluate it as if it was written in line. Currently only supports + /// content or code type. + /// + public IEnumerable LogicFor(Instruction instruction) + { + if (instruction is null) + throw new ArgumentNullException(nameof(instruction)); + + // All instructions primary logic is contained in the routine named 'Logic' + var logic = Routines.FirstOrDefault(r => r.Name == "Logic"); + + var rungs = logic?.Content(); + if (rungs is null) return Enumerable.Empty(); + + //Skip first operand as it is always the AoiBlock tag, which does not have corresponding parameter within the logic. + var arguments = instruction.Arguments.Select(a => a.ToString()).Skip(1).ToList(); + + //Only required parameters are part of the instruction signature + var parameters = Parameters.Where(p => p.Required is true).Select(p => p.Name).ToList(); + + //Generate a mapping of the provided instructions arguments to instruction parameters. + var mapping = arguments.Zip(parameters, (a, p) => new {Argument = a, Parameter = p}).ToList(); + + //Replace all parameter names with argument names in the instruction logic text, and return the results. + return rungs.Select(r => r.Text) + .Select(t => mapping.Aggregate(t, (current, pair) => + { + if (!pair.Argument.IsTag()) return current; + var replace = $@"(?<=[^.]){pair.Parameter}\b"; + return Regex.Replace(current, replace, pair.Argument.ToString()); + })) + .ToList(); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Components/Controller.cs b/src/L5Sharp/Components/Controller.cs new file mode 100644 index 00000000..86d51b24 --- /dev/null +++ b/src/L5Sharp/Components/Controller.cs @@ -0,0 +1,328 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix Controller component. Contains the properties that comprise the L5X Controller element. +/// +/// +/// A controller component may or may not contains various properties depending on if the exported L5X file +/// was a full project file or just a component export file. This is indicated in the by the property +/// ContainsContext, which if true, means the controller element exists simply to contain other components that +/// are needed by the TargetName for successful re-imports of the content, and therefore will typically only have +/// a name, revision, and processor type. +/// +/// Observe these guidelines when defining a controller:
+/// • All declarations must be ordered in the prescribed syntax.
+/// • The maximum number of tasks vary by the controller type.
+/// • There can be only one continuous task.
+/// • Programs can be scheduled under only one task.
+/// • There can be a maximum of 1000 programs under a task.
+/// • Scheduled programs must be defined.
+///
+///
+/// +public class Controller : LogixComponent +{ + private const string DateTimeFormat = "ddd MMM d HH:mm:ss yyyy"; + + /// + /// Creates a new with default values. + /// + public Controller() : base(new XElement(L5XName.Controller)) + { + Use = Use.Context; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Controller(XElement element) : base(element) + { + } + + /// + /// The catalog number representing the processor of the controller component. + /// + /// A representing the alpha numeric catalog number. + public string? ProcessorType + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Percentage of available CPU time (10...90) that is assigned to communication. + /// + public string? TimeSlice + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify whether to share an unused or not. + /// + public bool? ShareUnusedTimeSlice + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Name of the program to be executed upon restart after a power loss. + /// + /// A representing the name of the program. + public string? PowerLossProgram + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Name of the program to be executed when a major fault occurs. + /// + /// A representing the name of the program. + public string? MajorFaultProgram + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the devices in the communication path. The communication path ends with the + /// controller (\Backplane\1). This is exported only if you select manual configuration of the + /// communication path in RSLinx® software. + /// + public string? CommPath + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the type of communication driver. This is the name of the selected driver in + /// RSLinx® software. This is exported only if you select manual configuration of the + /// communication driver in RSLinx® software. + /// + public string? CommDriver + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The revision or hardware version of the controller. + /// + /// A value representing the major/minor version of the controller + public Revision? Revision + { + get + { + var major = Element.Attribute(L5XName.MajorRev)?.Value; + var minor = Element.Attribute(L5XName.MinorRev)?.Value; + return major is not null && minor is not null ? new Revision($"{major}.{minor}") : default; + } + set + { + Element.SetAttributeValue(L5XName.MajorRev, value?.Major); + Element.SetAttributeValue(L5XName.MinorRev, value?.Minor); + } + } + + /// + /// The date/time the current project was created. + /// + /// A representing the date and time of creation. + public DateTime ProjectCreationDate + { + get => GetDateTime(DateTimeFormat) ?? default; + set => SetDateTime(value, DateTimeFormat); + } + + /// + /// The date/time the current project was last modified. + /// + /// A representing the date and time of modification. + public DateTime LastModifiedDate + { + get => GetDateTime(DateTimeFormat) ?? default; + set => SetDateTime(value, DateTimeFormat); + } + + /// + /// Specify whether the SFC executes the current active steps before returning control + /// (CurrentActive) or whether the SFC executes all threads until reaching a false transition + /// (UntilFalse). + /// + public SFCExecutionControl? SFCExecutionControl + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify whether the SFC restarts at the most recently executed step (MostRecent) or at the + /// initial step (InitialStep). + /// + public SFCRestartPosition? SFCRestartPosition + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify how the SFC manages its state on a last scan. Select AutomaticReset, + /// ProgrammaticReset, or DontScan. + /// + public SFCLastScan? SFCLastScan + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the serial number of the controller. If a serial number is specified, it is + /// imported into the project regardless of the MatchProjectToController setting. Type a 32-bit, + /// hexadecimal number with the 16# prefix, such as 16#0012_E2BC. + /// + // ReSharper disable once InconsistentNaming to match logix name. + public string? ProjectSN + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify whether to be sure that the project matches the controller or not. + /// + public bool? MatchProjectToController + { + get => GetValue() is not null ? GetValue() == "Yes" : default; + set => SetValue(value is true ? "Yes" : "No"); + } + + /// + /// Specify whether to inhibit the automatic update of controller firmware. + /// + public bool? InhibitAutomaticFirmwareUpdate + { + get => GetValue() is not null ? GetValue() == 1 : default; + set => SetValue(value is true ? 1 : 0); + } + + /// + /// Specify the current project language for a project documentation project. + /// + public string? CurrentProjectLanguage + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the default project language for a project document at on project. + /// + public string? DefaultProjectLanguage + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the controller project language for a project document at on project. + /// + public string? ControllerLanguage + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify whether the consumed tags in the controller can connect to the producer with an + /// RPI provided by the producer (true or false) + /// + public bool? CanUseRPIFromProducer + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Indicates the pass through state of documentation for the project. + /// + /// A representing the configured value. + public PassThroughOption? PassThroughConfiguration + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Indicates the download project documentation configuration setting of the project. + /// + public bool? DownloadProjectDocumentationAndExtendedProperties + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Indicates the download custom properties configuration setting of the project. + /// Only applies if the project is already configured to DownloadProjectDocumentation + /// + /// + /// Rockwell Automation® recommends setting this attribute to false only during startup + /// testing to improve download speeds during commissioning testing. It should be set to true + /// for the normal operating state of a system. + /// + public bool? DownloadCustomProperties + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The EtherNet/IP™ Mode describes the relationship between the CIP™ EtherNet/IP™ ports + /// and the physical Ethernet ports. The CIP™ EtherNet/IP™ port can be configured as one of two modes: + /// Dual-IP, Linear/DLR + /// + public string? EtherNetIPMode + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The object that specifies the redundancy configuration of the controller. + /// + public RedundancyInfo? RedundancyInfo + { + get => GetComplex(); + set => SetComplex(value); + } + + /// + /// The object that specifies the security configuration of the controller. + /// + public Security? Security + { + get => GetComplex(); + set => SetComplex(value); + } + + /// + /// The object that specifies the safety configuration of the controller. + /// + public SafetyInfo? SafetyInfo + { + get => GetComplex(); + set => SetComplex(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Components/DataType.cs b/src/L5Sharp/Components/DataType.cs new file mode 100644 index 00000000..a5db6112 --- /dev/null +++ b/src/L5Sharp/Components/DataType.cs @@ -0,0 +1,117 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix DataType component. Contains the properties that comprise the L5X DataType element. +/// +/// +/// Observe these guidelines when defining a DataType:
+/// • DataTypes must be defined first within the controller body.
+/// • DataTypes can be defined out of order. For example, if Type1 depends on Type2, Type2 can be defined first.
+/// • DataTypes can be unverified. For example if Type1 depends on Type2 and Type2 is never defined, then Type1
+/// will be accessible as an unverified type. Type2 will be typeless type. Tags of Type1 may be created but not of Type2.
+/// • Datatype members can be arrays but only one dimension is allowed.
+/// • These DataTypes cannot be used in a user-defined datatype:
+/// • ALARM_ANALOG
+/// • ALARM_DIGITAL
+/// • AXIS types
+/// • COORDINATE_SYSTEM
+/// • MOTION_GROUP
+/// • MESSAGE
+/// • MODULE
+/// • If one user-defined datatype references a second user-defined datatype defined in the file, the second
+/// user-defined datatype appears before the first one in the import/export file.
+///
+/// +public class DataType : LogixComponent +{ + /// + /// Creates a new with default values. + /// + public DataType() + { + Family = DataTypeFamily.None; + Class = DataTypeClass.User; + Members = new LogixContainer(); + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public DataType(XElement element) : base(element) + { + } + + /// + /// Creates a new initialized with the provided name. + /// + /// The name of the data type component. + /// name is null. + public DataType(string name) : this() + { + if (name is null) throw new ArgumentNullException(nameof(name)); + SetValue(L5XName.Name, name); + } + + /// + /// The family of the DataType component. + /// + /// + /// A option indicating the family for which the current data type belongs. + /// This is just string for string types and none for all others. + /// + public DataTypeFamily? Family + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The class of the DataType component. + /// + /// + /// A option indicating the class for which the current data type belongs. + /// L5X files will only ever contain class types. + /// + public DataTypeClass? Class + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The collection of that make up the structure of the DataType component. + /// + public LogixContainer Members + { + get => GetContainer(); + set => SetContainer(value); + } + + /// + public override IEnumerable Dependencies() + { + if (L5X is null) return Enumerable.Empty(); + + var dependencies = new List(); + + foreach (var member in Members) + { + var dataType = L5X.Find(member.DataType); + if (dataType is null) continue; + dependencies.Add(dataType); + dependencies.AddRange(dataType.Dependencies()); + } + + return dependencies.Distinct(); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Components/Module.cs b/src/L5Sharp/Components/Module.cs new file mode 100644 index 00000000..b9095dae --- /dev/null +++ b/src/L5Sharp/Components/Module.cs @@ -0,0 +1,438 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix Module component. Contains the properties that comprise the L5X Module element. +/// +/// +public class Module : LogixComponent +{ + /// + public Module() + { + CatalogNumber = string.Empty; + Vendor = Vendor.Unknown; + ProductType = ProductType.Unknown; + ProductCode = default; + Revision = new Revision(); + ParentModule = string.Empty; + ParentModPortId = default; + Inhibited = default; + MajorFault = default; + SafetyEnabled = default; + Keying = ElectronicKeying.CompatibleModule; + Ports = new LogixContainer(); + Communications = new Communications(); + } + + /// + public Module(XElement element) : base(element) + { + } + + /// + /// + /// Module components don't all have a name attribute (e.g. VFD peripheral modules). For this reason, + /// the name property is overriden to not be a required field for this component type. If the name is not found, + /// this property returns an empty string. + /// + public override string Name + { + get => GetValue() ?? string.Empty; + set => SetValue(value); + } + + /// + /// Specify the catalog number that uniquely identifies the module. This is a rockwell defined convention, + /// and represents the identity of the module type. + /// + /// A value containing the catalog number. + public string? CatalogNumber + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The vendor or manufacturer of the module. + /// + /// A entity that contains the id and name of the vendor. + /// + /// All modules have a vendor representing the manufacturer of the module. + /// + public Vendor? Vendor + { + get => GetValue(); + set => SetValue(value?.Id); + } + + /// + /// The product type of the module, representing a category of the module. + /// + /// + /// All modules have a product type representing the product category of the module. + /// + public ProductType? ProductType + { + get => GetValue(); + set => SetValue(value?.Id); + } + + /// + /// The unique product code value of the module. + /// + /// + /// This is a unique value that identifies the module and is assigned by Logix. + /// + public ushort ProductCode + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The revision number or hardware version of the module. + /// + /// A object representing the major and minor version. + /// + /// All modules must have a specified revision number. + /// + public Revision? Revision + { + get + { + var major = Element.Attribute(L5XName.Major)?.Value; + var minor = Element.Attribute(L5XName.Minor)?.Value; + return major is not null && minor is not null ? new Revision($"{major}.{minor}") : default; + } + set + { + Element.SetAttributeValue(L5XName.Major, value?.Major); + Element.SetAttributeValue(L5XName.Minor, value?.Minor); + } + } + + /// + /// The name of the parent module, or module that the current module is connected to upstream. + /// This specifies how the module is connected within the module tree. + /// + /// A representing the parent module name. Default is an empty string. + public string? ParentModule + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The port id of the parent module that the current module is connected to. + /// This specified how the module is connected within the module tree. + /// + /// A representing the id of the parent port. Default is zero. + public int ParentModPortId + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// An indication of whether the module is inhibited or disabled. + /// + public bool Inhibited + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// An indication of whether the module the module will cause a major fault when faulted. + /// + public bool MajorFault + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// An indication of whether whether the module has safety features enabled. + /// + public bool SafetyEnabled + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The electronic keying mode of the module. + /// + /// A enum value representing the mode. + public ElectronicKeying? Keying + { + get => Element.Element(L5XName.EKey)?.Attribute(L5XName.State)?.Value.Parse(); + set + { + if (value is null) + { + Element.Element(L5XName.EKey)?.Remove(); + return; + } + + if (Element.Element(L5XName.EKey) is null) + { + Element.Add(new XElement(L5XName.EKey, new XAttribute(L5XName.State, value))); + return; + } + + Element.Element(L5XName.EKey)!.SetAttributeValue(L5XName.State, value); + } + } + + /// + /// A collection of elements that define the module's connection within the module tree. + /// + /// A of objects. + /// + /// Ports define how a module's peripherals are connected to other module's, forming the network or tree of + /// devices used to communicated with a controller and field equipment. Ports must have a unique id, a type, + /// and address. + /// + public LogixContainer Ports + { + get => GetContainer(); + set => SetContainer(value); + } + + /// + /// + /// + public Communications? Communications + { + get => GetComplex(); + set => SetComplex(value); + } + + #region Extensions + + /// + /// Gets the slot number of the current module if one exists. If the module does not have an slot, returns null. + /// + /// An representing the slot number of the module. + /// + /// This property is not directly part of the L5X structure, but is a helper to make accessing the slot number simple. + /// This property looks for an upstream with a valid slot byte number. + /// + public byte? Slot => Ports.FirstOrDefault(p => p.Upstream && p.Address.IsSlot)?.Address.ToSlot(); + + /// + /// Gets the IP address of the current module if one exists. If the module does not have an IP, returns null. + /// + /// An representing the IP of the module. + /// + /// This property is not directly part of the L5X structure, but is a helper to make accessing the IP simple. + /// This property looks for an Ethernet type with a valid IPv4 address. + /// + public IPAddress? IP => + Ports.FirstOrDefault(p => p is {Type: "Ethernet", Address.IsIPv4: true})?.Address.ToIPAddress(); + + /// + /// Gets the parent module of this module component defined in the current L5X document. + /// + /// A representing the parent of this module if it exists; otherwise, null. + /// + /// The L5X structure serializes modules in a flat list with each element having properties (ParentModule, ParentModPortId) + /// defining the parent/child IO tree relationship. It would be nice to navigate this hierarchy programatically, + /// hence the reason for this extension method. Of course, this requires the module is attached to the L5X content. + /// In-memory created modules will inherently return an empty collection, as there is no L5X structure to introspect. + /// + public Module? Parent + { + get + { + var parent = Element.Parent?.Elements().FirstOrDefault(m => m.LogixName() == ParentModule); + return parent is not null ? new Module(parent) : default; + } + } + + /// + /// Gets the child modules of this module component defined in the current L5X content. + /// + /// + /// A of components that have the ParentModule property + /// configured as the name of this module. + /// + /// + /// The L5X structure serializes modules in a flat list with each element having properties (ParentModule, ParentModPortId) + /// defining the parent/child IO tree relationship. It would be nice to navigate this hierarchy programatically, + /// hence the reason for this extension method. Of course, this requires the module is attached to the L5X content. + /// In-memory created modules will inherently return an empty collection, as there is no L5X structure to introspect. + /// + public IEnumerable Modules + { + get + { + return Element.Parent?.Elements() + .Where(m => m.Attribute(L5XName.ParentModule)?.Value == Name) + .Select(e => new Module(e)) + ?? Enumerable.Empty(); + } + } + + /// + /// Returns a collection of all non-null objects for the current Module, including all + /// config, input, and output tags. + /// + /// An containing the base tags for the Module. + /// + /// Since module tags are nested within different layers of complex types, it can be difficult to just + /// get a single list of all module tags. This extension makes that easy by sifting through the object and returning + /// a flat list containing all non-null config, input, and output tags defined for the component. + /// + public IEnumerable Tags + { + get + { + var tags = new List(); + + if (Communications is null) return tags; + + if (Communications.ConfigTag is not null) + tags.Add(Communications.ConfigTag); + + foreach (var connection in Communications.Connections) + { + if (connection.InputTag is not null) + tags.Add(connection.InputTag); + + if (connection.OutputTag is not null) + tags.Add(connection.OutputTag); + } + + return tags; + } + } + + /// + /// Returns the module's config tag object contained in the communications element. + /// + /// A containing the module's config tag data. + /// This is a simple helper to make accessing the module config data more concise. + public Tag? Config => Communications?.ConfigTag; + + /// + /// Adds a child module to the current module object by updating the parent module properties, configuring the + /// child module upstream port, and adding the component to the underlying L5X content. + /// + /// The module to add. + /// The optional (slot or IP) of the module to add. + /// parent does not have a downstream port for which to connect + /// child modules. + /// + /// This extension gives us an easy way to add modules hierarchically to the underlying L5X content. + /// If the parent module is attached to a L5X content file, this will add child module. Otherwise, this method only + /// + /// + public void Add(Module child, Address? address = default) + { + var parentPort = Ports.FirstOrDefault(p => p.Upstream is false); + + if (parentPort is null) + throw new InvalidOperationException( + $"The module '{Name}' does not have a port for downstream module connections."); + + if (parentPort.Type == "Ethernet" && address is null) address = Address.DefaultIP(); + if (parentPort.Address.IsSlot && IsAttached && address is null) address = NextSlot(); + address ??= Address.DefaultSlot(); + + var childPort = new Port {Id = 1, Type = parentPort.Type, Address = address, Upstream = true}; + + child.ParentModule = Name; + child.ParentModPortId = parentPort.Id; + child.Ports.Add(childPort); + + var container = Element.Parent; + + if (container is null) + throw new InvalidOperationException($"The module '{Name}' is not attached to and L5X content file."); + + container.Add(child.Serialize()); + } + + /// + /// Creates a new with the provided name and catalog number. + /// + /// The name of the module + /// The catalog number to lookup a catalog entry for. + /// + /// A new object initialized with data return by the catalog service. + /// The module catalog service could not load the installed catalog + /// database file -or- catalog number does not exist in the catalog database. + /// catalogNumber is null or empty. + /// This factory method uses the service to lookup info for the specified + /// catalog number. If RSLogix is not installed on the current environment, this will throw an exception. + public static Module Add(string name, string catalogNumber, Address? address = null) + { + var catalog = new ModuleCatalog(); + var entry = catalog.Lookup(catalogNumber); + + return new Module + { + Name = name, + CatalogNumber = entry.CatalogNumber, + Revision = entry.Revisions.Max(), + Vendor = entry.Vendor, + ProductType = entry.ProductType, + ProductCode = entry.ProductCode, + Ports = new LogixContainer( + entry.Ports.Select(p => new Port + { + Id = p.Number, + Type = p.Type, + Address = p.Type == "Ethernet" ? Address.DefaultIP() : Address.DefaultSlot(), + Upstream = !p.DownstreamOnly + }).ToList()), + Description = entry.Description + }; + } + + /// + /// Gets the next largest slot number for the current module by introspecting the slot numbers of all other + /// child modules of this parent module. + /// + private Address NextSlot() + { + var children = Element.Parent?.Elements() + .Where(m => m.Attribute(L5XName.ParentModule)?.Value == Name); + + var next = children?.Select(c => c.Descendants(L5XName.Port) + .FirstOrDefault(p => p.Attribute(L5XName.Upstream)?.Value.Parse() is true && + byte.TryParse(p.Attribute(L5XName.Address)?.Value, out _)) + ?.Attribute(L5XName.Address)?.Value.Parse()) + .OrderByDescending(b => b) + .FirstOrDefault(); + + return next.HasValue ? Address.Slot(next.Value) : Address.DefaultSlot(); + } + + #endregion +} + +/// +/// Extensions methods for a single or collection of components. +/// +public static class ModuleExtensions +{ + /// + /// Gets the Local module or module that represents the controller of the module collection. + /// + /// A collection of modules. + /// A single which is named local if found; Otherwise, null. + /// This is a helper to concisely get the controller or root local module from the modules collection. + public static Module? Local(this IEnumerable modules) => modules.SingleOrDefault(m => m.Name == "Local"); +} \ No newline at end of file diff --git a/src/L5Sharp/Components/Program.cs b/src/L5Sharp/Components/Program.cs new file mode 100644 index 00000000..3688428d --- /dev/null +++ b/src/L5Sharp/Components/Program.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix Program component. Contains the properties that comprise the L5X Program element. +/// +/// +public class Program : LogixComponent +{ + /// + /// Creates a new with default values. + /// + public Program() + { + Type = ProgramType.Normal; + TestEdits = default; + Disabled = default; + MainRoutineName = default; + FaultRoutineName = default; + UseAsFolder = default; + Tags = new LogixContainer(); + Routines = new LogixContainer(); + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Program(XElement element) : base(element) + { + } + + /// + /// Gets the type of the program (Normal, Equipment Phase). + /// + /// A enum representing the type of the program. + public ProgramType Type + { + get => GetValue() ?? ProgramType.Normal; + set => SetValue(value); + } + + /// + /// The value indicating whether the program has current test edits pending. + /// + /// >A ; trueif the program has test edits; otherwise false. + public bool TestEdits + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The value indicating whether the program is disabled (or inhibited). + /// + /// A ; true if the program is disabled; otherwise false. + public bool Disabled + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The name of the routine that serves as the entry point for the program (i.e. main routine). + /// + /// A representing the name of the main routine for the program. + public string? MainRoutineName + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The name of the routine that serves as the fault routine for the program. + /// + /// A representing the name of the fault routine for the program. + public string? FaultRoutineName + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// A flag indicating whether the program is used as a folder or container for other programs, + /// as opposed to a container of tags and logix. + /// + /// A ; true if the program is a folder; otherwise, false. + public bool UseAsFolder + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The collection of objects for the program component. + /// + public LogixContainer Tags + { + get => GetContainer(); + set => SetContainer(value); + } + + /// + /// The collection of objects for the program component. + /// + public LogixContainer Routines + { + get => GetContainer(); + set => SetContainer(value); + } + + /// + /// The collection of program names that are children of this component. + /// This defines the structure of the program tree in the logical organizer. + /// + /// A containing the string program names. + /// + /// + /// This member just returns the read only list of child program names. To modify the list, use the local + /// and methods. This list is what is serialized and defines the collection + /// of child programs for a given program in the logical organizer. + /// + /// + /// To get access to the actual child + /// programs, use , which is a built in helper that uses the parent L5X + /// to retrieve the child program components. + /// + /// + public IEnumerable Children => + Element.Descendants(L5XName.ChildProgram).Select(e => e.LogixName()); + + /// + /// Gets a collection of Program components that are children of this component. + /// + /// A of component elements. + /// + /// This is a helper to retrieve the other program component objects as children of this Program. + /// This allows the caller to travers down the logical hierarchy of programs. This requires an attached L5X as + /// it reaches back up the document tree and back down to down to find the child programs. If this component is not + /// attached to an L5X or as no configured, then this will return an empty collection. + /// + public IEnumerable Programs => + L5X?.Programs.Where(p => Children.Any(c => c == p.Name)) ?? Enumerable.Empty(); + + /// + /// Finds the in which this is scheduled. + /// + /// If this component is attached and is scheduled to a defined Task, then the + /// component instance, Otherwise, null. + /// + /// This is a helper for retrieving the parent Task for this program. + /// This requires an attached L5X as it traverses the L5X document tree to find the target component(s). + /// + public Task? Task => L5X?.Tasks.FirstOrDefault(t => t.Scheduled.Any(p => p.IsEquivalent(Name))); + + /// + /// Adds the specified program name as a child of this component. + /// + /// The name of the program to add as a child. + /// programName is null or empty. + public void AddChild(string programName) + { + if (string.IsNullOrEmpty(programName)) + throw new ArgumentException("Can not remove program with null or empty name.", nameof(programName)); + + var element = new XElement(L5XName.ChildProgram, new XAttribute(L5XName.Name, programName)); + + if (Element.Element(L5XName.ChildProgram) is null) + Element.Add(new XElement(L5XName.ChildProgram)); + + Element.Element(L5XName.ChildProgram)!.Add(element); + } + + /// + /// Removes the program with the specified name from the children collection of this + /// component. + /// + /// The name of the child program to remove. + /// programName is null or empty. + public void RemoveChild(string programName) + { + if (string.IsNullOrEmpty(programName)) + throw new ArgumentException("Can not remove program with null or empty name.", nameof(programName)); + + Element.Element(L5XName.ChildPrograms)?.Elements().Where(e => e.LogixName().IsEquivalent(programName)).Remove(); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Components/Routine.cs b/src/L5Sharp/Components/Routine.cs new file mode 100644 index 00000000..eed62049 --- /dev/null +++ b/src/L5Sharp/Components/Routine.cs @@ -0,0 +1,118 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix Routine component. Contains the properties for a generic Routine element. This type does not +/// include content property. More specific routine types are derived from this base class. +/// +/// +public class Routine : LogixComponent +{ + /// + /// Creates a new with default values. + /// + /// + /// By default this will be a RLL routine type. + /// To specify a different type, use the constructor. + /// + public Routine() + { + Element.Add(new XAttribute(L5XName.Type, RoutineType.RLL)); + Element.Add(new XElement(L5XName.RLLContent)); + } + + /// + /// Creates a new of the specified . + /// + /// The of the routine. + /// type is null. + public Routine(RoutineType type) + { + if (type is null) throw new ArgumentNullException(nameof(type)); + Element.Add(new XAttribute(L5XName.Type, type)); + Element.Add(new XElement(type.ContentName)); + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Routine(XElement element) : base(element) + { + } + + /// + /// The type of the component. + /// + /// A enum specifying the type content the routine contains. + public RoutineType Type + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The online edit type for the ST/FBD/SFC type. + /// + /// + /// If the routine is a ST, FBD, or SFC type, then the value; + /// Otherwise, null for RLL routines. + /// + public OnlineEditType? OnlineEditType + { + get => GetValue(e => e.Element(Type.ContentName)); + set => SetValue(value, e => e.Element(Type.ContentName)); + } + + /// + /// The sheet size for the FBD or SFC type. + /// + /// + /// If the routine is a FBD or SFC type, then the value; + /// Otherwise, null for RLL or ST routines. + /// + public SheetSize? SheetSize + { + get => GetValue(e => e.Element(Type.ContentName)); + set => SetValue(value, e => e.Element(Type.ContentName)); + } + + /// + /// The sheet orientation for the FBD or SFC type. + /// + /// + /// If the routine is a FBD or SFC type, then the value; + /// Otherwise, null for RLL or ST routines. + /// + public SheetOrientation? SheetOrientation + { + get => GetValue(e => e.Element(Type.ContentName)); + set => SetValue(value, e => e.Element(Type.ContentName)); + } + + /// + /// Gets the routine content as a containing elements of the specified type. + /// + /// The content element type to return. + /// A with access to the root content and specified element types. + /// No content element corresponding to the specified exists for the + /// underlying . This can happen if the provided element is not valid. + /// + /// This method offers a dynamic interface for accessing content of any routine type. If the underlying routine + /// content does not match the specified, a L5XException will be thrown. + /// + public LogixContainer Content() where TCode : LogixCode + { + var content = Element.Element(Type.ContentName); + + return content is not null + ? new LogixContainer(content) + : throw Element.L5XError(Type.ContentName); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Components/Tag.cs b/src/L5Sharp/Components/Tag.cs new file mode 100644 index 00000000..e3c7dc15 --- /dev/null +++ b/src/L5Sharp/Components/Tag.cs @@ -0,0 +1,759 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix Tag component. Contains the properties that comprise the L5X Tag element. +/// +/// +[L5XType(L5XName.Tag)] +[L5XType(L5XName.LocalTag)] +[L5XType(L5XName.ConfigTag)] +[L5XType(L5XName.InputTag)] +[L5XType(L5XName.OutputTag)] +public class Tag : LogixComponent +{ + private readonly LogixMember _member; + + /// + /// Creates a new with default values. + /// + public Tag() + { + _member = new LogixMember(Element); + _member.DataChanged += OnDataChanged; + + Root = this; + TagType = TagType.Base; + ExternalAccess = ExternalAccess.ReadWrite; + Constant = false; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Tag(XElement element) : base(element) + { + _member = new LogixMember(Element); + _member.DataChanged += OnDataChanged; + + Root = this; + } + + /// + /// Creates a new nested member initialized with the root tag, underlying member, + /// and optional parent tag. + /// + /// The root or base tag of this tag member. + /// The underlying member that this tag wraps. + /// The parent tag of this tag member. + /// + /// This constructor is used internally for methods like to return new + /// tag member objects. + /// + private Tag(Tag root, LogixMember member, Tag parent) : base(root.Element) + { + _member = member ?? throw new ArgumentNullException(nameof(member)); + Root = root ?? throw new ArgumentNullException(nameof(root)); + Parent = parent ?? throw new ArgumentNullException(nameof(parent)); + } + + /// + /// + /// + /// The name of a Tag component is unique in that not all elements that we consider Tag objects + /// have the Name attribute (namely module tags like config, input, and output). + /// For this reason, the Tag component contains special code that will detect and determine a module tag name. + /// If the underlying element represents a normal tag element, this simply returns the Name attribute, + /// similar to other components. Setting the Name property will always update the name attribute of the + /// underlying element. + /// + /// Note that this property will always represent the name of the root tag component. This is true even for + /// nested tag objects. To get the full tag name for any given tag object, use the property. + /// + /// + public override string Name + { + get => GetTagName(); + set => SetValue(value); + } + + /// + /// The description (either root, comment, or parent) for the tag or tag member. + /// + /// A containing the text description for the tag. + /// + /// + /// If this is the root tag, this will return the root/base description. + /// If this is a nested tag member, this will look for a configured comment (as comments are stored in a different + /// element in the L5X), and return the value if found. If the comment is not found for the tag member, + /// this will return the parent description, which mimics the pass through feature of logix tag documentation. + /// + /// + /// Setting this value for a nested tag member will update the underlying comments element for the tag. + /// Setting this value for the root tag will simply update the root tag description element. + /// + /// + public override string? Description + { + get => GetTagDescription(); + set => SetTagDescription(value); + } + + /// + /// The name of the data type the tag represents. + /// + /// A representing the name of the tag data type. + /// + /// This property simply points to the name property of . + /// This keeps the properties in sync. By initializing value, you are setting the data type name. + /// Once initialized, the data type won't change. To change the tag's type, use . + /// + public string DataType => Value.Name; + + /// + /// The dimensions of the tag, indicating the length and dimensions of it's array. + /// + /// A value representing the array dimensions of the tag. + /// + /// This value will always point to the dimensions property of , assuming it is an + /// . + /// If Value is not an array type, this property will always return . + /// + public Dimensions Dimensions => Value is ArrayType array ? array.Dimensions : Dimensions.Empty; + + /// + /// The radix format of Value. Only applies if the tag is an . + /// + /// A option representing data format of the tag value. + /// + /// This value will always point to the radix of , assuming it is an . + /// If Value is not an atomic type, this property will always return . + /// + public Radix Radix => Value is AtomicType atomic ? atomic.Radix : Radix.Null; + + /// + /// The value or data of the . + /// + /// A containing the tag data. + /// + /// + /// The is the basis for all tag data types. This property may represent the atomic + /// value (bool, integer, float), string, complex structure, or array. LogixType has built in implicit operators + /// to convert .NET types to LogixType objects so to make setting Value more concise. + /// + /// + /// Since the type can not be known at compile time when deserializing, we treat it as the abstract base class. + /// However, the will attempt to create concrete instances of types that are available, + /// allowing the user to cast Value down to more derived types. + /// + /// + public LogixType Value + { + get => _member.DataType; + set => _member.DataType = value; + } + + /// + /// The external access option indicating the read/write access of the tag. + /// + /// A option representing read/write access of the tag. + public ExternalAccess? ExternalAccess + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// A type indicating whether the current tag component is a base tag, or alias for another tag instance. + /// + /// A option representing the type of tag component. + public TagType? TagType + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The usage option indicating the scope in which the tag is visible or usable from. + /// + /// A option representing the tag scope. + public TagUsage? Usage + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The tag name of the tag that is the alias of the current tag object. + /// + /// A string representing the full tag name of the alias tag. + public TagName? AliasFor + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Indicates whether the tag is a constant. + /// + /// true if the tag is constant; otherwise, false. + /// Only value type tags have the ability to be set as a constant. Default is false. + public bool? Constant + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The configured unit value of the tag. + /// + /// A representing the defined units of the tag. + /// This appears only used for module defined tags. + public string? Unit + { + get => GetUnit(); + set => SetUnit(value); + } + + /// + /// The parent tag of this member. + /// + /// + /// A representing the immediate parent tag of the this tag member. Will be null + /// for all root tag objects. + /// + /// + /// This property helps model the hierarchical structure of a tag object. Tags has a + /// which can represent a nested complex data type. This class models this by keeping references to the Root + /// and Parent tags for each tag object. Only nested tag members should have a Parent. + /// + /// + public Tag? Parent { get; } + + /// + /// The root tag of this member. + /// + /// A representing the root tag. + /// + /// This property helps model the hierarchical structure of a tag object. Tags has a + /// which can represent a nested complex data type. This class models this by keeping references to the Root + /// and Parent tags for each tag object. All tags should have a Root. + /// + /// + public Tag Root { get; } + + /// + /// + /// + public Tag? Alias => AliasFor is not null ? L5X?.Find(AliasFor) : default; + + /// + /// The full tag name path of the . + /// + /// A containing the full dot-down path of the tag member name. + /// + /// + /// This property will always represent the fully qualified tag name path, which includes nested tag + /// member object. This property is determined using the hierarchical structure of the tag component. + /// + /// + public TagName TagName => Parent is not null ? TagName.Concat(Parent.TagName, _member.Name) : new TagName(Name); + + /// + /// Gets the tag member having the provided tag name value. The tag name can represent either an immediate member + /// or a nested member in the tag hierarchy. + /// + /// The relative to the current tag member for which to retrieve. + /// tagName is null. + /// tagName does not represent a valid member for the tag member data structure. + /// A child component represented by the provided tag name value. + /// + /// Note that tagName can be a path to a member more than one layer down the hierarchical structure + /// of the tag. However, it must start with a member of the current tag, and not the + /// actual name of this tag object. The difference between this indexer property and + /// is that this will throw and exception if a member with tagName is not found + /// (i.e. returns non-nullable reference type). + /// + public Tag this[TagName tagName] + { + get + { + if (tagName is null) throw new ArgumentNullException(nameof(tagName)); + if (tagName.IsEmpty) return this; + + var member = Value.Member(tagName.Root); + if (member is null) + throw new ArgumentException( + $"No member with name '{tagName.Root}' exists in the tag data structure for type {DataType}."); + + var tag = new Tag(Root, member, this); + var remaining = TagName.Combine(tagName.Members.Skip(1)); + return remaining.IsEmpty ? tag : tag[remaining]; + } + } + + /// + /// Adds a new member to the tag's complex data structure. + /// + /// The name of the member to add to the tag's data structure. + /// The of the member to add to the tag's data structure. + /// The current tag does not contain a mutable complex logix type. + /// + /// This will operate relative to the current tag member object, and is simply a call to the underlying + /// Add method. Therefore this is simply a helper to make mutating tag structures + /// more concise. + /// + public void Add(string name, LogixType value) + { + var member = new LogixMember(name, value); + if (Value is not ComplexType complexType) + throw new InvalidOperationException("Can only mutate ComplexType tags."); + complexType.Add(member); + } + + /// + /// Gets a descendent tag member relative to the current tag member. + /// + /// The full path of the member to get. + /// A representing the child member instance. + /// + /// Note that tagName can be a path to a member more than one layer down the hierarchical structure + /// of the tag or tag member. However, it must start with a member of the current tag or tag member, and not the + /// actual name of the current tag or tag member. + /// + /// + /// var member = tag.Member("Array[1].SubType.Member.0"); + /// + public Tag? Member(TagName tagName) + { + if (tagName is null) throw new ArgumentNullException(nameof(tagName)); + if (tagName.IsEmpty) return this; + + var member = Value.Member(tagName.Root); + if (member is null) return default; + + var tag = new Tag(Root, member, this); + var remaining = TagName.Combine(tagName.Members.Skip(1)); + return remaining.IsEmpty ? tag : tag.Member(remaining); + } + + /// + /// Gets this and all descendent tag members of the tag data structure. + /// + /// A containing objects. + /// + /// This recursively traverses the hierarchical data structure of tag's and returns all + /// descendant tags, as well as this tag. + /// + public IEnumerable Members() + { + var members = Parent is null ? new List {this} : new List(); + + foreach (var member in Value.Members) + { + var tagMember = new Tag(Root, member, this); + members.Add(tagMember); + members.AddRange(tagMember.Members()); + } + + return members; + } + + /// + /// Gets this and all descendent tag members of the tag data structure that satisfy the specified tag name predicate. + /// + /// A predicate expression specifying the tag name filter. + /// A containing objects that satisfy the predicate. + /// + /// This recursively traverses the hierarchical data structure of tag's and returns all + /// tags that satisfy the specified predicate. + /// + public IEnumerable Members(Predicate predicate) + { + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + + var members = Parent is null && predicate.Invoke(TagName) ? new List {this} : new List(); + + foreach (var member in Value.Members) + { + var tag = new Tag(Root, member, this); + + if (predicate.Invoke(tag.TagName)) + members.Add(tag); + + members.AddRange(tag.Members(predicate)); + } + + return members; + } + + /// + /// Gets this and all descendent tag members of the tag data structure that that satisfy the specified tag predicate. + /// + /// A predicate expression specifying the tag filter. + /// A containing objects that satisfy the predicate. + /// + /// This recursively traverses the hierarchical data structure of tag's and returns all + /// tags that satisfy the specified predicate. + /// + public IEnumerable Members(Predicate predicate) + { + if (predicate is null) throw new ArgumentNullException(nameof(predicate)); + + var members = Parent is null && predicate.Invoke(this) ? new List {this} : new List(); + + foreach (var member in Value.Members) + { + var tag = new Tag(Root, member, this); + + if (predicate.Invoke(tag)) + members.Add(tag); + + members.AddRange(tag.Members(predicate)); + } + + return members; + } + + /// + /// Gets all descendent tags of the tag specified by the provided tag name. + /// + /// A tag name path to the tag member for which to get members of. + /// A containing objects. + /// This recursively traverses the hierarchical data structure of the tag and returns all + /// child/descendant members. + public IEnumerable MembersOf(TagName tagName) + { + if (tagName is null) throw new ArgumentNullException(nameof(tagName)); + if (tagName.IsEmpty) return Members(); + + var member = Value.Member(tagName.Root); + if (member is null) return Enumerable.Empty(); + + var tag = new Tag(Root, member, this); + var remaining = TagName.Combine(tagName.Members.Skip(1)); + return remaining.IsEmpty ? tag.Members() : tag.MembersOf(remaining); + } + + /// + /// Creates a new with the provided name and specified type parameter. + /// + /// The name of the tag. + /// The logix data type of the tag. Type must have parameterless constructor to create. + /// A new object with specified parameters. + public static Tag New(string name) where TLogixType : LogixType, new() => + new() {Name = name, Value = new TLogixType()}; + + /// + /// Removes a member with the specified name from the tag's complex data structure. + /// + /// The name of the member to remove. + /// The current tag does not contain a mutable complex logix type. + /// + /// This will operate relative to the current tag member object, and is simply a call to the underlying + /// Remove method. Therefore this is simply a helper to make mutating tag structures + /// more concise. + /// + public void Remove(string name) + { + if (Value is not ComplexType complexType) + throw new InvalidOperationException("Can only mutate ComplexType tags."); + complexType.Remove(name); + } + + /// + /// Returns a collection of all descendent tag names of the current Tag, including the tag name of the + /// this Tag. + /// + /// + /// A of containing the this tag name and all child tag names. + /// + public IEnumerable TagNames() => Members().Select(t => t.TagName); + + /// + public override string ToString() => TagName; + + /// + /// Returns as new with the updated data type value provided. + /// + /// The value to change to. + /// + /// A with the same underlying and corresponding properties with + /// changed to the provided . + /// + /// When this tag is a nested tag member and it's parent tag's + /// property is not a object. + /// + /// + /// This is meant to be a concise way to change the data type of tag while leaving all else the same, since setting + /// should only ever update the value and not change the data type. + /// + /// + /// If this is called for the Root tag object, then the entire data element is replaced and a new instance + /// is returned. The Tag will still be attached as we are mutating the underlying element object in place. + /// If this is called for a nested tag member, then this method checks if the parent tag is a complex type, and if so, + /// calls the underlying Replace method for the current member name. Therefore, calls to this method for nested tags + /// will fail if for the parent tag is not a complex type object. + /// + /// + public Tag With(LogixType value) + { + if (Parent is null) + { + SetData(value); + return new Tag(Element); + } + + if (Parent.Value is not ComplexType complexType) + throw new InvalidOperationException( + $"Can not mutate tag data for parent type {Parent.DataType} as it is not a complex type instance."); + + complexType.Replace(TagName.Member, value); + return Root[TagName.Path]; + } + + #region Internal + + /// + /// Triggers when a nested data type value of this tag's changes. + /// + private void OnDataChanged(object sender, EventArgs e) => SetData(Root.Value); + + /// + /// Handles setting the data of the root tag and updating the root properties + /// + private void SetData(LogixType value) + { + var data = Element.Elements().FirstOrDefault(e => + DataFormat.Supported.Any(f => f == e.Attribute(L5XName.Format)?.Value)); + + if (data is null) + Element.Add(GenerateData(value)); + else + data.ReplaceWith(GenerateData(value)); + + SetTagAttributes(value); + } + + /// + /// Handles setting the , , and of the underlying + /// element for the tag. + /// + private void SetTagAttributes(LogixType value) + { + Element.SetAttributeValue(L5XName.DataType, value.Name); + + var radix = value is AtomicType atomicType ? atomicType.Radix + : value is ArrayType arrayType && arrayType.Radix != Radix.Null ? arrayType.Radix + : null; + Element.SetAttributeValue(L5XName.Radix, radix); + + var dimensions = value is ArrayType array ? array.Dimensions : null; + Element.SetAttributeValue(L5XName.Dimensions, dimensions); + } + + /// + /// Generates the root data element for a tag component provided a logix type. + /// + private static XElement GenerateData(LogixType type) + { + return type switch + { + StringType stringType => stringType.Serialize(), + ALARM_ANALOG alarmAnalog => GenerateFormatted(alarmAnalog, DataFormat.Alarm), + ALARM_DIGITAL alarmDigital => GenerateFormatted(alarmDigital, DataFormat.Alarm), + MESSAGE message => GenerateFormatted(message, DataFormat.Message), + ILogixSerializable serializable => GenerateFormatted(serializable, DataFormat.Decorated) + }; + } + + /// + /// Generates data element with provided format value and serializable type. + /// + private static XElement GenerateFormatted(ILogixSerializable type, DataFormat format) + { + var data = new XElement(L5XName.Data, new XAttribute(L5XName.Format, format)); + data.Add(type.Serialize()); + return data; + } + + /// + /// Handles determining the tag name of the current object from the underlying XElement. This handles module + /// tag elements (ConfigTag, InputTag, OutputTag) as well as normal component elements (Tag, LocalTag). + /// + private string GetTagName() + { + var xName = Element.Name; + + if (xName == L5XName.ConfigTag || xName == L5XName.InputTag || xName == L5XName.OutputTag) + return ModuleTagName(Element); + + return Element.Attribute(L5XName.Name)?.Value ?? string.Empty; + } + + /// + /// A helper for determining a module tag name for an input, output, or config tag element. This involves getting + /// the tag suffix, module name, parent module name, and configured slot number. + /// + private static string ModuleTagName(XElement element) + { + var suffix = DetermineModuleSuffix(element); + + var moduleName = element.Ancestors(L5XName.Module) + .FirstOrDefault()?.Attribute(L5XName.Name)?.Value; + + var parentName = element.Ancestors(L5XName.Module) + .FirstOrDefault()?.Attribute(L5XName.ParentModule)?.Value; + + var slot = element + .Ancestors(L5XName.Module) + .Descendants(L5XName.Port) + .Where(p => bool.TryParse(p.Attribute(L5XName.Upstream)?.Value!, out _) + && p.Attribute(L5XName.Type)?.Value != "Ethernet" + && int.TryParse(p.Attribute(L5XName.Address)?.Value, out _)) + .Select(p => p.Attribute(L5XName.Address)?.Value) + .FirstOrDefault(); + + return slot is not null ? $"{parentName}:{slot}:{suffix}" : $"{moduleName}:{suffix}"; + } + + private static string DetermineModuleSuffix(XElement element) + { + if (element.Name == L5XName.ConfigTag) return "C"; + + if (element.Name == L5XName.InputTag) + return element.Parent?.Attribute(L5XName.InputTagSuffix)?.Value ?? "I"; + + if (element.Name == L5XName.OutputTag) + return element.Parent?.Attribute(L5XName.OutputTagSuffix)?.Value ?? "O"; + + throw new ArgumentException($"Module tag element name {element.Name} not valid."); + } + + /// + /// Handles getting a comment value for the current tag. + /// + private string? GetTagDescription() + { + if (Parent is null) return Element.Element(L5XName.Description)?.Value; + + var comment = Element.Descendants(L5XName.Comment) + .FirstOrDefault(e => string.Equals(e.Attribute(L5XName.Operand)?.Value, TagName.Operand, + StringComparison.OrdinalIgnoreCase)); + + //logix descriptions propagates to their children when not overriden. This mimics that. + return comment is not null ? comment.Value : Parent.Description; + } + + /// + /// Handles setting a comment element of the root tag structure for the current tag name operand. + /// + private void SetTagDescription(string? value) + { + //If the parent is null forward set to base description implementation which is essentially + //setting the description element of the root tag component. + if (Parent is null) + { + base.Description = value; + return; + } + + //Child descriptions are set in the comments element of a tag. + if (string.IsNullOrEmpty(value)) + { + Element.Descendants(L5XName.Comment) + .FirstOrDefault(e => string.Equals(e.Attribute(L5XName.Operand)?.Value, TagName.Operand, + StringComparison.OrdinalIgnoreCase))?.Remove(); + return; + } + + var comments = Element.Element(L5XName.Comments); + if (comments is null) + { + comments = new XElement(L5XName.Comments); + + //This is to place comments right after description if it exists, otherwise as the first element. + if (Element.FirstNode is XElement element && element.Name == L5XName.Description) + Element.FirstNode.AddAfterSelf(comments); + else + Element.AddFirst(comments); + } + + var comment = comments.Elements(L5XName.Comment) + .FirstOrDefault(e => string.Equals(e.Attribute(L5XName.Operand)?.Value, TagName.Operand, + StringComparison.OrdinalIgnoreCase)); + + if (comment is not null) + { + comment.Value = value; + return; + } + + comments.Add(GenerateDescriptor(value, L5XName.Comment)); + } + + /// + /// Handles getting a unit value for the current tag name operand. + /// + private string? GetUnit() + { + return Element.Descendants(L5XName.EngineeringUnit) + .FirstOrDefault(e => string.Equals(e.Attribute(L5XName.Operand)?.Value, TagName.Operand, + StringComparison.OrdinalIgnoreCase))?.Value; + } + + /// + /// Handles setting a unit element of the root tag structure for the current tag name operand. + /// + private void SetUnit(string? value) + { + if (string.IsNullOrEmpty(value)) + { + Element.Descendants(L5XName.EngineeringUnit) + .FirstOrDefault(e => string.Equals(e.Attribute(L5XName.Operand)?.Value, TagName.Operand, + StringComparison.OrdinalIgnoreCase))?.Remove(); + return; + } + + var units = Element.Element(L5XName.EngineeringUnits); + if (units is null) + { + units = new XElement(L5XName.EngineeringUnits); + Element.Add(units); + } + + var unit = units.Elements(L5XName.EngineeringUnit) + .FirstOrDefault(e => string.Equals(e.Attribute(L5XName.Operand)?.Value, TagName.Operand, + StringComparison.OrdinalIgnoreCase)); + + if (unit is not null) + { + unit.Value = value; + return; + } + + units.Add(GenerateDescriptor(value, L5XName.EngineeringUnit)); + } + + /// + /// Generates a new comment/unit descriptor element with the provided value and name. + /// + private XElement GenerateDescriptor(string value, string name) + { + var element = new XElement(name); + element.Add(new XAttribute(L5XName.Operand, TagName.Operand.ToUpper())); + element.Add(new XCData(value)); + return element; + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Components/Task.cs b/src/L5Sharp/Components/Task.cs new file mode 100644 index 00000000..dc4bccc9 --- /dev/null +++ b/src/L5Sharp/Components/Task.cs @@ -0,0 +1,215 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix Task component. Contains the properties that comprise the L5X Task element. +/// +/// +/// Observe these guidelines when defining a task:
+/// • Tasks must be defined after programs and before controller objects.
+/// • There is a maximum of 32 tasks.
+/// • There is one continuous task only.
+/// • A program can be scheduled under one task only.
+/// • Scheduled programs must be defined (must exist).
+///
+/// +public class Task : LogixComponent +{ + /// + /// Creates a new with default values. + /// + /// By default uses , 10ms , 10ms , + /// and 500ms . + public Task() + { + Type = TaskType.Periodic; + Priority = new TaskPriority(10); + Rate = new ScanRate(10); + Watchdog = new Watchdog(500); + InhibitTask = false; + DisableUpdateOutputs = false; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Task(XElement element) : base(element) + { + } + + /// + /// Gets the type of the task component (Continuous, Periodic, Event). + /// + /// A enum representing the type of the task. + public TaskType Type + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The scan priority of the task component. Default of 10. + /// + /// >A value type representing the priority of the task. + public TaskPriority Priority + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The scan rate (ms) of the task component. Default of 10. + /// + /// >A value type representing the rate of the task. + public ScanRate? Rate + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The watchdog rate (ms) of the task component. Default of 500. + /// + /// A value type representing the watchdog of the task. + public Watchdog Watchdog + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The value indicating whether the task is inhibited. + /// + /// true if the task is inhibited; otherwise false. + public bool InhibitTask + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The value indicating whether the task is set to disable updating output values. + /// + /// true if the task has disabled update outputs; otherwise false. + public bool DisableUpdateOutputs + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The trigger for the event task. Only used for event tasks. + /// + /// + /// A value indicating what triggers the task. Returns null for non-event tasks. + /// + public TaskEventTrigger? EventTrigger + { + get => GetValue(L5XName.EventInfo.XName()); + set => SetValue(value, L5XName.EventInfo.XName()); + } + + /// + /// The tag name that the event task consumes. Only used for event tasks. + /// + /// + /// A value indicating what tag to consume. Returns null for non-event tasks. + /// + /// Only used for event tasks with a Consumed Tag trigger or a Module Input Data State Change trigger. + public TagName? EventTag + { + get => GetValue(L5XName.EventInfo.XName()); + set => SetValue(value, L5XName.EventInfo.XName()); + } + + /// + /// The value indicating whether timeouts are enabled for the event task. Only used for event tasks. + /// + /// + /// If the task is an event type task, true indicating that timeouts are enabled, false to indicate + /// they are disabled. Returns null for non-event tasks. + /// + public bool? EnableTimeout + { + get => GetValue(L5XName.EventInfo.XName()); + set => SetValue(value, L5XName.EventInfo.XName()); + } + + /// + /// Retrieves a collection of components that are scheduled to this . + /// + /// A containing component objects schedule to this task. + /// + /// This is an extension to the type and uses the attached L5X file to retrieve the program components. + /// Therefore if this task is not attached it will return and empty collection. Also if no program exists with the + /// scheduled name, this will return an empty collection. + /// + public IEnumerable Programs => + L5X?.Programs.Where(p => Scheduled.Any(s => s == p.Name)) ?? Enumerable.Empty(); + + /// + /// The collection of program names that are scheduled to the task. + /// + /// A containing the string program names. + /// This member just returns the read only list of scheduled programs. To modify the list, use + public IEnumerable Scheduled => + Element.Descendants(L5XName.ScheduledProgram).Select(e => e.LogixName()); + + /// + public override L5X Export(Revision? softwareRevision = null) + { + throw new NotSupportedException("Task components do not support export function."); + } + + /// + public override void Delete() + { + if (Element.Parent is null || !IsAttached) return; + + foreach (var program in Programs) + { + program.Delete(); + } + + Element.Remove(); + } + + /// + /// Adds the provided program name to the underlying list of . + /// + /// The name of the program to schedule. + public void Schedule(string program) + { + var element = new XElement(L5XName.ScheduledProgram, new XAttribute(L5XName.Name, program)); + + if (Element.Element(L5XName.ScheduledPrograms) is null) + Element.Add(new XElement(L5XName.ScheduledPrograms)); + + Element.Element(L5XName.ScheduledPrograms)!.Add(element); + } + + /// + /// Removes the specified program name from the underlying list of programs. + /// + /// The name of the program to cancel. + public void Cancel(string program) + { + var scheduled = Element.Element(L5XName.ScheduledPrograms); + + if (scheduled is null) return; + + scheduled.Elements(L5XName.ScheduledProgram).FirstOrDefault(p => p.LogixName() == program)?.Remove(); + + if (!scheduled.Elements().Any()) + scheduled.Remove(); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Components/Trend.cs b/src/L5Sharp/Components/Trend.cs new file mode 100644 index 00000000..7e38dd59 --- /dev/null +++ b/src/L5Sharp/Components/Trend.cs @@ -0,0 +1,381 @@ +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix Trend component. Contains the properties that comprise the L5X Trend element. +/// +/// +/// Observe these guidelines when defining a trend:
+/// • A trend can support as many as eight pen declarations.
+///
+/// +public class Trend : LogixComponent +{ + /// + public Trend() + { + SamplePeriod = 1; + CaptureSizeType = CaptureSizeType.Samples; + } + + /// + public Trend(XElement element) : base(element) + { + } + + /// + /// Specify how often trending tags are collected in milliseconds (1 msec...30 minutes). + /// + public int SamplePeriod + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specifies the maximum number of captures allowed (1...100). + /// + public int NumberOfCaptures + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Define how the capture size is specified. + /// + /// A representing the capture size option. Type Samples, TimePeriod, or NoLimit. + public CaptureSizeType? CaptureSizeType + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the number of samples for each capture. + /// + /// + /// Rockwell: The maximum number of samples is 2-hours worth of data samples or 1000 samples, + /// whichever is greater. If the CaptureSizeType is Samples, the range is 1...(2 hours/SamplePeriod) or 1000 samples, + /// whichever is greater. If the CaptureSizeType is TimePeriod, the range is SamplePeriod...2 hours or + /// (SamplePeriod * 1000), whichever is greater + /// + public int CaptureSize + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the type of the start trigger + /// + /// + public TriggerType? StartTriggerType + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the tag name of the first start trigger. The name must be one of the pen names. + /// + public TagName? StartTriggerTag1 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the operation that is applied on , + /// and or . + /// + public TriggerOperation? StartTriggerOperation1 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the type of the first start trigger target. + /// + /// + /// If you type TargetValue, is expected. + /// Otherwise, is expected. + /// + public TriggerTargetType? StartTriggerTargetType1 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify a target value if the is TargetValue. + /// + /// Type a binary, octal, decimal, or hexadecimal integer number or type a floating point number. + public AtomicType? StartTriggerTargetValue1 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify a target tag if the StartTriggerTargetType is TargetTag. + /// + /// The tag must be one of the pen names. + public TagName? StartTriggerTargetTag1 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify a logical operation (AND or OR) that is performed on StartTriggerXXX1 and StartTriggerXXX2. + /// + /// StartTriggerXXX1 consists of StartTriggerTag1, StartTriggerOperation1, StartTriggerTargetType1, and + /// StartTriggerTargetValue1 or StartTriggerTargetTag1. StartTriggerXXX2 consists of StartTriggerTag2, + /// StartTriggerOperation2, StartTriggerTargetType2, and StartTriggerTargetValue2 or StartTriggerTargetTag2. + public Operator? StartTriggerLogicalOperation + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the tag name of the second start trigger. The name must be one of the pen names. + /// + public TagName? StartTriggerTag2 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the operation that is applied on , + /// and or . + /// + public TriggerOperation? StartTriggerOperation2 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the type of the first start trigger target. + /// + /// + /// If you type TargetValue, is expected. + /// Otherwise, is expected. + /// + public TriggerTargetType? StartTriggerTargetType2 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify a target value if the is TargetValue. + /// + /// Type a binary, octal, decimal, or hexadecimal integer number or type a floating point number. + public AtomicType? StartTriggerTargetValue2 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify a target tag if the StartTriggerTargetType is TargetTag. + /// + /// The tag must be one of the pen names. + public TagName? StartTriggerTargetTag2 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Define how pre-samples are specified. Type Samples or TimePeriod. + /// + public SamplesType? PreSamplesType + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the number of pre-samples (0...1000) if the PreSampleType is Samples. Specify a time period + /// (0...(SamplePeriod ∗ 1000)) that covers pre-samples if the PreSampleType is TimePeriod. + /// + public int? PreSamples + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the type of the stop trigger + /// + /// A representing the value NoTrigger or Event Trigger. + public TriggerType? StopTriggerType + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the tag name of the first start trigger. The name must be one of the pen names. + /// + public TagName? StopTriggerTag1 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the operation that is applied on , + /// and or . + /// + public TriggerOperation? StopTriggerOperation1 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the type of the first start trigger target. + /// + /// + /// If you type TargetValue, is expected. + /// Otherwise, is expected. + /// + public TriggerTargetType? StopTriggerTargetType1 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify a target value if the is TargetValue. + /// + /// Type a binary, octal, decimal, or hexadecimal integer number or type a floating point number. + public AtomicType? StopTriggerTargetValue1 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify a target tag if the StopTriggerTargetType is TargetTag. + /// + /// The tag must be one of the pen names. + public TagName? StopTriggerTargetTag1 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify a logical operation (AND or OR) that is performed on StopTriggerXXX1 and StopTriggerXXX2. + /// + /// StopTriggerXXX1 consists of StopTriggerTag1, StopTriggerOperation1, StopTriggerTargetType1, and + /// StopTriggerTargetValue1 or StopTriggerTargetTag1. StopTriggerXXX2 consists of StopTriggerTag2, + /// StopTriggerOperation2, StopTriggerTargetType2, and StopTriggerTargetValue2 or StopTriggerTargetTag2. + public Operator? StopTriggerLogicalOperation + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the tag name of the second start trigger. The name must be one of the pen names. + /// + public TagName? StopTriggerTag2 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the operation that is applied on , + /// and or . + /// + public TriggerOperation? StopTriggerOperation2 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the type of the first start trigger target. + /// + /// + /// If you type TargetValue, is expected. + /// Otherwise, is expected. + /// + public TriggerTargetType? StopTriggerTargetType2 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify a target value if the is TargetValue. + /// + /// Type a binary, octal, decimal, or hexadecimal integer number or type a floating point number. + public AtomicType? StopTriggerTargetValue2 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify a target tag if the StopTriggerTargetType is TargetTag. + /// + /// The tag must be one of the pen names. + public TagName? StopTriggerTargetTag2 + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Define how post-samples are specified. Type Samples or TimePeriod. + /// + public SamplesType? PostSamplesType + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the number of post-samples (0...1000) if the PostSampleType is Samples. Specify a time period + /// (0...(SamplePeriod ∗ 1000)) that covers post-samples if the PostSampleType is TimePeriod. + /// + public int? PostSamples + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the version of the Trend feature. + /// + public int? TrendxVersion + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The collection of components the Trend is configured with. + /// + /// Only supports up to 8 pens per trend. + public LogixContainer Pens + { + get => GetContainer(); + set => SetContainer(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Components/WatchList.cs b/src/L5Sharp/Components/WatchList.cs new file mode 100644 index 00000000..c2cbdec0 --- /dev/null +++ b/src/L5Sharp/Components/WatchList.cs @@ -0,0 +1,88 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix WatchList component. Contains the properties that comprise the L5X QuickWatchList element. +/// +/// +[L5XType(L5XName.QuickWatchList)] +public class WatchList : LogixComponent +{ + /// + public WatchList() : base(new XElement(L5XName.QuickWatchList)) + { + } + + /// + public WatchList(XElement element) : base(element) + { + } + + /// + /// Accesses a at the specified index of the . + /// + /// The zer0-based index of the . + public WatchTag this[int index] + { + get => new(Element.Elements(L5XName.WatchTag).ElementAt(index)); + set => Element.Elements(L5XName.WatchTag).ElementAt(index).ReplaceWith(value.Serialize()); + } + + /// + /// Accesses a of the with the specifier tag name. + /// + /// The tag specifier of the to access. + public WatchTag this[string specifier] + { + get + { + var tag = Element.Elements(L5XName.WatchTag) + .FirstOrDefault(e => e.Attribute(L5XName.Specifier)?.Value == specifier); + return tag is not null ? new WatchTag(tag) : throw Element.L5XError(specifier!); + } + set + { + if (value is null) throw new ArgumentNullException(nameof(value)); + + var tag = Element.Elements(L5XName.WatchTag) + .FirstOrDefault(e => e.Attribute(L5XName.Specifier)?.Value == specifier); + + if (tag is null) + { + Element.Add(value.Serialize()); + return; + } + + tag.ReplaceWith(value.Serialize()); + } + } + + /// + /// Adds a to the end of the . + /// + /// The tag to add. + /// tag is null. + public void Add(WatchTag tag) + { + if (tag is null) throw new ArgumentNullException(nameof(tag)); + Element.Add(tag.Serialize()); + } + + /// + /// Removes the first found in the with the provided specifier name. + /// + /// The tag specifier to remove. + public void Remove(string specifier) + { + if (string.IsNullOrEmpty(specifier)) throw new ArgumentNullException(nameof(specifier)); + + Element.Elements(L5XName.WatchTag) + .FirstOrDefault(e => e.Attribute(L5XName.Specifier)?.Value == specifier)?.Remove(); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Block.cs b/src/L5Sharp/Elements/Block.cs new file mode 100644 index 00000000..595d17cd --- /dev/null +++ b/src/L5Sharp/Elements/Block.cs @@ -0,0 +1,1056 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo + +namespace L5Sharp.Core; + +/// +/// A DiagramElement type that defines the properties for nested function block elements in a +/// Function Block Diagram (FBD). +/// +/// +/// A Block represents a function block within the FBD. These are blocks that represent +/// specific logix built-in instructions as opposed to AOIs. A Block differs from a Function +/// in that it requires a backing tag to operate over, whereas a Function represents a simple logic gate or +/// operation that takes inputs and produces an output without the need for a backing tag. +/// +/// +[L5XType(L5XName.IRef, L5XName.Sheet)] +[L5XType(L5XName.ORef, L5XName.Sheet)] +[L5XType(L5XName.ICon, L5XName.Sheet)] +[L5XType(L5XName.OCon, L5XName.Sheet)] +[L5XType(L5XName.Block, L5XName.Sheet)] +[L5XType(L5XName.Function, L5XName.Sheet)] +[L5XType(L5XName.AddOnInstruction, L5XName.Sheet)] +[L5XType(L5XName.JSR, L5XName.Sheet)] +[L5XType(L5XName.SBR, L5XName.Sheet)] +[L5XType(L5XName.RET, L5XName.Sheet)] +public class Block : DiagramElement +{ + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Block(XElement element) : base(element) + { + } + + /// + /// The descriptive indication of the location of this within the containing Diagram. + /// + /// A indicating the cell and optional sheet or chart number where the block is located. + /// + /// This is an internally defined value so that we can identify instructions when referencing components. + /// + public override string Location => + Sheet is not null ? $"Sheet {Sheet.Number} {Cell} ({X}, {Y})" : $"{Cell} ({X}, {Y})"; + + /// + /// Represents the instuction, function, or element type name of the element. + /// + /// A containing the block type name. + /// + public string Type + { + get + { + return L5XType switch + { + L5XName.Block => Element.Attribute(L5XName.Type)?.Value ?? throw Element.L5XError(L5XName.Type), + L5XName.Function => Element.Attribute(L5XName.Type)?.Value ?? throw Element.L5XError(L5XName.Type), + L5XName.AddOnInstruction => Element.Attribute(L5XName.Name)?.Value ?? + throw Element.L5XError(L5XName.Name), + _ => Element.L5XType(), + }; + } + } + + /// + /// Represents the Operand property of a generic function block element. This can be the backing tag name, + /// reference name, connector name, or routine name depending on the block type. + /// + /// Thrown when attempting to set the operand for an unsupported block type. + /// An object containing the value, which could be a tag name, immediate value, or simple string name. + /// + /// Since this class models all the different function block types, this proprty has to determine which attribute + /// to read/write to internally depending on the type. For IRef, ORef, Block, and AOI blocks, this represents the + /// Operand tag name. For an ICon and OCOn, this represents the connector Name. For a routine blocks (JSR, SBR, RET), + /// this represents the Routine name. Note that this is nullable since a Function + /// block does not have an operand or backing tag property, and therefore attempting to set a value for that block type + /// will result in an exception. + /// + public Argument? Operand + { + get + { + return L5XType switch + { + L5XName.ICon => Element.Attribute(L5XName.Name)?.Value.Parse(), + L5XName.OCon => Element.Attribute(L5XName.Name)?.Value.Parse(), + L5XName.JSR => Element.Attribute(L5XName.Routine)?.Value.Parse(), + L5XName.SBR => Element.Attribute(L5XName.Routine)?.Value.Parse(), + L5XName.RET => Element.Attribute(L5XName.Routine)?.Value.Parse(), + _ => Element.Attribute(L5XName.Operand)?.Value.Parse() + }; + } + set + { + switch (L5XType) + { + case L5XName.IRef: + case L5XName.ORef: + case L5XName.Block: + case L5XName.AddOnInstruction: + Element.SetAttributeValue(L5XName.Operand, value); + break; + case L5XName.ICon: + case L5XName.OCon: + Element.SetAttributeValue(L5XName.Name, value); + break; + case L5XName.JSR: + case L5XName.SBR: + case L5XName.RET: + Element.SetAttributeValue(L5XName.Routine, value); + break; + default: + throw new NotSupportedException($"Block type '{L5XType}' does not support Operand."); + } + } + } + + /// + /// Whether or not to hide the description for the block element. + /// + /// true if the description is hidden; Otherwise; false. + /// This property is only found on IREF, OREF, and Block type elements. + public bool? HideDesc + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The pins + /// + /// + public IEnumerable Pins + { + get + { + return L5XType switch + { + L5XName.Block => GetValues(L5XName.VisiblePins), + L5XName.AddOnInstruction => GetValues(L5XName.VisiblePins), + L5XName.JSR => GetValues(L5XName.In).Concat(GetValues(L5XName.Ret)), + L5XName.SBR => GetValues(L5XName.In), + L5XName.RET => GetValues(L5XName.Ret), + _ => Enumerable.Empty() + }; + } + } + + /// + /// The that this block is contained within. + /// + /// A + public Sheet? Sheet => Element.Parent is not null ? new Sheet(Element.Parent) : default; + + /// + /// Retrieves a collection of tag names that the function block contains. + /// + /// A collection of values. + /// + /// This is not a underlying block property of the XML but rather a helper that combins the + /// and to form a set of tag names that can be references by the block. + /// + public IEnumerable Tags + { + get + { + if (Operand is null || !Operand.IsTag) return Enumerable.Empty(); + var tags = new List { (TagName)Operand }; + tags.AddRange(Pins.Select(p => TagName.Concat(Operand.ToString(), p))); + return tags; + } + } + + /// + public override IEnumerable References() + { + var references = new List { new(Element, L5XName.Instruction, Type) }; + + if (Operand is null || !Operand.IsTag) + return references; + + references.Add(new CrossReference(Element, L5XName.Tag, Operand.ToString(), Type)); + references.AddRange(Tags.Select(t => new CrossReference(Element, L5XName.Tag, t, Type))); + return references; + } + + #region Elements + + /// + /// Creates a new IREF function block element initialized with appropriate values. + /// + /// The optional to pass as the operand for the reference. This should + /// be a tag name or immediate atomic value. + /// A instance representing the IREF block type. + public static Block IREF(Argument? operand = null) => NewReference(L5XName.IRef, operand); + + /// + /// Creates a new OREF function block element initialized with appropriate values. + /// + /// The optional to pass as the operand for the reference. This should + /// be a tag name or immediate atomic value. + /// A instance representing the OREF block type. + public static Block OREF(Argument? operand = null) => NewReference(L5XName.ORef, operand); + + /// + /// Creates a new ICON function block element initialized with appropriate values. + /// + /// The required name of the connector element. + /// A instance representing the ICON block type. + public static Block ICON(string name) => NewConnector(L5XName.ICon, name); + + /// + /// Creates a new OCON function block element initialized with appropriate values. + /// + /// The required name of the connector element. + /// A instance representing the OCON block type. + public static Block OCON(string name) => NewConnector(L5XName.OCon, name); + + /// + /// Creates a new AOI with the provided name, operand, and pins + /// + /// The name of the AOI block. + /// The operand of the block. + /// The set of pins for the block. + /// A new instance representing the AOI block type. + public static Block AOI(string name, TagName? operand = null, params string[] pins) => NewAoi(name, operand, pins); + + /// + /// Creates a new AOI with the provided definition and operand tag name. + /// + /// The definition to initialize the block. + /// The backing tag operand for the block. + /// A new instance representing the AOI block type. + public static Block AOI(AddOnInstruction definition, TagName? operand = null) => NewAoi(definition, operand); + + /// + /// Creates a new JSR routine with the provided routine name and optional parmaeters. + /// + /// The name of the routine the block calls. + /// The set of input parmaeters to the routine. + /// The set of output parmaeters from the routine. + /// A new instance representing the JSR block type. + public static Block JSR(string routine, string[]? inputs = null, string[]? outputs = null) => + NewRoutine(routine, inputs, outputs); + + /// + /// Creates a new SBR routine with the provided routine name and input parmaeters. + /// + /// The name of the routine the block calls. + /// The set of input parameters to the routine. + /// A new instance representing the SBR block type. + public static Block SBR(string routine, params string[] parameters) => NewRoutine(L5XName.SBR, routine, parameters); + + /// + /// Creates a new SBR routine with the provided routine name and input parmaeters. + /// + /// The name of the routine the block calls. + /// The set of output parameters from the routine. + /// A new instance representing the RET block type. + public static Block RET(string routine, params string[] parameters) => NewRoutine(L5XName.RET, routine, parameters); + + #endregion + + #region Blocks + + /// + /// Creates a new ABS function block element with the optional operand tag name. + /// + /// The optional tag name to initialize the block with. This will default to the function + /// name with '_01' appended if not provided. + /// A object representing the ABS function. + public static Block ABS(TagName? operand = null) => + NewBlock(nameof(ABS), operand, "Source Destination"); + + /// + /// Returns a ne ACS Block element with the type initialized. + /// + public static Block ACS(TagName? operand = null) => + NewBlock(nameof(ACS), operand, "Source Destination"); + + /// + /// Returns a ne ADD Block element with the type initialized. + /// + public static Block ADD(TagName? operand = null) => + NewBlock(nameof(ADD), operand, "SourceA SourceB Destination"); + + /// + /// Returns a ne ALM Block element with the type initialized. + /// + public static Block ALM(TagName? operand = null) => NewBlock(nameof(ALM), operand, + "In HHAlarm HAlarm LAlarm LLAlarm ROCPosAlarm ROCNegAlarm"); + + /// + /// Returns a new ALMA Block element with the type initialized. + /// + public static Block ALMA(TagName? operand = null) => NewBlock(nameof(ALMA), operand, + "In HHInAlarm HInAlarm LInAlarm LLInAlarm ROCPosInAlarm ROCNegInAlarm HHAcked HAcked LAcked LLAcked ROCPosAcked ROCNegAcked Suppressed Disabled"); + + /// + /// Returns a new ALMD Block element with the type initialized. + /// + public static Block ALMD(TagName? operand = null) => + NewBlock(nameof(ALMD), operand, "In InAlarm Acked Suppressed Disabled"); + + /// + /// Returns a ne AND Block element with the type initialized. + /// + public static Block AND(TagName? operand = null) => + NewBlock(nameof(AND), operand, "SourceA SourceB Destination"); + + /// + /// Returns a ne ASN Block element with the type initialized. + /// + public static Block ASN(TagName? operand = null) => + NewBlock(nameof(ASN), operand, "Source Destination"); + + /// + /// Returns a ne ATN Block element with the type initialized. + /// + public static Block ATN(TagName? operand = null) => + NewBlock(nameof(ATN), operand, "Source Destination"); + + /// + /// Returns a new BAND Block element with the type initialized. + /// + public static Block BAND(TagName? operand = null) => + NewBlock(nameof(BAND), operand, "In1 In2 In3 In4 Out"); + + /// + /// Returns a new BNOT Block element with the type initialized. + /// + public static Block BNOT(TagName? operand = null) => NewBlock(nameof(BNOT), operand, "In Out"); + + /// + /// Returns a ne BOR Block element with the type initialized. + /// + public static Block BOR(TagName? operand = null) => + NewBlock(nameof(BOR), operand, "In1 In2 In3 In4 Out"); + + /// + /// Returns a new BTDT Block element with the type initialized. + /// + public static Block BTDT(TagName? operand = null) => NewBlock(nameof(BTDT), operand, + "Source SourceBit Length DestBit Target Dest"); + + /// + /// Returns a new BXOR Block element with the type initialized. + /// + public static Block BXOR(TagName? operand = null) => NewBlock(nameof(BXOR), operand, "In1 In2 Out"); + + /// + /// Returns a ne COS Block element with the type initialized. + /// + public static Block COS(TagName? operand = null) => NewBlock(nameof(COS), operand, "Source Dest"); + + /// + /// Returns a new CTUD Block element with the type initialized. + /// + public static Block CTUD(TagName? operand = null) => + NewBlock(nameof(CTUD), operand, "CUEnable CDEnable PRE Reset ACC DN"); + + /// + /// Returns a new D2SD Block element with the type initialized. + /// + public static Block D2SD(TagName? operand = null) => NewBlock(nameof(D2SD), operand, + "ProgCommand State0Perm State1Perm FB0 FB1 HandFB ProgProgReq ProgOperReq ProgOverrideReq ProgHandReq Out Device0State Device1State CommandStatus FaultAlarm ModeAlarm ProgOper Override Hand"); + + /// + /// Returns a new D3SD Block element with the type initialized. + /// + public static Block D3SD(TagName? operand = null) => NewBlock(nameof(D3SD), operand, + "Prog0Command Prog1Command Prog2Command State0Perm State1Perm State2Perm FB0 FB1 FB2 FB3 HandFB0 HandFB1 HandFB2 ProgProgReq ProgOperReq ProgOverrideReq ProgHandReq Out0 Out1 Out2 Device0State Device1State Device2State Command0Status Command1Status Command2Status FaultAlarm ModeAlarm ProgOper Override Hand"); + + /// + /// Returns a new DEDT Block element with the type initialized. + /// + public static Block DEDT(TagName? operand = null) => NewBlock(nameof(DEDT), operand, "In Out"); + + /// + /// Returns a ne DEG Block element with the type initialized. + /// + public static Block DEG(TagName? operand = null) => NewBlock(nameof(DEG), operand, "Source Dest"); + + /// + /// Returns a new DERV Block element with the type initialized. + /// + public static Block DERV(TagName? operand = null) => + NewBlock(nameof(DERV), operand, "In ByPass Out"); + + /// + /// Returns a ne DFF Block element with the type initialized. + /// + public static Block DFF(TagName? operand = null) => + NewBlock(nameof(DFF), operand, "D Clear Clock Q QNot"); + + /// + /// Returns a ne DIV Block element with the type initialized. + /// + public static Block DIV(TagName? operand = null) => + NewBlock(nameof(DIV), operand, "SourceA SourceB Dest"); + + /// + /// Returns a new ESEL Block element with the type initialized. + /// + public static Block ESEL(TagName? operand = null) => NewBlock(nameof(ESEL), operand, + "In1 In2 In3 In4 In5 In6 ProgSelector ProgProgReq ProgOperReq ProgOverrideReq Out SelectedIn ProgOper Override"); + + /// + /// Returns a ne EQU Block element with the type initialized. + /// + public static Block EQU(TagName? operand = null) => NewBlock(nameof(EQU), operand, "SourceA SourceB"); + + /// + /// Returns a new FGEN Block element with the type initialized. + /// + public static Block FGEN(TagName? operand = null) => NewBlock(nameof(FGEN), operand, "In Out"); + + /// + /// Returns a ne FRD Block element with the type initialized. + /// + public static Block FRD(TagName? operand = null) => NewBlock(nameof(FRD), operand, "Source Dest"); + + /// + /// Returns a ne GEQ Block element with the type initialized. + /// + public static Block GEQ(TagName? operand = null) => NewBlock(nameof(GEQ), operand, "SourceA SourceB"); + + /// + /// Returns a ne GRT Block element with the type initialized. + /// + public static Block GRT(TagName? operand = null) => NewBlock(nameof(GRT), operand, "SourceA SourceB"); + + /// + /// Returns a ne HLL Block element with the type initialized. + /// + public static Block HLL(TagName? operand = null) => + NewBlock(nameof(HLL), operand, "In Out HighAlarm LowAlarm"); + + /// + /// Returns a ne HPF Block element with the type initialized. + /// + public static Block HPF(TagName? operand = null) => NewBlock(nameof(HPF), operand, "In Out"); + + /// + /// Returns a new INTG Block element with the type initialized. + /// + public static Block INTG(TagName? operand = null) => NewBlock(nameof(INTG), operand, "In Out"); + + /// + /// Returns a new JKFF Block element with the type initialized. + /// + public static Block JKFF(TagName? operand = null) => + NewBlock(nameof(JKFF), operand, "Clear Clock Q QNot"); + + /// + /// Returns a ne LEQ Block element with the type initialized. + /// + public static Block LEQ(TagName? operand = null) => NewBlock(nameof(LEQ), operand, "SourceA SourceB"); + + /// + /// Returns a ne LES Block element with the type initialized. + /// + public static Block LES(TagName? operand = null) => NewBlock(nameof(LES), operand, "SourceA SourceB"); + + /// + /// Returns a ne LIM Block element with the type initialized. + /// + public static Block LIM(TagName? operand = null) => + NewBlock(nameof(LIM), operand, "LowLimit Test HighLimit"); + + /// + /// Returns a n LN Block element with the type initialized. + /// + public static Block LN(TagName? operand = null) => NewBlock(nameof(LN), operand, "Source Dest"); + + /// + /// Returns a ne LOG Block element with the type initialized. + /// + public static Block LOG(TagName? operand = null) => NewBlock(nameof(LOG), operand, "Source Dest"); + + /// + /// Returns a ne LPF Block element with the type initialized. + /// + public static Block LPF(TagName? operand = null) => NewBlock(nameof(LPF), operand, "In Out"); + + /// + /// Returns a new MAVE Block element with the type initialized. + /// + public static Block MAVE(TagName? operand = null) => NewBlock(nameof(MAVE), operand, "In Out"); + + /// + /// Returns a new MAXC Block element with the type initialized. + /// + public static Block MAXC(TagName? operand = null) => + NewBlock(nameof(MAXC), operand, "In Reset ResetValue Out"); + + /// + /// Returns a ne MEQ Block element with the type initialized. + /// + public static Block MEQ(TagName? operand = null) => + NewBlock(nameof(MEQ), operand, "Source Mask Compare"); + + /// + /// Returns a new MINC Block element with the type initialized. + /// + public static Block MINC(TagName? operand = null) => + NewBlock(nameof(MINC), operand, "In Reset ResetValue Out"); + + /// + /// Returns a ne MOD Block element with the type initialized. + /// + public static Block MOD(TagName? operand = null) => + NewBlock(nameof(MOD), operand, "SourceA SourceB Dest"); + + /// + /// Returns a new MSTD Block element with the type initialized. + /// + public static Block MSTD(TagName? operand = null) => + NewBlock(nameof(MSTD), operand, "In SampleEnable Out"); + + /// + /// Returns a ne MUL Block element with the type initialized. + /// + public static Block MUL(TagName? operand = null) => + NewBlock(nameof(MUL), operand, "SourceA SourceB Dest"); + + /// + /// Returns a ne MUX Block element with the type initialized. + /// + public static Block MUX(TagName? operand = null) => NewBlock(nameof(MUX), operand, + "In1 In2 In3 In4 In5 In6 In7 In8 Selector Out"); + + /// + /// Returns a new MVMT Block element with the type initialized. + /// + public static Block MVMT(TagName? operand = null) => + NewBlock(nameof(MVMT), operand, "Source Mask Target Dest"); + + /// + /// Returns a ne NEG Block element with the type initialized. + /// + public static Block NEG(TagName? operand = null) => NewBlock(nameof(NEG), operand, "Source Dest"); + + /// + /// Returns a ne NEQ Block element with the type initialized. + /// + public static Block NEQ(TagName? operand = null) => NewBlock(nameof(NEQ), operand, "SourceA SourceB"); + + /// + /// Returns a ne NOT Block element with the type initialized. + /// + public static Block NOT(TagName? operand = null) => NewBlock(nameof(NOT), operand, "Source Dest"); + + /// + /// Returns a new NTCH Block element with the type initialized. + /// + public static Block NTCH(TagName? operand = null) => NewBlock(nameof(NTCH), operand, "In Out"); + + /// + /// Returns a n OR Block element with the type initialized. + /// + public static Block OR(TagName? operand = null) => + NewBlock(nameof(OR), operand, "SourceA SourceB Dest"); + + /// + /// Returns a new OSFI Block element with the type initialized. + /// + public static Block OSFI(TagName? operand = null) => + NewBlock(nameof(OSFI), operand, "InputBit OutputBit"); + + /// + /// Returns a new OSRI Block element with the type initialized. + /// + public static Block OSRI(TagName? operand = null) => + NewBlock(nameof(OSRI), operand, "InputBit OutputBit"); + + /// + /// Returns a n PI Block element with the type initialized. + /// + public static Block PI(TagName? operand = null) => NewBlock(nameof(PI), operand, "In Out"); + + /// + /// Returns a new PIDE Block element with the type initialized. + /// + public static Block PIDE(TagName? operand = null) => NewBlock(nameof(PIDE), operand, + "PV SPProg SPCascade RatioProg CVProg FF HandFB ProgProgReq ProgOperReq ProgCasRatReq ProgAutoReq ProgManualReq ProgOverrideReq ProgHandReq CVEU SP PVHHAlarm PVHAlarm PVLAlarm PVLLAlarm PVROCPosAlarm PVROCNegAlarm DevHHAlarm DevHAlarm DevLAlarm DevLLAlarm ProgOper CasRat Auto Manual Override Hand"); + + /// + /// Returns a new PMUL Block element with the type initialized. + /// + public static Block PMUL(TagName? operand = null) => NewBlock(nameof(PMUL), operand, "In Multiplier Out"); + + /// + /// Returns a new POSP Block element with the type initialized. + /// + public static Block POSP(TagName? operand = null) => NewBlock(nameof(POSP), operand, + "SP Position OpenedFB ClosedFB OpenOut CloseOut"); + + /// + /// Returns a ne RAD Block element with the type initialized. + /// + public static Block RAD(TagName? operand = null) => NewBlock(nameof(RAD), operand, "Source Dest"); + + /// + /// Returns a new RESD Block element with the type initialized. + /// + public static Block RESD(TagName? operand = null) => + NewBlock(nameof(RESD), operand, "Set Reset Out OutNot"); + + /// + /// Returns a new RLIM Block element with the type initialized. + /// + public static Block RLIM(TagName? operand = null) => + NewBlock(nameof(RLIM), operand, "In ByPass Out"); + + /// + /// Returns a new RMPS Block element with the type initialized. + /// + public static Block RMPS(TagName? operand = null) => NewBlock(nameof(RMPS), operand, + "PV CurrentSegProg OutProg SoakTimeProg ProgProgReq ProgOperReq ProgAutoReq ProgManualReq ProgHoldReq Out CurrentSeg SoakTimeLeft GuarRampOn GuarSoakOn ProgOper Auto Manual Hold"); + + /// + /// Returns a new RTOR Block element with the type initialized. + /// + public static Block RTOR(TagName? operand = null) => + NewBlock(nameof(RTOR), operand, "TimerEnable PRE Reset ACC DN"); + + /// + /// Returns a ne SCL Block element with the type initialized. + /// + public static Block SCL(TagName? operand = null) => NewBlock(nameof(SCL), operand, "In Out"); + + /// + /// Returns a new SCRV Block element with the type initialized. + /// + public static Block SCRV(TagName? operand = null) => NewBlock(nameof(SCRV), operand, "In Out"); + + /// + /// Returns a ne SEL Block element with the type initialized. + /// + public static Block SEL(TagName? operand = null) => + NewBlock(nameof(SEL), operand, "In1 In2 SelectorIn Out"); + + /// + /// Returns a new SETD Block element with the type initialized. + /// + public static Block SETD(TagName? operand = null) => + NewBlock(nameof(SETD), operand, "Set Reset Out OutNot"); + + /// + /// Returns a ne SIN Block element with the type initialized. + /// + public static Block SIN(TagName? operand = null) => + NewBlock(nameof(SIN), operand, "Source Destination"); + + /// + /// Returns a new SNEG Block element with the type initialized. + /// + public static Block SNEG(TagName? operand = null) => + NewBlock(nameof(SNEG), operand, "In NegateEnable Out"); + + /// + /// Returns a ne SOC Block element with the type initialized. + /// + public static Block SOC(TagName? operand = null) => NewBlock(nameof(SOC), operand, "In Out"); + + /// + /// Returns a ne SQR Block element with the type initialized. + /// + public static Block SQR(TagName? operand = null) => NewBlock(nameof(SQR), operand, "Source Dest"); + + /// + /// Returns a new SRTP Block element with the type initialized. + /// + public static Block SRTP(TagName? operand = null) => NewBlock(nameof(SRTP), operand, + "In HeatOut CoolOut HeatTimePercent CoolTimePercent"); + + /// + /// Returns a new SSUM Block element with the type initialized. + /// + public static Block SSUM(TagName? operand = null) => NewBlock(nameof(SSUM), operand, + "In1 Select1 In2 Select2 In3 Select3 In4 Select4 Out"); + + /// + /// Returns a ne SUB Block element with the type initialized. + /// + public static Block SUB(TagName? operand = null) => + NewBlock(nameof(SUB), operand, "SourceA SourceB Dest"); + + /// + /// Returns a ne TAN Block element with the type initialized. + /// + public static Block TAN(TagName? operand = null) => NewBlock(nameof(TAN), operand, "Source Dest"); + + /// + /// Returns a ne TOD Block element with the type initialized. + /// + public static Block TOD(TagName? operand = null) => NewBlock(nameof(TOD), operand, "Source Dest"); + + /// + /// Returns a new TOFR Block element with the type initialized. + /// + public static Block TOFR(TagName? operand = null) => + NewBlock(nameof(TOFR), operand, "TimerEnable PRE Reset ACC DN"); + + /// + /// Returns a new TONR Block element with the type initialized. + /// + public static Block TONR(TagName? operand = null) => + NewBlock(nameof(TONR), operand, "TimerEnable PRE Reset ACC DN"); + + /// + /// Returns a ne TOT Block element with the type initialized. + /// + public static Block TOT(TagName? operand = null) => NewBlock(nameof(TOT), operand, + "In ProgProgReq ProgOperReq ProgStartReq ProgStopReq ProgResetReq Total OldTotal ProgOper RunStop ProgResetDone TargetFlag TargetDev1Flag TargetDev2Flag"); + + /// + /// Returns a ne TRN Block element with the type initialized. + /// + public static Block TRN(TagName? operand = null) => NewBlock(nameof(TRN), operand, "Source Dest"); + + /// + /// Returns a new UPDN Block element with the type initialized. + /// + public static Block UPDN(TagName? operand = null) => + NewBlock(nameof(UPDN), operand, "InPlus InMinus Out"); + + /// + /// Returns a ne XOR Block element with the type initialized. + /// + public static Block XOR(TagName? operand = null) => + NewBlock(nameof(XOR), operand, "SourceA SourceB Dest"); + + /// + /// Returns a ne XPY Block element with the type initialized. + /// + public static Block XPY(TagName? operand = null) => + NewBlock(nameof(XPY), operand, "SourceA SourceB Dest"); + + #endregion + + #region Functions + + /// + /// Creates a new ABS__F function block element initialized with appropriate values. + /// + /// A instance representing the ABS__F function block. + public static Block ABS__F() => NewFunction(nameof(ABS__F)); + + /// + /// Creates a new ADD__F function block element initialized with appropriate values. + /// + /// A instance representing the ADD__F function block. + public static Block ADD__F() => NewFunction(nameof(ADD__F)); + + /// + /// Creates a new BAND__F function block element initialized with appropriate values. + /// + /// A instance representing the BAND__F function block. + public static Block BAND__F() => NewFunction(nameof(BAND__F)); + + /// + /// Creates a new BNOT__F function block element initialized with appropriate values. + /// + /// A instance representing the BNOT__F function block. + public static Block BNOT__F() => NewFunction(nameof(BNOT__F)); + + /// + /// Creates a new BXOR__F function block element initialized with appropriate values. + /// + /// A instance representing the BXOR__F function block. + public static Block BXOR__F() => NewFunction(nameof(BXOR__F)); + + /// + /// Creates a new DIV__F function block element initialized with appropriate values. + /// + /// A instance representing the DIV__F function block. + public static Block DIV__F() => NewFunction(nameof(DIV__F)); + + /// + /// Creates a new EQU__F function block element initialized with appropriate values. + /// + /// A instance representing the EQU__F function block. + public static Block EQU__F() => NewFunction(nameof(EQU__F)); + + /// + /// Creates a new GEQ__F function block element initialized with appropriate values. + /// + /// A instance representing the GEQ__F function block. + public static Block GEQ__F() => NewFunction(nameof(GEQ__F)); + + /// + /// Creates a new GRT__F function block element initialized with appropriate values. + /// + /// A instance representing the GRT__F function block. + public static Block GRT__F() => NewFunction(nameof(GRT__F)); + + /// + /// Creates a new LEQ__F function block element initialized with appropriate values. + /// + /// A instance representing the LEQ__F function block. + public static Block LEQ__F() => NewFunction(nameof(LEQ__F)); + + /// + /// Creates a new LES__F function block element initialized with appropriate values. + /// + /// A instance representing the LES__F function block. + public static Block LES__F() => NewFunction(nameof(LES__F)); + + /// + /// Creates a new LIM__F function block element initialized with appropriate values. + /// + /// A instance representing the LIM__F function block. + public static Block LIM__F() => NewFunction(nameof(LIM__F)); + + /// + /// Creates a new MEQ__F function block element initialized with appropriate values. + /// + /// A instance representing the MEQ__F function block. + public static Block MEQ__F() => NewFunction(nameof(MEQ__F)); + + /// + /// Creates a new MOD__F function block element initialized with appropriate values. + /// + /// A instance representing the MOD__F function block. + public static Block MOD__F() => NewFunction(nameof(MOD__F)); + + /// + /// Creates a new MUL__F function block element initialized with appropriate values. + /// + /// A instance representing the MUL__F function block. + public static Block MUL__F() => NewFunction(nameof(MUL__F)); + + /// + /// Creates a new NEG__F function block element initialized with appropriate values. + /// + /// A instance representing the NEG__F function block. + public static Block NEG__F() => NewFunction(nameof(NEG__F)); + + /// + /// Creates a new NEQ__F function block element initialized with appropriate values. + /// + /// A instance representing the NEQ__F function block. + public static Block NEQ__F() => NewFunction(nameof(NEQ__F)); + + /// + /// Creates a new SQR__F function block element initialized with appropriate values. + /// + /// A instance representing the SQR__F function block. + public static Block SQR__F() => NewFunction(nameof(SQR__F)); + + /// + /// Creates a new SUB__F function block element initialized with appropriate values. + /// + /// A instance representing the SUB__F function block. + public static Block SUB__F() => NewFunction(nameof(SUB__F)); + + #endregion + + #region Internal + + private static Block NewReference(string type, Argument? operand = null) + { + var element = new XElement(type); + element.Add(new XAttribute(L5XName.ID, 0)); + element.Add(new XAttribute(L5XName.X, 0)); + element.Add(new XAttribute(L5XName.Y, 0)); + element.Add(new XAttribute(L5XName.Operand, operand ?? Argument.Empty)); + return new Block(element); + } + + private static Block NewConnector(string type, string name) + { + var element = new XElement(type); + element.Add(new XAttribute(L5XName.ID, 0)); + element.Add(new XAttribute(L5XName.X, 0)); + element.Add(new XAttribute(L5XName.Y, 0)); + element.Add(new XAttribute(L5XName.Name, name)); + return new Block(element); + } + + private static Block NewBlock(string type, TagName? operand = null, string? pins = null) + { + var element = new XElement(L5XName.Block); + element.Add(new XAttribute(L5XName.Type, type)); + element.Add(new XAttribute(L5XName.ID, 0)); + element.Add(new XAttribute(L5XName.X, 0)); + element.Add(new XAttribute(L5XName.Y, 0)); + element.Add(new XAttribute(L5XName.Operand, operand ?? $"{type}_01")); + element.Add(new XAttribute(L5XName.VisiblePins, pins ?? string.Empty)); + return new Block(element); + } + + private static Block NewFunction(string type) + { + var element = new XElement(L5XName.Function); + element.Add(new XAttribute(L5XName.Type, type)); + element.Add(new XAttribute(L5XName.ID, 0)); + element.Add(new XAttribute(L5XName.X, 0)); + element.Add(new XAttribute(L5XName.Y, 0)); + return new Block(element); + } + + private static Block NewAoi(string type, TagName? operand = null, params string[] pins) + { + var element = new XElement(L5XName.AddOnInstruction); + element.Add(new XAttribute(L5XName.Name, type)); + element.Add(new XAttribute(L5XName.ID, 0)); + element.Add(new XAttribute(L5XName.X, 0)); + element.Add(new XAttribute(L5XName.Y, 0)); + element.Add(new XAttribute(L5XName.Operand, operand ?? Argument.Empty)); + element.Add(new XAttribute(L5XName.VisiblePins, string.Join(' ', pins) ?? string.Empty)); + return new Block(element); + } + + private static Block NewAoi(AddOnInstruction definition, TagName? operand = null) + { + var element = new XElement(L5XName.AddOnInstruction); + element.Add(new XAttribute(L5XName.Name, definition.Name)); + element.Add(new XAttribute(L5XName.ID, 0)); + element.Add(new XAttribute(L5XName.X, 0)); + element.Add(new XAttribute(L5XName.Y, 0)); + element.Add(new XAttribute(L5XName.Operand, operand ?? Argument.Empty)); + + var pins = string.Join(' ', definition.Parameters.Where(p => p.Visible == true).Select(p => p.Name)); + element.Add(new XAttribute(L5XName.VisiblePins, pins)); + + return new Block(element); + } + + private static Block NewRoutine(string routine, string[]? inputs = null, string[]? outputs = null) + { + var element = new XElement(L5XName.JSR); + element.Add(new XAttribute(L5XName.ID, 0)); + element.Add(new XAttribute(L5XName.X, 0)); + element.Add(new XAttribute(L5XName.Y, 0)); + element.Add(new XAttribute(L5XName.Routine, routine)); + element.Add(new XAttribute(L5XName.In, string.Join(' ', inputs) ?? string.Empty)); + element.Add(new XAttribute(L5XName.Ret, string.Join(' ', outputs) ?? string.Empty)); + return new Block(element); + } + + private static Block NewRoutine(string type, string routine, params string[] parameters) + { + var element = new XElement(type); + element.Add(new XAttribute(L5XName.ID, 0)); + element.Add(new XAttribute(L5XName.X, 0)); + element.Add(new XAttribute(L5XName.Y, 0)); + element.Add(new XAttribute(L5XName.Routine, routine)); + element.Add(new XAttribute(L5XName.VisiblePins, string.Join(' ', parameters) ?? string.Empty)); + return new Block(element); + } + + private IEnumerable> Endpoints(string? param = null) + { + if (Sheet is null) return Enumerable.Empty>(); + + var arguments = new List>(); + + arguments.AddRange(GetInputs(Sheet, param)); + arguments.AddRange(GetOutputs(Sheet, param)); + + return arguments; + } + + private IEnumerable> GetInputs(Sheet sheet, string? param) + { + var arguments = new List>(); + + var inputs = sheet.Wires().Where(w => w.IsTo(ID, param)); + + foreach (var wire in inputs) + { + var block = sheet.Block(wire.FromID); + if (block is null) continue; + + if (block.Type == L5XName.OCon) + { + arguments.AddRange(GetPair()?.Endpoints() ?? Enumerable.Empty>()); + continue; + } + + var arg = block.GetArguments(wire.FromParam); + arguments.Add(new KeyValuePair(arg, block.Type)); + } + + return arguments; + } + + private IEnumerable> GetOutputs(Sheet sheet, string? param) + { + var arguments = new List>(); + + var wires = sheet.Wires().Where(w => w.IsFrom(ID, param)); + + foreach (var wire in wires) + { + var block = sheet.Block(wire.ToID); + if (block is null) continue; + + if (block.Type == L5XName.ICon) + { + arguments.AddRange(GetPair()?.Endpoints() ?? Enumerable.Empty>()); + continue; + } + + var arg = block.GetArguments(wire.ToParam); + arguments.Add(new KeyValuePair(arg, block.Type)); + } + + return arguments; + } + + private TagName GetArguments(string? param = null) + { + var operand = Operand is not null && Operand.IsTag ? new TagName(Operand.ToString()) : TagName.Empty; + var parameter = param is not null ? new TagName(param) : TagName.Empty; + return TagName.Concat(operand, parameter); + + /*return L5XType switch + { + L5XName.IRef => Operand is not null && Operand.IsTag ? (TagName)Operand : TagName.Empty, + L5XName.ORef => Operand is not null && Operand.IsTag ? (TagName)Operand : TagName.Empty, + L5XName.Block => Operand is not null && param is not null ? TagName.Concat(Operand.ToString(), param) : TagName.Empty, + L5XName.Function => param is not null ? new TagName(param) : Argument.Empty, + L5XName.AddOnInstruction => new[] + { + Operand is not null && param is not null ? TagName.Concat(Operand.ToString(), param) : Argument.Empty + }, + L5XName.JSR => new[] { param is not null ? new TagName(param) : Argument.Empty }, + L5XName.SBR => new[] { param is not null ? new TagName(param) : Argument.Empty }, + L5XName.RET => new[] { param is not null ? new TagName(param) : Argument.Empty }, + _ => new[] { Argument.Empty } + };*/ + } + + /// + /// Gets the connector block (ICON/OCON) that is the pair/compliment to this connector block. + /// + private Block? GetPair() => Sheet?.Blocks().FirstOrDefault(b => b.ID != ID && Equals(b.Operand, Operand)); + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Chart.cs b/src/L5Sharp/Elements/Chart.cs new file mode 100644 index 00000000..98b3f682 --- /dev/null +++ b/src/L5Sharp/Elements/Chart.cs @@ -0,0 +1,53 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// Represents the containing element for Sequential Function Chart (SFC) block elements. This class inherits +/// the common logic and allows easier manipulation for SFC elements +/// within a SFC type routine. +/// +public class Chart : Diagram +{ + /// + /// Creates a new with default values. + /// + public Chart() + { + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// block is null. + public Chart(XElement element) : base(element) + { + } + + /// + public override int Number => 0; + + /// + public override IEnumerable References() => Enumerable.Empty(); + + /// + protected override IEnumerable Ordering() + { + return new List + { + L5XName.Step, + L5XName.Transition, + L5XName.Condition, + L5XName.SbrRet, + L5XName.Stop, + L5XName.Branch, + L5XName.DirectedLink, + L5XName.TextBox, + L5XName.Attachment + }; + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Communications.cs b/src/L5Sharp/Elements/Communications.cs new file mode 100644 index 00000000..99e2616b --- /dev/null +++ b/src/L5Sharp/Elements/Communications.cs @@ -0,0 +1,51 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A component of a that represents the properties and data of the connection to the field device. +/// +public class Communications : LogixElement +{ + /// + /// Creates a new with default values. + /// + public Communications() + { + Element.Add(new XElement(L5XName.ConfigTag)); + Connections = new LogixContainer(); + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Communications(XElement element) : base(element) + { + } + + /// + /// A Tag component containing the configuration data for the module. + /// + /// A component representing the complex module defined data structure. + public Tag? ConfigTag + { + get => GetComplex(); + set => SetComplex(value); + } + + /// + /// A collection of defining the input and output connection specific to the module. + /// + /// A of objects. + /// + /// Each connection may contain input or output tag structures, as well as several other configuration properties. + /// + public LogixContainer Connections + { + get => GetContainer(); + set => SetContainer(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Connection.cs b/src/L5Sharp/Elements/Connection.cs new file mode 100644 index 00000000..8201ec62 --- /dev/null +++ b/src/L5Sharp/Elements/Connection.cs @@ -0,0 +1,197 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A component of a that represents the properties and data of the connection to the field device. +/// +public class Connection : LogixElement +{ + /// + /// Creates a new with default values. + /// + public Connection() + { + Name = string.Empty; + RPI = 0; + Type = ConnectionType.Unknown; + Priority = ConnectionPriority.Scheduled; + InputConnectionType = TransmissionType.Multicast; + InputProductionTrigger = ProductionTrigger.Cyclic; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Connection(XElement element) : base(element) + { + } + + /// + /// Gets the name of the component. + /// + public string Name + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// Gets the value of the Request Packet Interval for the . + /// + public int RPI + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// Gets the input connection point for the primary . + /// + public ushort InputCxnPoint + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the input size for the . + /// + public ushort InputSize + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the output connection point for the primary . + /// + public ushort OutputCxnPoint + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the output size for the . + /// + public ushort OutputSize + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the value for the . + /// + public ConnectionType? Type + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the value for the . + /// + public ConnectionPriority? Priority + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the value for the . + /// + public TransmissionType? InputConnectionType + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets a value indicating whether the output is a redundant owner. + /// + public bool OutputRedundantOwner + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the value for the . + /// + public ProductionTrigger? InputProductionTrigger + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the value indicating whether the EtherNet/IP connection is unicast. + /// + public bool Unicast + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the value of the Event ID used in conjunction with an event task for the . + /// + public int? EventId + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The character used to define the tag name for the element. + /// + /// A containing the suffix character if it exists; Otherwise, will default as 'I'. + /// + /// This value is used in determining the module tag name. Not all modules serialize this property, + /// but still use 'I' as the suffix character for input tags. Therefore, we will default to 'I' if not found. + /// + public string InputTagSuffix + { + get => GetValue() ?? "I"; + set => SetValue(value); + } + + /// + /// The character used to define the tag name for the element. + /// + /// A containing the suffix character if it exists; Otherwise, will default as 'O'. + /// + /// This value is used in determining the module tag name. Not all modules serialize this property, + /// but still use 'O' as the suffix character for output tags. Therefore, we will default to 'O' if not found. + /// + public string OutputTagSuffix + { + get => GetValue() ?? "O"; + set => SetValue(value); + } + + /// + /// Gets the Tag that represents the input channel data for the element. + /// + /// A component containing the module defined data structure for the input connection data. + public Tag? InputTag + { + get => GetComplex(); + set => SetComplex(value?.Convert(L5XName.InputTag)); + } + + /// + /// Gets the Tag that represents the output channel data for the element. + /// + /// A component containing the module defined data structure for the output connection data. + public Tag? OutputTag + { + get => GetComplex(); + set => SetComplex(value?.Convert(L5XName.OutputTag)); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/DataTypeMember.cs b/src/L5Sharp/Elements/DataTypeMember.cs new file mode 100644 index 00000000..e817f8a3 --- /dev/null +++ b/src/L5Sharp/Elements/DataTypeMember.cs @@ -0,0 +1,162 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// An element of the that makes up the structure of the user defined type. +/// +/// +[L5XType(L5XName.Member, L5XName.Members)] +public class DataTypeMember : LogixElement +{ + /// + /// Creates a new with default values. + /// + public DataTypeMember() + { + Name = string.Empty; + DataType = string.Empty; + Dimension = Dimensions.Empty; + Radix = Radix.Null; + ExternalAccess = ExternalAccess.ReadWrite; + Hidden = false; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public DataTypeMember(XElement element) : base(element) + { + } + + /// + /// The unique name of the Member. + /// + /// A representing the component name. This property is required for valid elements. + public string Name + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The description of the Member. + /// + /// A containing the component description if it exists; Otherwise, null. + public string? Description + { + get => GetProperty(); + set => SetDescription(value); + } + + /// + /// The name of the data type of the Member. + /// + /// + /// A containing the data type name of the member. Default is . + /// This property is required for valid elements. + /// + public string DataType + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The array dimension of the Member. + /// + /// + /// A representing the array dimensions of the member. Default is . + /// + public Dimensions? Dimension + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The radix value format of the Member. + /// + /// + /// A representing the value type format of the member. Default is . + /// + public Radix? Radix + { + get => GetValue(); + set => SetValue(value?.Value); + } + + /// + /// The external access of the Member. + /// + /// + /// A representing read/write access of the member. + /// Default is . + /// + public ExternalAccess? ExternalAccess + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// An flag indicating whether the Member is a hidden backing member for another boolean member of the data type. + /// + /// true if member is a hidden member of the type, false if not, and null if the attribute + /// does not exist for the underlying element. + /// + /// This attribute is not required for valid elements and importing to work. Logix will internally + /// generate backing members for boolean type members. + /// + public bool? Hidden + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The name of the hidden member that is the backing member for this Member. + /// + /// + /// A representing the name of the hidden member. + /// null if the attribute does not exist for the underlying element + /// + /// + /// This attribute is not required for valid elements and importing to work. Logix will internally + /// generate backing members for boolean type members. Logix appends 10 Zs to the hidden + /// members. + /// + public string? Target + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The bit number of the hidden member that this boolean member maps to or is backed by. + /// + /// A zero-based integer representing the backing bit of the hidden member which corresponds to this member. + /// null if the attribute does not exist for the underlying element + /// + /// This attribute is not required for valid elements and importing to work. Logix will internally + /// generate backing members for boolean type members. + /// + public int? BitNumber + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the parent component that this Member is contained by. If this + /// Member is not contained or attached to a L5X document, this returns null. + /// + /// A representing the parent type for this Member. + public DataType? Parent => GetAncestor(); +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Diagram.cs b/src/L5Sharp/Elements/Diagram.cs new file mode 100644 index 00000000..938ba3ee --- /dev/null +++ b/src/L5Sharp/Elements/Diagram.cs @@ -0,0 +1,143 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; +using JetBrains.Annotations; + +namespace L5Sharp.Core; + +/// +/// An abstract class for FBD and SFC routine code elements. Both routine types can be viewed as a container with a +/// collection of child elements of different types. This abstraction defines the common interface for adding, +/// removing, and accessing child block types from the Diagram. +/// +[PublicAPI] +public abstract class Diagram : LogixCode +{ + /// + /// The defined order of all child diagram elements. This is required so we can add elements in the correct position. + /// + /// An of element name. + protected abstract IEnumerable Ordering(); + + /// + /// Creates a new with default values. + /// + protected Diagram() + { + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// block is null. + protected Diagram(XElement element) : base(element) + { + } + + /// + /// Add a element with the given text to the diagram. + /// + /// The text to be added to the TextBox element. + /// The ID of the added TextBox element. + public uint Add(string text) + { + var id = NextAvailableId(); + + var element = new XElement(L5XName.TextBox); + element.Add(new XAttribute(L5XName.ID, id)); + element.Add(new XAttribute(L5XName.X, 0)); + element.Add(new XAttribute(L5XName.Y, 0)); + element.Add(new XAttribute(L5XName.Width, 0)); + + if (!string.IsNullOrEmpty(text)) + { + element.Add(new XElement(L5XName.Text), new XCData(text)); + } + + Element.Add(element); + SortBlocks(); + return id; + } + + /// + /// Add a element at the specified position (x, y) with the given text to the diagram. + /// + /// The text to be added to the TextBox element. + /// The x-coordinate of the TextBox element. + /// The y-coordinate of the TextBox element. + /// The ID of the added TextBox element. + public uint AddAt(string text, uint x, uint y) + { + var id = NextAvailableId(); + + var element = new XElement(L5XName.TextBox); + element.Add(new XAttribute(L5XName.ID, id)); + element.Add(new XAttribute(L5XName.X, x)); + element.Add(new XAttribute(L5XName.Y, y)); + element.Add(new XAttribute(L5XName.Width, 0)); + + if (!string.IsNullOrEmpty(text)) + { + element.Add(new XElement(L5XName.Text), new XCData(text)); + } + + Element.Add(element); + SortBlocks(); + return id; + } + + /// + /// Retrieves all child of the current diagram. + /// + /// A collection of in the diagram. + /// + /// This is a generic method for retrieving whatever child elements exist on the diagram. Derived classes + /// will implement more specific methods for retrieving more concrete elements. + /// + public IEnumerable Elements() + { + return Element.Elements().Select(e => e.Deserialize()); + } + + /// + /// Joins the defined ordered set of element names with the child elements of the diagram, and replaces all current + /// nodes with the same set of order nodes. This maintains the order of the diagram elements base on the derived + /// classes order requirements. + /// + protected void SortBlocks() + { + var ordered = Ordering().Join(Element.Elements(), s => s, e => e.Name.LocalName, (_, e) => e).ToList(); + Element.ReplaceNodes(ordered); + } + + /// + /// Given a new diagram block, assign the Id to the next available Id only if it is not already used. + /// + protected void AssignId(XElement element) + { + var id = element.Attribute(L5XName.ID)?.Value.Parse() ?? 0; + + if (IsUsed(id)) + { + element.SetAttributeValue(L5XName.ID, NextAvailableId()); + } + } + + /// + /// Gets the next highest ID value with the given set of diagram elements. + /// + private uint NextAvailableId() + { + return Element.Elements().Select(e => e.Attribute(L5XName.ID)?.Value.Parse()).Max() + 1 ?? 0; + } + + /// + /// Determines if the provided Id is already used by another diagram element. + /// + private bool IsUsed(uint id) + { + return Element.Elements().Any(e => e.Attribute(L5XName.ID)?.Value.Parse() == id); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/DiagramElement.cs b/src/L5Sharp/Elements/DiagramElement.cs new file mode 100644 index 00000000..49e3e0fc --- /dev/null +++ b/src/L5Sharp/Elements/DiagramElement.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A base class for all FBD/SFC routine elements within a containing Diagram. This base class simply +/// contains some of the common properties and functions that all FBD/SFC elements share, +/// such as X and Y coordinates, and ID. +/// +public abstract class DiagramElement : LogixElement, ILogixReferencable +{ + /// + /// Creates a new with default values and initializes the required \ + /// diagram element properties ID, X, and Y to 0. + /// + protected DiagramElement() + { + Element.SetAttributeValue(L5XName.ID, 0); + Element.SetAttributeValue(L5XName.X, 0); + Element.SetAttributeValue(L5XName.Y, 0); + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + protected DiagramElement(XElement element) : base(element) + { + } + + /// + /// The unique identifier of the within it's containing diagram. + /// + /// A zero based representing the block id. + /// + /// This doesn't need to be set explicitly by the use as it will be assigned when adding an element to + /// a . + /// + public uint ID + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The X coordinate of the within it's containing diagram. + /// + /// + /// The X and Y grid locations are a relative position from the upper-left corner of the sheet. + /// X is the horizontal position; Y is the vertical position. + /// + public uint X + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The Y coordinate of the within it's containing diagram. + /// + /// + /// The X and Y grid locations are a relative position from the upper-left corner of the sheet. + /// X is the horizontal position; Y is the vertical position. + /// + public uint Y + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// Gets the cell of the diagram where the is located. + /// + /// A containing the cell coordinates (e.g. A1, B2, etc.) + /// This is determines from the X and Y coordinates of the element. Each cell is 200x200 + /// pixels which can be used to calculate the cell location. + public string Cell => $"{(char)(X / 200 + 'A')}{Y / 200 + 1}"; + + /// + /// The descriptive indication of the location of this diagram element within the containing . + /// + /// A indicating the cell and optional sheet or chart number where the block is located. + /// + /// This is an internally defined value so that we can identify instructions when referencing components. + /// + public virtual string Location => Cell; + + /// + /// Moves this diagram element to the specified X and Y coordinates. + /// + /// The X coordinate to move this block to. + /// The Y coordinate to move this block to. + /// This + public void Move(uint x, uint y) + { + Element.SetAttributeValue(L5XName.X, x); + Element.SetAttributeValue(L5XName.Y, y); + } + + /// + /// Moves this diagram element to the specified alpha-numeric cell location. + /// + /// The alpha-numeric cell location to move this block to. + /// is null, empty, not two characters, + /// does not start with a letter, or does not end with a digit. + public void Move(string cell) + { + if (string.IsNullOrEmpty(cell)) + throw new ArgumentException("Can not perform function with null or empty cell location."); + + if (cell.Length != 2) + throw new ArgumentException( + $"Cell {cell} is not a valid length argument. Must be 2 character cell location."); + + if (!char.IsLetter(cell[0])) + throw new ArgumentException($"Cell {cell} must start with a valid letter character"); + + if (!char.IsDigit(cell[1])) + throw new ArgumentException($"Cell {cell} must end with a valid number character"); + + var x = (uint)(cell.ToUpper()[0] - 'A') * 200; + var y = (uint)cell[1] * 200; + + Element.SetAttributeValue(L5XName.X, x); + Element.SetAttributeValue(L5XName.Y, y); + } + + /// + public virtual IEnumerable References() => Enumerable.Empty(); + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) return true; + + return obj switch + { + ValueType value => Equals(ID, value), + DiagramElement block => Equals(ID, block.ID), + _ => false + }; + } + + /// + public override int GetHashCode() => ID.GetHashCode(); + + /// + /// Gets a collection of values for the specified attribute name parsed as the specified generic type parameter if it exists. + /// If the element does not exist, returns an empty collection of the generic type parameter. + /// + /// The name of the attribute. + /// The value separator character. Default is ' '. + /// The return type of the value. + /// + /// If found, all values of the attribute split on the specified separator and parsed as the generic type parameter. + /// If not found, returns an empty collection. + /// + /// + /// This method makes getting/setting attributes with collection of values as a single string with a certain separator + /// character as concise as possible for derived classes. This method is added here since only types like + /// are using this method overload. + /// + protected IEnumerable GetValues(string name, char separator = ' ') + { + var value = Element.Attribute(name)?.Value; + + return value is not null + ? value.Split(separator, StringSplitOptions.RemoveEmptyEntries).Select(v => v.Parse()) + : Enumerable.Empty(); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Line.cs b/src/L5Sharp/Elements/Line.cs new file mode 100644 index 00000000..eac0830c --- /dev/null +++ b/src/L5Sharp/Elements/Line.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A Logix Line element containing the properties for a L5X Line component. +/// +public sealed class Line : LogixCode +{ + private NeutralText Text => new(Element.Value); + + /// + /// Creates a new with default values. + /// + public Line() + { + Element.ReplaceNodes(new XCData(NeutralText.Empty)); + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Line(XElement element) : base(element) + { + } + + /// + /// Creates a new initialized with the provided . + /// + /// The representing the line of structured text logic. + /// This will initialize ... + /// When importing, Logix ignores the rung number and imports Rung's in order of the container sequence, + /// meaning, its really only necessary to specify valid text, which is why this constructor is available, + /// allowing concise construction of a Rung object. + public Line(NeutralText text) + { + Element.ReplaceNodes(new XCData(text)); + } + + /// + public override IEnumerable References() + { + var references = new List(); + + var instructions = Text.Instructions().ToList(); + + foreach (var instruction in instructions) + { + references.Add(new CrossReference(Element, L5XName.Instruction, instruction.Key)); + + if (instruction.IsRoutineCall) + { + var routine = instruction.Arguments.FirstOrDefault()?.ToString() ?? string.Empty; + references.Add(new CrossReference(Element, L5XName.Routine, routine, instruction.Key)); + + var parameters = instruction.Arguments.Skip(1).Where(a => a.IsTag).Select(t => t.ToString()); + references.AddRange(parameters.Select(p => new CrossReference(Element, L5XName.Tag, p, instruction.Key))); + continue; + } + + if (instruction.IsTaskCall) + { + var task = instruction.Arguments.FirstOrDefault()?.ToString() ?? string.Empty; + references.Add(new CrossReference(Element, L5XName.Task, task, instruction.Key)); + continue; + } + + //todo other instructions like GSV SSV + + references.AddRange(instruction.Tags() + .Select(t => new CrossReference(Element, L5XName.Tag, t.ToString(), instruction.Key))); + } + + return references; + } + + /// + public override string ToString() => Text; + + /// + /// Implicitly converts the object to a . + /// + /// The Rung to convert. + /// A instance representing the contents of the Rung. + public static implicit operator NeutralText(Line rung) => new(rung.Text); + + /// + /// Implicitly converts the object to a . + /// + /// The NeutralText to convert. + /// A instance representing the contents of the NeutralText. + public static implicit operator Line(NeutralText text) => new(text); +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Parameter.cs b/src/L5Sharp/Elements/Parameter.cs new file mode 100644 index 00000000..c354796c --- /dev/null +++ b/src/L5Sharp/Elements/Parameter.cs @@ -0,0 +1,204 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A component of the that makes up the structure of the instruction type. +/// +/// +public class Parameter : LogixElement +{ + /// + /// Creates a new with default values. + /// + public Parameter() + { + Name = string.Empty; + TagType = TagType.Base; + DataType = string.Empty; + Dimension = Dimensions.Empty; + Usage = TagUsage.Input; + Radix = Radix.Null; + Required = false; + Visible = false; + ExternalAccess = ExternalAccess.ReadWrite; + Constant = false; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Parameter(XElement element) : base(element) + { + } + + /// + /// The unique name of the Parameter. + /// + /// A representing the component name. This property is required for valid elements. + public string Name + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The description of the Parameter. + /// + /// A containing the component description if it exists; Otherwise, null. + public string? Description + { + get => GetProperty(); + set => SetDescription(value); + } + + /// + /// The name of the data type of the Parameter. + /// + /// + /// A containing the data type name of the Parameter. + /// Default is . + /// Valid value is required for valid import. + /// + public string? DataType + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The array dimension of the Parameter. + /// + /// + /// A representing the array dimensions of the Parameter. + /// Default is . + /// Members should not have multidimensional arrays. + /// + public Dimensions? Dimension + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The radix value format of the Parameter. + /// + /// + /// A representing the value type format of the Parameter. + /// Default is . + /// Should be for all non atomic types. + /// + public Radix? Radix + { + get => GetValue(); + set => SetValue(value?.Value); + } + + /// + /// The external access of the Parameter. + /// + /// + /// A representing read/write access of the Parameter. + /// Default is . + /// + public ExternalAccess? ExternalAccess + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// A type indicating whether the current Parameter is a base tag, or alias for another tag instance. + /// + /// + /// A option representing the type of Parameter. + /// Default is . + /// + public TagType? TagType + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The usage option indicating the scope in which the Parameter is visible or usable from. + /// + /// + /// A option representing the Parameter scope. + /// Default for AoiBlock is . Only valid options for AoiBlock are Input, Output, + /// and InOut. + /// + public TagUsage Usage + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// Indicates whether the Parameter is required form the instruction code clock. + /// + /// + /// true if the Parameter is required; otherwise, false. Default is false. + public bool? Required + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Indicates whether the Parameter is visible from the instruction code block. + /// + /// true if the Parameter is visible; otherwise, false. Default is false. + public bool? Visible + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The tag name of the tag that is the alias of the current Parameter. + /// + /// A string representing the full tag name of the alias tag. + public TagName? AliasFor + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// A default value of the Parameter when instantiated. + /// + /// An representing the default value/data. Default is null. + public AtomicType? Default + { + get => LogixData.Deserialize(Element).As(); + set + { + if (value is null) + { + Element.Element(L5XName.DefaultData)?.Remove(); + return; + } + + var element = new XElement(L5XName.DefaultData, value.Serialize()); + Element.Add(element); + } + } + + /// + /// Indicates whether the Parameter is a constant. + /// + /// true if the Parameter is constant; otherwise, false. + /// Only value type tags have the ability to be set as a constant. Default is false. + public bool? Constant + { + get => GetValue(); + set => SetValue(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/ParameterConnection.cs b/src/L5Sharp/Elements/ParameterConnection.cs new file mode 100644 index 00000000..8958f1ce --- /dev/null +++ b/src/L5Sharp/Elements/ParameterConnection.cs @@ -0,0 +1,54 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A logix ParameterConnection element defining the connection between two different program tags. +/// +/// +public class ParameterConnection : LogixElement +{ + /// + /// Creates a new with default values. + /// + public ParameterConnection() + { + EndPoint1 = string.Empty; + EndPoint2 = string.Empty; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public ParameterConnection(XElement element) : base(element) + { + } + + /// + /// Specify the first end point of the parameter connection. + /// + /// A containing the end point path. The format for end point connections + /// is program_name.parameter_name + public string EndPoint1 + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// Specify the second end point of the parameter connection. + /// + /// A containing the end point path. The format for end point connections + /// is program_name.parameter_name + public string EndPoint2 + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Pen.cs b/src/L5Sharp/Elements/Pen.cs new file mode 100644 index 00000000..9b03b587 --- /dev/null +++ b/src/L5Sharp/Elements/Pen.cs @@ -0,0 +1,153 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A component of the that defines the tags used for the trend +/// +/// +///Observe these guidelines when defining a trend:
+/// • A trend can support as many as eight pen declarations.
+///
+/// +public class Pen : LogixElement +{ + /// + /// Creates a new with default values. + /// + public Pen() + { + Name = string.Empty; + Color = "16#00ff_0000"; + Visible = true; + Width = 1; + Type = PenType.Analog; + Style = 0; + Marker = 0; + Min = 0; + Max = 100; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Pen(XElement element) : base(element) + { + } + + /// + /// The unique name of the Pen. + /// + /// A representing the component name. This property is required for valid elements. + public string Name + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The description of the Pen. + /// + /// A containing the component description if it exists; Otherwise, null. + public string? Description + { + get => GetProperty(); + set => SetDescription(value); + } + + /// + /// Specify the color of the line in RGB format. Type the hex number for the color (16#0000_0000 – 16#00FF_FFFF). + /// + public string? Color + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify whether or not the line should be visible + /// + /// true if the pen is visible; otherwise, false. + public bool? Visible + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the width of the line in pixels (1...10). + /// + public int? Width + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the line type (Analog, Digital, or Full-Width). + /// + public PenType? Type + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the style of line. + /// + /// + /// Type: For:
+ /// 0 …………….
+ /// 1 … … … …
+ /// 2 . . . . . . . . . . .
+ /// 3 … . … . … . …
+ /// 4 … .. … .. … ..
+ ///
+ public int Style + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the line marker (0...83) + /// + public int? Marker + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the minimum value for the pen. The minimum cannot be greater than or equal to the maximum. + /// + public double? Min + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the maximum value for the pen. The maximum cannot be less than or equal to the minimum. + /// + public double? Max + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify engineering units. For example, rpm, gallon, fps, and degrees. + /// + public string? EngUnits + { + get => GetValue(); + set => SetValue(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Port.cs b/src/L5Sharp/Elements/Port.cs new file mode 100644 index 00000000..61af7a26 --- /dev/null +++ b/src/L5Sharp/Elements/Port.cs @@ -0,0 +1,119 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A component of a that represents the means for connecting devices on a network or in a chassis. +/// +/// +/// A Port is a component that helps define the structure of the IO tree. +/// Each port may contain the slot on the chassis or backplane where the resides, +/// or the the network address (IP) of the device. Each port may (or may not) have a . +/// Each port is identifiable by the property. +/// +public class Port : LogixElement +{ + /// + /// Creates a new with default values. + /// + public Port() + { + Id = 0; + Type = string.Empty; + Address = Address.None; + Upstream = false; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Port(XElement element) : base(element) + { + } + + /// + /// Gets the Id of the . + /// + /// + /// All Modules have at least one port. Each is identified by the Id property. Typically Modules will have one + /// or two ports with Ids '1' and '2', respectively. + /// + public int Id + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the value that represents the type. + /// + /// + /// This value appears to be specific to the product. Ports with IP will have 'Network' for their type. This + /// property is required for importing a correctly. + /// + public string? Type + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the address of the . + /// + /// + /// The Address of a port represents the slot or IP address for the port. This value is used in the + /// to determine the slot and IP properties. All ports must have an Address. + /// + public Address Address + { + get => GetValue
() ?? Address.None; + set => SetValue(value); + } + + /// + /// Gets the value indicating whether the there are devices upstream of the current . + /// + /// + /// From examining the L5X examples, the upstream flag seems to indicate that the port is one connected to a + /// parent module living 'upstream' of the current device. Non-upstream ports seem to indicate they contain + /// child modules, or connect to devices living 'downstream' of the current Module. This property must be set + /// correctly to be able to import L5X. + /// + public bool Upstream + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Gets the bus size or rack size of the port. + /// + /// + /// A port's bus represents the chassis or network on which Modules are accessible to the port. + /// Only downstream modules will have a valid Bus. + /// + public byte? BusSize + { + get => Element.Element(L5XName.Bus)?.Attribute(L5XName.Size)?.Value.Parse(); + set + { + if (value is null) + { + Element.Element(L5XName.Bus)?.Remove(); + return; + } + + var bus = Element.Element(L5XName.Bus); + if (bus is null) + { + bus = new XElement(L5XName.Bus); + Element.Add(bus); + } + + bus.SetAttributeValue(L5XName.Size, value); + } + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/RedundancyInfo.cs b/src/L5Sharp/Elements/RedundancyInfo.cs new file mode 100644 index 00000000..66457227 --- /dev/null +++ b/src/L5Sharp/Elements/RedundancyInfo.cs @@ -0,0 +1,73 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A sub element of the component that contains properties or configuration +/// related to the controller redundancy. +/// +public class RedundancyInfo : LogixElement +{ + + /// + /// Creates a new with default values. + /// + public RedundancyInfo() + { + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public RedundancyInfo(XElement element) : base(element) + { + } + + /// + /// Specify whether redundancy is used. + /// + /// true if enabled; otherwise, false. Default id false. + public bool Enabled + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify whether to keep test edits on when a switchover occurs in a redundant system. + /// + /// true if enabled; otherwise, false. Default id false. + public bool KeepTestEditsOnSwitchOver + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the percentage (0...100) of I/O memory that is available to the system after the download + /// when configured for redundancy. + /// + /// A indicating the percentage 0-100. + public float IOMemoryPadPercentage + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify the percentage (0...100) of the data table to reserve. + /// + /// A indicating the percentage 0-100. + /// + /// From docs: If redundancy is not enabled, type 0. + /// If redundancy is enabled, type 50. + /// + public float DataTablePadPercentage + { + get => GetValue(); + set => SetValue(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Rung.cs b/src/L5Sharp/Elements/Rung.cs new file mode 100644 index 00000000..55c28e5d --- /dev/null +++ b/src/L5Sharp/Elements/Rung.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A Logix Rung element containing the properties for a L5X Rung component. +/// +public class Rung : LogixCode +{ + /// + /// Creates a new with default values. + /// + public Rung() + { + Type = RungType.Normal; + Text = NeutralText.Empty; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Rung(XElement element) : base(element) + { + } + + /// + /// Creates a new initialized with the provided . + /// + /// The representing the rung logic. + /// The optional string comment of the rung. Default is null (no comment). + /// This will initialize ... + /// When importing, Logix ignores the rung number and imports Rung's in order of the container sequence, + /// meaning, its really only necessary to specify valid text, which is why this constructor is available, + /// allowing concise construction of a Rung object. + public Rung(NeutralText text, string? comment = null) + { + Type = RungType.Normal; + Text = text; + Comment = comment; + } + + /// + /// The , indicating edit state option of the rung. + /// + public RungType? Type + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The logic of the as a value. + /// + /// A instance containing the textual format of the run logic. + public NeutralText Text + { + get => GetProperty() ?? NeutralText.Empty; + set => SetProperty(value); + } + + /// + /// The text comment of the . + /// + /// A containing the text comment of the rung if it exists; Otherwise, null. + public string? Comment + { + get => GetProperty(); + set => SetProperty(value); + } + + /// + /// Returns a flat list of representing all base and nested AoiBlock logic in the + /// collection of objects. + /// + /// A containing all the , including nested instruction + /// text, found in the rung collection. + /// + /// + /// This extension was specifically created to assist in getting a flat list of logic, including + /// nested AoiBlock logic, for specialized querying purposes, such as finding tag references within nested logic. + /// This method will replace the instruction logic parameters with the neutral text operands of the instruction signature, + /// so to get the effective flattened list of executing code. + /// + public IEnumerable Flatten() + { + if (L5X is null) + throw new InvalidOperationException("Can not flatten rungs that are not attached to a L5X content file."); + + var code = new List(); + + var references = L5X.Instructions + .Select(i => new { Instruction = i, Instances = Text.Instructions(i.Name) }) + .ToList(); + + foreach (var reference in references.Where(r => r.Instances.Any())) + { + var logic = reference.Instances.SelectMany(i => reference.Instruction.LogicFor(i)); + code.AddRange(logic); + } + + return code; + } + + /// + public override IEnumerable References() + { + var references = new List(); + + var instructions = Text.Instructions().ToList(); + + foreach (var instruction in instructions) + { + references.Add(new CrossReference(Element, L5XName.Instruction, instruction.Key)); + + if (instruction.IsRoutineCall) + { + var routine = instruction.Arguments.FirstOrDefault()?.ToString() ?? string.Empty; + references.Add(new CrossReference(Element, L5XName.Routine, routine, instruction.Key)); + + var parameters = instruction.Arguments.Skip(1).Where(a => a.IsTag).Select(t => t.ToString()); + references.AddRange(parameters.Select(p => new CrossReference(Element, L5XName.Tag, p, instruction.Key))); + continue; + } + + if (instruction.IsTaskCall) + { + var task = instruction.Arguments.FirstOrDefault()?.ToString() ?? string.Empty; + references.Add(new CrossReference(Element, L5XName.Task, task, instruction.Key)); + continue; + } + + //todo other instructions like GSV SSV + + references.AddRange(instruction.Tags() + .Select(t => new CrossReference(Element, L5XName.Tag, t.ToString(), instruction.Key))); + } + + return references; + } + + /// + public override string ToString() => Text; + + /// + /// Implicitly converts the object to a . + /// + /// The Rung to convert. + /// A instance representing the contents of the Rung. + public static implicit operator NeutralText(Rung rung) => new(rung.Text); + + /// + /// Implicitly converts the object to a . + /// + /// The NeutralText to convert. + /// A instance representing the contents of the NeutralText. + public static implicit operator Rung(NeutralText text) => new(text); +} + +/// +/// Extension methods for a element or collections or elements. +/// +public static class RungExtensions +{ + /// + /// Returns a flat list of representing all base and nested AoiBlock logic in the + /// collection of objects. + /// + /// A containing all the , including nested instruction + /// text, found in the rung collection. + /// + /// + /// This extension was specifically created to assist in getting a flat list of logic, including + /// nested AoiBlock logic, for specialized querying purposes, such as finding tag references within nested logic. + /// This method will replace the instruction logic parameters with the neutral text operands of the instruction signature, + /// so to get the effective flattened list of executing code. + /// + public static IEnumerable Flatten(this IEnumerable rungs) + { + var code = new List(); + var collection = rungs.ToList(); + + var content = collection.FirstOrDefault()?.L5X; + if (content is null) + throw new InvalidOperationException("Can not flatten rungs that are not attached to a L5X content file."); + + var lookup = content.Instructions.ToDictionary(k => k.Name, v => v); + var text = collection.Select(r => r.Text); + + foreach (var line in text) + { + var instructions = lookup.SelectMany(l => line.Instructions(l.Key)).ToList(); + + if (instructions.Count == 0) + { + code.Add(line); + continue; + } + + foreach (var logic in from instruction in instructions + let definition = lookup[instruction.Key] + select definition.LogicFor(instruction)) + { + code.AddRange(logic); + } + } + + return code; + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/SafetyInfo.cs b/src/L5Sharp/Elements/SafetyInfo.cs new file mode 100644 index 00000000..6c0b03cc --- /dev/null +++ b/src/L5Sharp/Elements/SafetyInfo.cs @@ -0,0 +1,89 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A sub element of the component that contains properties or configuration +/// related to the controller safety. +/// +public class SafetyInfo : LogixElement +{ + /// + /// Creates a new with default values. + /// + public SafetyInfo() + { + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public SafetyInfo(XElement element) : base(element) + { + } + + /// + /// Specifies the safety signature control as defined in the controller properties + /// + /// A representing the safety signature. + /// This value is exported only; it is ignored on import. + public string? SafetySignature + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Displays whether the safety controller is locked or not. + /// + /// true if the setting is enabled; Otherwise, false. + /// This value is exported only; it is ignored on import. + public bool SafetyLocked + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specifies the lock password in the controller. This value is encrypted on export. + /// + /// A representing the encrypted password. + public string? SafetyLockPassword + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specifies the unlock password in the controller. This value is encrypted on export. + /// + /// A representing the encrypted password. + public string? SafetyUnlockPassword + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Specify whether to configure safety I/O when replacing safety I/O. + /// + /// true if the setting is enabled; Otherwise, false. + public bool ConfigureSafetyIOAlways + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Indicates whether you can modify the safety signature when in Run mode. + /// + /// true if the setting is enabled; Otherwise, false. + public bool SignatureRunModeProtect + { + get => GetValue(); + set => SetValue(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Security.cs b/src/L5Sharp/Elements/Security.cs new file mode 100644 index 00000000..d8b1cb99 --- /dev/null +++ b/src/L5Sharp/Elements/Security.cs @@ -0,0 +1,82 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A sub element of the component that contains properties or configuration +/// related to the controller security. +/// +public class Security : LogixElement +{ + /// + /// Creates a new with default values. + /// + public Security() + { + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public Security(XElement element) : base(element) + { + } + + /// + /// Specify whether the RSI Security Server is enabled for the controller. Type 0 if the controller is unsecured; + /// type a 10-digit, non-zero value if the controller is secured. + /// + public int Code + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// ID of the FactoryTalk® Diagnostics to which your controller is bound. + /// + public string? SecurityAuthorityID + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Network path to the FactoryTalk® Diagnostics to which your controller is bound + /// + public string? SecurityAuthorityURI + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Name of the set of permissions, configured in FactoryTalk® Security, to apply to this object. + /// + public string? PermissionSet + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Mask that specifies the controller events that you wish to track. + /// + public string? ChangesToDetect + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// Mask defining the slots through which the Trusted® communication is permitted to the controller. + /// + public string? TrustedSlots + { + get => GetValue(); + set => SetValue(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Sheet.cs b/src/L5Sharp/Elements/Sheet.cs new file mode 100644 index 00000000..90c6c607 --- /dev/null +++ b/src/L5Sharp/Elements/Sheet.cs @@ -0,0 +1,248 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A Logix Sheet block containing the properties for a L5X Sheet or Function Block Diagram (FBD). +/// +/// +/// A Sheet implements and is the type of content that FBD routines contain. +/// +/// Observe these guidelines when defining a sheet:
+/// • The sheets in the routine appear in order in the export file. +/// Each sheet section contains all the drawing elements and wires for that sheet.
+/// • On import, sheet numbers are assigned based on order in the file, not on the number attribute on the sheet.
+/// • The sheet name is stored as description on the sheet.
+/// • Input references, blocks, output references, special drawing elements, +/// and wires are contained within the sheet. On export, the elements +/// appear in the order shown. On import in the L5K format, elements can +/// be interspersed in the file. On import in the L5X format, the elements +/// must appear in the exported order.
+/// • Wire and feedback wire statements must appear after all the other +/// components.
+/// • Be careful when copying and pasting function block components +/// within an import/export file. Each component within a sheet must +/// have a unique id number within that sheet.
+///
+///
+[L5XType(L5XName.Sheet, L5XName.FBDContent)] +public class Sheet : Diagram +{ + private static readonly HashSet BlockTypes = typeof(Block).L5XTypes().ToHashSet(); + + /// + /// Creates a new with default values. + /// + public Sheet() + { + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// block is null. + public Sheet(XElement element) : base(element) + { + } + + /// + /// The description of the block. + /// + /// A containing the description if it exists; Otherwise, null + public string? Description + { + get => GetProperty(); + set => SetDescription(value); + } + + /// + /// Adds a block to the sheet, assigns a new block id if already used, and sorts the blocks to maintain + /// a valid diagram order. + /// + /// The element to add. + /// is null. + /// Ths id of the block that was added to the sheet. + /// + /// This will update the block id to the next available id if the id is already used. + /// Doing this will preserve the uniqueness of the id's and prevent import errors. This will also perform a sort of the + /// current underlying diagram elements to ensure the order of the element is maintained, which is also required to + /// prevent import errors. + /// + public uint Add(Block block) + { + if (block is null) + throw new ArgumentNullException(nameof(block)); + + var element = block.Serialize(); + + AssignId(element); + Element.Add(element); + SortBlocks(); + return block.ID; + } + + /// + /// Adds a block to the sheet at the specified coordinates, assigns a new block id if already used, and sorts the + /// blocks to maintain a valid diagram order. + /// + /// The X coordinate at which to place the block being added. + /// The Y coordinate at which to place the block being added. + /// The element to add. + /// is null. + /// Ths id of the block that was added to the sheet. + /// + /// This will update the block id to the next available id if the id is already used. + /// Doing this will preserve the uniqueness of the id's and prevent import errors. This will also perform a sort of the + /// current underlying diagram elements to ensure the order of the element is maintained, which is also required to + /// prevent import errors. + /// + public uint AddAt(uint x, uint y, Block block) + { + if (block is null) + throw new ArgumentNullException(nameof(block)); + + block.X = x; + block.Y = y; + + var element = block.Serialize(); + + AssignId(element); + Element.Add(element); + SortBlocks(); + return block.ID; + } + + /// + /// Adds the provided wire element to the sheet diagram and sorts the sheet elements to maintain a valid element order. + /// + /// The to add to the sheet. + /// is null. + public void Add(Wire wire) + { + if (wire is null) + throw new ArgumentNullException(nameof(wire)); + + var element = wire.Serialize(); + + Element.Add(element); + SortBlocks(); + } + + /// + /// Finds a block with the specified block id. If not found, returns null. + /// + /// The zero based id of the block to find. + /// A with the specified id if found; Otherwise null + /// + /// + /// + public Block? Block(uint id) + { + return Element.Elements() + .SingleOrDefault(e => e.Attribute(L5XName.ID)?.Value.Parse() == id) + ?.Deserialize(); + } + + /// + /// Finds a block with the specified operand value. If not found, returns null. + /// + /// The operand of that + /// A with the specified id if found; Otherwise null + /// + /// + /// + public Block? Block(string operand) + { + return Element.Elements() + .FirstOrDefault(e => e.Attribute(L5XName.Operand)?.Value == operand) + ?.Deserialize(); + } + + /// + /// Retrieves all elements found in the sheet diagram. + /// + /// A collection of elements found in the diagram. + /// + /// + public IEnumerable Blocks() + { + return Element.Elements().Where(e => BlockTypes.Contains(e.L5XType())).Select(e => e.Deserialize()); + } + + /// + /// + /// + /// The operand and pin to wire from. + /// The operand and pin to wire to. + public void Wire(TagName from, TagName to) + { + if (from is null || from.IsEmpty) + throw new ArgumentException("Can not create wire for null or empty from tag."); + if (to is null || to.IsEmpty) + throw new ArgumentException("Can not create wire for null or empty to tag."); + + var source = Block(from.Root) ?? + throw new InvalidOperationException($"Could not find source block from operand '{from}'"); + var destination = Block(to.Root) ?? + throw new InvalidOperationException( + $"Could not find destination block from operand '{from}'"); + + var wire = new Wire + { + FromID = source.ID, + FromParam = !string.IsNullOrEmpty(from.Path) ? from.Path : default, + ToID = destination.ID, + ToParam = !string.IsNullOrEmpty(to.Path) ? to.Path : default + }; + + Add(wire); + } + + /// + /// Retrieves all wires that are connected to the specified block ID. + /// + /// The id to filter the wires by. + /// An collection of elements that are connected to or from the specified id. + public IEnumerable Wires(uint id) + { + return Wires().Where(w => w.IsTo(id)).Concat(Wires().Where(w => w.IsFrom(id))); + } + + /// + /// Retrieves all wires found in the . + /// + /// A collection of elements found in the sheet diagram. + public IEnumerable Wires() + { + return Element.Elements(L5XName.Wire).Select(e => e.Deserialize()); + } + + /// + public override IEnumerable References() => Blocks().SelectMany(r => r.References()); + + /// + protected override IEnumerable Ordering() + { + return new List + { + L5XName.IRef, + L5XName.ORef, + L5XName.ICon, + L5XName.OCon, + L5XName.Block, + L5XName.Function, + L5XName.AddOnInstruction, + L5XName.JSR, + L5XName.SBR, + L5XName.Ret, + L5XName.Wire, + L5XName.FeedbackWire, + L5XName.TextBox, + L5XName.Attachment + }; + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/TextBox.cs b/src/L5Sharp/Elements/TextBox.cs new file mode 100644 index 00000000..b7531424 --- /dev/null +++ b/src/L5Sharp/Elements/TextBox.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A element of a diagram that contains a descriptive section of text. This element can be found on both FBD or SFC +/// diagram elements. +/// +/// +/// This element contains methods for creating attachments to other elements on a diagram. Use the +/// and methods to create or remove the attachments within the parent diagram. +/// +/// +[L5XType(L5XName.TextBox)] +public class TextBox : DiagramElement +{ + /// + /// Creates a new with the provided text. + /// + /// The text to initialize the text box with. + public TextBox(string text) + { + Element.SetAttributeValue(L5XName.Text, text); + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public TextBox(XElement element) : base(element) + { + } + + /// + /// The text information contained in the TextBox element. + /// + /// A containing the text contents. + public string? Text + { + get => GetProperty(); + set => SetProperty(value); + } + + /// + /// Creates an attachment from this to the specified block id. + /// + /// The id of the block attach this text to. + /// No parent element exists for this TextBox element. + /// This means that the object is created but has not yet been added to a diagram. + public void Attach(uint to) + { + var element = new XElement(L5XName.Attachment); + element.Add(new XAttribute(L5XName.FromID, ID)); + element.Add(new XAttribute(L5XName.ToID, to)); + + var diagram = Element.Parent ?? + throw new InvalidOperationException("Can not attach text to element that does not have a parent."); + + diagram.LastNode.AddAfterSelf(element); + } + + /// + /// Detaches this from the block it is currently attached to. + /// + public void Detach() + { + Element.Elements(L5XName.Attachment) + .FirstOrDefault(e => e.Attribute(L5XName.FromID)?.Value.Parse() == ID) + ?.Remove(); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/WatchTag.cs b/src/L5Sharp/Elements/WatchTag.cs new file mode 100644 index 00000000..e7aa361f --- /dev/null +++ b/src/L5Sharp/Elements/WatchTag.cs @@ -0,0 +1,52 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A tag element within a component. +/// +/// +public class WatchTag : LogixElement +{ + /// + /// Creates a new with default values. + /// + public WatchTag() + { + Specifier = TagName.Empty; + Scope = string.Empty; + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + public WatchTag(XElement element) : base(element) + { + } + + /// + /// Specify the tag or part of a tag to watch. + /// + /// A representing the tag specifier. + public TagName Specifier + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// Specify the name of program, equipment phase, or Add-On Instruction that contains the watch tag. + /// + /// A representing the scope of the tag. If controller scope set to empty. + public string Scope + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Elements/Wire.cs b/src/L5Sharp/Elements/Wire.cs new file mode 100644 index 00000000..ebf39953 --- /dev/null +++ b/src/L5Sharp/Elements/Wire.cs @@ -0,0 +1,115 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A LogixElement type that defines the properties for a wire connector within a +/// Function Block Diagram (FBD). +/// +/// +/// A Connector is not a itself since is does not have the location and ID properties. +/// It simply maps the connections of pins within a diagram. +/// +/// +[L5XType(L5XName.Wire, L5XName.Sheet)] +public class Wire : LogixElement +{ + /// + /// Creates a new with default values. + /// + public Wire() + { + Element.SetAttributeValue(L5XName.FromID, 0); + Element.SetAttributeValue(L5XName.ToID, 0); + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// block is null. + public Wire(XElement element) : base(element) + { + } + + /// + /// The ID of the source block the wire connection is from. + /// + /// A indicating the source block ID. + public uint FromID + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The parameter name of source block's pin this wire connection is from. + /// + /// A parameter name if found on the wire element. Otherwise, null. + /// + /// Some wires connect from blocks that don't have pins. For example, an IREF block contain a single + /// reference and no pin parameter, and therefore will have a null FromParam for the wire element connected to it. + /// + public string? FromParam + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The ID of the destination block the wire connection is to. + /// + /// A indicating the destination block ID. + public uint ToID + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The parameter name of destination block's pin this wire connection is to. + /// + /// A parameter name if found on the wire element; Otherwise, null. + /// + /// Some wires connect to blocks that don't have pins. For example, OREF blocks contain a single + /// reference and no pin parameter, and therefore will have a null ToParam for the wire element connected to it. + /// + public string? ToParam + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The parent element that this FunctionBlock is contained within. + /// + public Sheet? Sheet => Element.Parent is not null ? new Sheet(Element.Parent) : default; + + /// + /// Determines if this wire element is connected from the provided block ID and optional param name. + /// + /// The ID of the block to check connection from. + /// The parameter/pin of the block to check connection from. + /// true if this wire is connected from the specified block ID and param name; Otherwise, false. + /// + /// Note that param is optional. If not provided then the null comparison will be checked, and return true + /// only if this wire has the specified FromID and a null FromParam value. + /// + public bool IsFrom(uint id, string? param = null) => FromID == id && FromParam == param; + + /// + /// Determines if this wire element is connected to the provided block ID and optional param name. + /// + /// The ID of the block to check connection to. + /// The parameter/pin of the block to check connection to. + /// true if this wire is connected to the specified block ID and param name; Otherwise, false. + /// + /// Note that param is optional. If not provided then the null comparison will be checked, and return true + /// only if this wire has the specified ToID and a null ToParam value. + /// + public bool IsTo(uint id, string? param = null) => ToID == id && ToParam == param; +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/CaptureSizeType.cs b/src/L5Sharp/Enums/CaptureSizeType.cs new file mode 100644 index 00000000..b92f4e8e --- /dev/null +++ b/src/L5Sharp/Enums/CaptureSizeType.cs @@ -0,0 +1,26 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all for a Logix . +/// +public class CaptureSizeType : LogixEnum +{ + private CaptureSizeType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Samples . + /// + public static readonly CaptureSizeType Samples = new(nameof(Samples), nameof(Samples)); + + /// + /// Represents a TimePeriod . + /// + public static readonly CaptureSizeType TimePeriod = new(nameof(TimePeriod), nameof(TimePeriod)); + + /// + /// Represents a NoLimit . + /// + public static readonly CaptureSizeType NoLimit = new(nameof(NoLimit), nameof(NoLimit)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/ComponentType.cs b/src/L5Sharp/Enums/ComponentType.cs new file mode 100644 index 00000000..c8dd1bdc --- /dev/null +++ b/src/L5Sharp/Enums/ComponentType.cs @@ -0,0 +1,64 @@ +namespace L5Sharp.Core; + +public class ComponentType : LogixEnum +{ + private ComponentType(string name, string value) : base(name, value) + { + } + + /// + /// + /// + public virtual string ContainerName => $"{Name}s"; + + /// + /// + /// + public virtual bool IsTopLevel => Name != Routine.Name; + + /// + /// Represents a DataType . + /// + public static readonly ComponentType DataType = new(nameof(DataType), L5XName.DataType); + + /// + /// Represents a Module . + /// + public static readonly ComponentType Module = new(nameof(Module), L5XName.Module); + + /// + /// Represents a AddOnInstruction . + /// + public static readonly ComponentType AddOnInstruction = + new(nameof(AddOnInstruction), L5XName.AddOnInstructionDefinition); + + /// + /// Represents a Tag . + /// + public static readonly ComponentType Tag = new(nameof(Tag), L5XName.Tag); + + /// + /// Represents a Program . + /// + public static readonly ComponentType Program = new(nameof(Program), L5XName.Program); + + /// + /// Represents a Routine . + /// + public static readonly ComponentType Routine = new(nameof(Routine), L5XName.Routine); + + /// + /// Represents a Task . + /// + public static readonly ComponentType Task = new(nameof(Task), L5XName.Task); + + /// + /// Represents a Trend . + /// + public static readonly ComponentType Trend = new(nameof(Trend), L5XName.Trend); + + /// + /// Represents a WatchList . + /// + public static readonly ComponentType WatchList = new(nameof(WatchList), L5XName.QuickWatchList); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/ConnectionPriority.cs b/src/L5Sharp/Enums/ConnectionPriority.cs new file mode 100644 index 00000000..d213127e --- /dev/null +++ b/src/L5Sharp/Enums/ConnectionPriority.cs @@ -0,0 +1,31 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all for a Logix . +/// +public sealed class ConnectionPriority : LogixEnum +{ + private ConnectionPriority(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Scheduled . + /// + public static readonly ConnectionPriority Scheduled = new(nameof(Scheduled), nameof(Scheduled)); + + /// + /// Represents a Low . + /// + public static readonly ConnectionPriority Low = new(nameof(Low), nameof(Low)); + + /// + /// Represents a High . + /// + public static readonly ConnectionPriority High = new(nameof(High), nameof(High)); + + /// + /// Represents a Urgent . + /// + public static readonly ConnectionPriority Urgent = new(nameof(Urgent), nameof(Urgent)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/ConnectionType.cs b/src/L5Sharp/Enums/ConnectionType.cs new file mode 100644 index 00000000..85ffc92f --- /dev/null +++ b/src/L5Sharp/Enums/ConnectionType.cs @@ -0,0 +1,74 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all values for a given Logix +/// +public sealed class ConnectionType : LogixEnum +{ + private ConnectionType(string name, string value) : base(name, value) + { + } + + /// + /// Represents an Unknown value. + /// + public static readonly ConnectionType Unknown = new(nameof(Unknown), nameof(Unknown)); + + /// + /// Represents an Input value. + /// + public static readonly ConnectionType Input = new(nameof(Input), nameof(Input)); + + /// + /// Represents an Output value. + /// + public static readonly ConnectionType Output = new(nameof(Output), nameof(Output)); + + /// + /// Represents an Output value. + /// + public static readonly ConnectionType DiagnosticInput = new(nameof(DiagnosticInput), nameof(DiagnosticInput)); + + /// + /// Represents an MotionSync value. + /// + public static readonly ConnectionType MotionSync = new(nameof(MotionSync), nameof(MotionSync)); + + /// + /// Represents an MotionAsync value. + /// + public static readonly ConnectionType MotionAsync = new(nameof(MotionAsync), nameof(MotionAsync)); + + /// + /// Represents an MotionEvent value. + /// + public static readonly ConnectionType MotionEvent = new(nameof(MotionEvent), nameof(MotionEvent)); + + /// + /// Represents an SafetyInput value. + /// + public static readonly ConnectionType SafetyInput = new(nameof(SafetyInput), nameof(SafetyInput)); + + /// + /// Represents an SafetyOutput value. + /// + public static readonly ConnectionType SafetyOutput = new(nameof(SafetyOutput), nameof(SafetyOutput)); + + /// + /// Represents an SafetyOutput value. + /// + public static readonly ConnectionType StandardDataDriven = + new(nameof(StandardDataDriven), nameof(StandardDataDriven)); + + /// + /// Represents an SafetyOutput value. + /// + public static readonly ConnectionType SafetyInputDataDriven = + new(nameof(SafetyInputDataDriven), nameof(SafetyInputDataDriven)); + + /// + /// Represents an SafetyOutput value. + /// + public static readonly ConnectionType SafetyOutputDataDriven = + new(nameof(SafetyOutputDataDriven), nameof(SafetyOutputDataDriven)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/DataFormat.cs b/src/L5Sharp/Enums/DataFormat.cs new file mode 100644 index 00000000..1d564805 --- /dev/null +++ b/src/L5Sharp/Enums/DataFormat.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; + +namespace L5Sharp.Core; + +/// +/// Represents the types of data formats produced by a L5X file. +/// +public class DataFormat : LogixEnum +{ + private DataFormat(string name, string value) : base(name, value) + { + } + + /// + /// Represents a common verbose formatted data structure. + /// + public static readonly DataFormat Decorated = new(nameof(Decorated), nameof(Decorated)); + + /// + /// Represents String formatted data structure. + /// + public static readonly DataFormat String = new(nameof(String), nameof(String)); + + /// + /// Represents Alarm formatted data structure. + /// + public static readonly DataFormat Alarm = new(nameof(Alarm), nameof(Alarm)); + + /// + /// Represents Message formatted data structure. + /// + public static readonly DataFormat Message = new(nameof(Message), nameof(Message)); + + /// + /// Represents L5K formatted data structure. + /// + public static readonly DataFormat L5K = new(nameof(L5K), nameof(L5K)); + + /// + /// A list of all data formats that are supported for deserialization of the library. + /// + public static readonly IEnumerable Supported = new List + { Decorated, String, Alarm, Message }; +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/DataTypeClass.cs b/src/L5Sharp/Enums/DataTypeClass.cs new file mode 100644 index 00000000..273c27e4 --- /dev/null +++ b/src/L5Sharp/Enums/DataTypeClass.cs @@ -0,0 +1,43 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all Logix for a given data type component. +/// +/// Only user, +public sealed class DataTypeClass : LogixEnum +{ + private DataTypeClass(string name, string value) : base(name, value) + { + } + + /// + /// Represents an Unknown . + /// + public static readonly DataTypeClass Unknown = new(nameof(Unknown), nameof(Unknown)); + + /// + /// Represents an UserDefined . + /// + public static readonly DataTypeClass User = new(nameof(User), nameof(User)); + + /// + /// Represents an Atomic . + /// + /// + /// BOOL, Sint, Int, Dint, Lint, Real. + /// + public static readonly DataTypeClass Atomic = new(nameof(Atomic), nameof(Atomic)); + + /// + /// Represents an Predefined . + /// + /// + /// String, Timer, Counter, Message. + /// + public static readonly DataTypeClass Predefined = new(nameof(Predefined), nameof(Predefined)); + + /// + /// Represents an ModuleDefined . + /// + public static readonly DataTypeClass Module = new(nameof(Module), nameof(Module)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/DataTypeFamily.cs b/src/L5Sharp/Enums/DataTypeFamily.cs new file mode 100644 index 00000000..ee9f50bd --- /dev/null +++ b/src/L5Sharp/Enums/DataTypeFamily.cs @@ -0,0 +1,24 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all Logix options for a given . +/// Valid options are None and String. +/// +public sealed class DataTypeFamily : LogixEnum +{ + private DataTypeFamily(string name, string value) : base(name, value) + { + } + + /// + /// Represents no specific data type family. + /// All objects except should have this option. + /// + public static readonly DataTypeFamily None = new(nameof(None), "NoFamily"); + + + /// + /// Represents a string family. Only objects should have this option. + /// + public static readonly DataTypeFamily String = new(nameof(String), "StringFamily"); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/DiagramType.cs b/src/L5Sharp/Enums/DiagramType.cs new file mode 100644 index 00000000..ac56c674 --- /dev/null +++ b/src/L5Sharp/Enums/DiagramType.cs @@ -0,0 +1,49 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// An enumeration object containing the possible types of a Diagram element. +/// +public sealed class DiagramType : LogixEnum +{ + private DiagramType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Function Block Diagram . + /// + public static readonly DiagramType Block = new(nameof(Block), nameof(Block)); + + /// + /// Represents a Sequential Function Chart . + /// + public static readonly DiagramType Sequence = new(nameof(Sequence), nameof(Sequence)); + + /// + /// Determines the from the provided element using the name of the element. + /// + /// The element to examine. + /// If the element name is Sheet then . If the element name is SFCContent + /// then . If neither or null then throws an exception. + /// element is null. + /// element does not have one of the supported names. + public static DiagramType FromElement(XElement element) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + + if (element.Name == L5XName.Sheet) + { + return Block; + } + + if (element.Name == L5XName.SFCContent) + { + return Sequence; + } + + throw new NotSupportedException($"The element '{element.Name}' is not a supported as a DiagramType."); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/Direction.cs b/src/L5Sharp/Enums/Direction.cs new file mode 100644 index 00000000..97dd5e4f --- /dev/null +++ b/src/L5Sharp/Enums/Direction.cs @@ -0,0 +1,23 @@ +namespace L5Sharp.Core; + +/// +/// This is a library construct to help define the direction of a parameter for an instruction of function block. +/// +public sealed class Direction : LogixEnum +{ + private Direction(string name, string value) : base(name, value) + { + } + + /// + /// Indicates that a Direction is an Input . + /// + /// A option. + public static readonly Direction Input = new(nameof(Input), nameof(Input)); + + /// + /// Indicates that a Direction is an Output . + /// + /// A option. + public static readonly Direction Output = new(nameof(Output), nameof(Output)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/ElectronicKeying.cs b/src/L5Sharp/Enums/ElectronicKeying.cs new file mode 100644 index 00000000..2660d5b4 --- /dev/null +++ b/src/L5Sharp/Enums/ElectronicKeying.cs @@ -0,0 +1,31 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all values for a given Logix +/// +public class ElectronicKeying : LogixEnum +{ + private ElectronicKeying(string name, string value) : base(name, value) + { + } + + /// + /// Represents the ExactMatch value. + /// + public static readonly ElectronicKeying ExactMatch = new(nameof(ExactMatch), nameof(ExactMatch)); + + /// + /// Represents the CompatibleModule value. + /// + public static readonly ElectronicKeying CompatibleModule = new(nameof(CompatibleModule), nameof(CompatibleModule)); + + /// + /// Represents the Disabled value. + /// + public static readonly ElectronicKeying Disabled = new(nameof(Disabled), nameof(Disabled)); + + /// + /// Represents the Custom value. + /// + public static readonly ElectronicKeying Custom = new(nameof(Custom), nameof(Custom)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/ExternalAccess.cs b/src/L5Sharp/Enums/ExternalAccess.cs new file mode 100644 index 00000000..ca9237f5 --- /dev/null +++ b/src/L5Sharp/Enums/ExternalAccess.cs @@ -0,0 +1,29 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all Logix options. +/// +/// is a Logix setting that determines the ability to read from or write to a given component. +/// +/// +public sealed class ExternalAccess : LogixEnum +{ + private ExternalAccess(string name, string value) : base(name, value) + { + } + + /// + /// Represents no read or write . + /// + public static readonly ExternalAccess None = new(nameof(None), nameof(None)); + + /// + /// Represents read but not write . + /// + public static readonly ExternalAccess ReadOnly = new(nameof(ReadOnly), "Read Only"); + + /// + /// Represents read and write . + /// + public static readonly ExternalAccess ReadWrite = new(nameof(ReadWrite), "Read/Write"); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/Keyword.cs b/src/L5Sharp/Enums/Keyword.cs new file mode 100644 index 00000000..15fc12e7 --- /dev/null +++ b/src/L5Sharp/Enums/Keyword.cs @@ -0,0 +1,124 @@ +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo +namespace L5Sharp.Core; + +/// +/// An enumeration of known Logix Keywords used for the StructuredText programming language. +/// +public class Keyword : LogixEnum +{ + private Keyword(string name, string value) : base(name, value) + { + } + + /// + /// Represents the By Logix . + /// + public static readonly Keyword By = new(nameof(By), "by"); + + /// + /// Represents the Case Logix . + /// + public static readonly Keyword Case = new(nameof(Case), "case"); + + /// + /// Represents the Do Logix . + /// + public static readonly Keyword Do = new(nameof(Do), "do"); + + /// + /// Represents the Else Logix . + /// + public static readonly Keyword Else = new(nameof(Else), "else"); + + /// + /// Represents the ElsIf Logix . + /// + public static readonly Keyword ElsIf = new(nameof(ElsIf), "elseif"); + + /// + /// Represents the EndCase Logix . + /// + public static readonly Keyword EndCase = new(nameof(EndCase), "end_case"); + + /// + /// Represents the EndFor Logix . + /// + public static readonly Keyword EndFor = new(nameof(EndFor), "end_for"); + + /// + /// Represents the EndIf Logix . + /// + public static readonly Keyword EndIf = new(nameof(EndIf), "end_if"); + + /// + /// Represents the EndRepeat Logix . + /// + public static readonly Keyword EndRepeat = new(nameof(EndRepeat), "end_repeat"); + + /// + /// Represents the EndWhile Logix . + /// + public static readonly Keyword EndWhile = new(nameof(EndWhile), "end_while"); + + /// + /// Represents the Exit Logix . + /// + public static readonly Keyword Exit = new(nameof(Exit), "exit"); + + /// + /// Represents the For Logix . + /// + public static readonly Keyword For = new(nameof(For), "for"); + + /// + /// Represents the Goto Logix . + /// + public static readonly Keyword Goto = new(nameof(Goto), "goto"); + + /// + /// Represents the If Logix . + /// + public static readonly Keyword If = new(nameof(If), "if"); + + /// + /// Represents the Of Logix . + /// + public static readonly Keyword Of = new(nameof(Of), "of"); + + /// + /// Represents the Repeat Logix . + /// + public static readonly Keyword Repeat = new(nameof(Repeat), "repeat"); + + /// + /// Represents the Return Logix . + /// + public static readonly Keyword Return = new(nameof(Return), "return"); + + /// + /// Represents the Then Logix . + /// + public static readonly Keyword Then = new(nameof(Then), "then"); + + /// + /// Represents the This Logix . + /// + public static readonly Keyword This = new(nameof(This), "this"); + + /// + /// Represents the To Logix . + /// + public static readonly Keyword To = new(nameof(To), "to"); + + /// + /// Represents the Until Logix . + /// + public static readonly Keyword Until = new(nameof(Until), "until"); + + /// + /// Represents the While Logix . + /// + public static readonly Keyword While = new(nameof(While), "while"); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/ModuleCategory.cs b/src/L5Sharp/Enums/ModuleCategory.cs new file mode 100644 index 00000000..a5489889 --- /dev/null +++ b/src/L5Sharp/Enums/ModuleCategory.cs @@ -0,0 +1,47 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of for a given . +/// +public sealed class ModuleCategory : LogixEnum +{ + private ModuleCategory(string name, string value) : base(name, value) + { + } + + + /// + /// Represents an Analog . + /// + public static readonly ModuleCategory Analog = new(nameof(Analog), "Analog"); + + /// + /// Represents a Digital . + /// + public static readonly ModuleCategory Digital = new(nameof(Digital), "Digital"); + + /// + /// Represents an Analog . + /// + public static readonly ModuleCategory Input = new(nameof(Input), "Input"); + + /// + /// Represents a Communication . + /// + public static readonly ModuleCategory Communication = new(nameof(Communication), "Communication"); + + /// + /// Represents a Controller . + /// + public static readonly ModuleCategory Controller = new(nameof(Controller), "Controller"); + + /// + /// Represents a Motion . + /// + public static readonly ModuleCategory Motion = new(nameof(Motion), "Motion"); + + /// + /// Represents a Specialty . + /// + public static readonly ModuleCategory Specialty = new(nameof(Specialty), "Specialty"); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/OnlineEditType.cs b/src/L5Sharp/Enums/OnlineEditType.cs new file mode 100644 index 00000000..7f39a765 --- /dev/null +++ b/src/L5Sharp/Enums/OnlineEditType.cs @@ -0,0 +1,26 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public class OnlineEditType : LogixEnum +{ + private OnlineEditType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a 'Original' value. + /// + public static readonly OnlineEditType Original = new(nameof(Original), nameof(Original)); + + /// + /// Represents a 'PendingEdits' value. + /// + public static readonly OnlineEditType PendingEdits = new(nameof(PendingEdits), nameof(PendingEdits)); + + /// + /// Represents a 'TestEdits' value. + /// + public static readonly OnlineEditType TestEdits = new(nameof(TestEdits), nameof(TestEdits)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/Operator.cs b/src/L5Sharp/Enums/Operator.cs new file mode 100644 index 00000000..a79b76c9 --- /dev/null +++ b/src/L5Sharp/Enums/Operator.cs @@ -0,0 +1,96 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of known Logix operators found within the Logix programming languages. +/// +public class Operator : LogixEnum +{ + private Operator(string name, string value) : base(name, value) + { + } + + /// + /// Represents the Assignment Logix . + /// + public static readonly Operator Assignment = new(nameof(Assignment), ":="); + + /// + /// Represents the Equal Logix . + /// + public static readonly Operator Equal = new(nameof(Equal), "="); + + /// + /// Represents the NotEqual Logix . + /// + public static readonly Operator NotEqual = new(nameof(NotEqual), "<>"); + + /// + /// Represents the GreaterThan Logix . + /// + public static readonly Operator GreaterThan = new(nameof(GreaterThan), ">"); + + /// + /// Represents the GreaterThanOrEqual Logix . + /// + public static readonly Operator GreaterThanOrEqual = new(nameof(GreaterThanOrEqual), ">="); + + /// + /// Represents the LessThan Logix . + /// + public static readonly Operator LessThan = new(nameof(LessThan), "<"); + + /// + /// Represents the LessThanOrEqual Logix . + /// + public static readonly Operator LessThanOrEqual = new(nameof(LessThanOrEqual), "<="); + + /// + /// Represents the Add Logix . + /// + public static readonly Operator Add = new(nameof(Add), "+"); + + /// + /// Represents the Subtract Logix . + /// + public static readonly Operator Subtract = new(nameof(Subtract), "-"); + + /// + /// Represents the Multiply Logix . + /// + public static readonly Operator Multiply = new(nameof(Multiply), "*"); + + /// + /// Represents the Exponent Logix . + /// + public static readonly Operator Exponent = new(nameof(Exponent), "**"); + + /// + /// Represents the Divide Logix . + /// + public static readonly Operator Divide = new(nameof(Divide), "/"); + + /// + /// Represents the Modulo Logix . + /// + public static readonly Operator Mod = new(nameof(Mod), "MOD"); + + /// + /// Represents the AND Logix . + /// + public static readonly Operator And = new(nameof(And), "AND"); + + /// + /// Represents the OR Logix . + /// + public static readonly Operator Or = new(nameof(Or), "OR"); + + /// + /// Represents the XOR Logix . + /// + public static readonly Operator Xor = new(nameof(Xor), "XOR"); + + /// + /// Represents the NOT Logix . + /// + public static readonly Operator Not = new(nameof(Not), "NOT"); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/PassThroughOption.cs b/src/L5Sharp/Enums/PassThroughOption.cs new file mode 100644 index 00000000..8893e08a --- /dev/null +++ b/src/L5Sharp/Enums/PassThroughOption.cs @@ -0,0 +1,27 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all for a Logix . +/// +public class PassThroughOption : LogixEnum +{ + private PassThroughOption(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Disabled . + /// + public static readonly PassThroughOption Disabled = new(nameof(Disabled), nameof(Disabled)); + + /// + /// Represents a Enabled . + /// + public static readonly PassThroughOption Enabled = new(nameof(Enabled), nameof(Enabled)); + + /// + /// Represents a EnabledWithAppend . + /// + public static readonly PassThroughOption EnabledWithAppend = + new(nameof(EnabledWithAppend), nameof(EnabledWithAppend)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/PenType.cs b/src/L5Sharp/Enums/PenType.cs new file mode 100644 index 00000000..792c3569 --- /dev/null +++ b/src/L5Sharp/Enums/PenType.cs @@ -0,0 +1,26 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public class PenType : LogixEnum +{ + private PenType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a 'Analog' value. + /// + public static readonly PenType Analog = new(nameof(Analog), nameof(Analog)); + + /// + /// Represents a 'Digital' value. + /// + public static readonly PenType Digital = new(nameof(Digital), nameof(Digital)); + + /// + /// Represents a 'Full-Width' value. + /// + public static readonly PenType FullWidth = new(nameof(FullWidth), "Full-Width"); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/ProductionTrigger.cs b/src/L5Sharp/Enums/ProductionTrigger.cs new file mode 100644 index 00000000..eddd7709 --- /dev/null +++ b/src/L5Sharp/Enums/ProductionTrigger.cs @@ -0,0 +1,26 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all Logix options for a given . +/// +public class ProductionTrigger : LogixEnum +{ + private ProductionTrigger(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Cyclic value. + /// + public static readonly ProductionTrigger Cyclic = new(nameof(Cyclic), nameof(Cyclic)); + + /// + /// Represents a COS value. + /// + public static readonly ProductionTrigger Cos = new(nameof(Cos), nameof(Cos)); + + /// + /// Represents a Application value. + /// + public static readonly ProductionTrigger Application = new(nameof(Application), nameof(Application)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/ProgramType.cs b/src/L5Sharp/Enums/ProgramType.cs new file mode 100644 index 00000000..0ffd3824 --- /dev/null +++ b/src/L5Sharp/Enums/ProgramType.cs @@ -0,0 +1,21 @@ +namespace L5Sharp.Core; + +/// +/// Provides an enumeration of all Logix options or a given component. +/// +public class ProgramType : LogixEnum +{ + private ProgramType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Normal value. + /// + public static readonly ProgramType Normal = new(nameof(Normal), nameof(Normal)); + + /// + /// Represents a EquipmentPhase value. + /// + public static readonly ProgramType EquipmentPhase = new(nameof(EquipmentPhase), nameof(EquipmentPhase)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/Radix.cs b/src/L5Sharp/Enums/Radix.cs new file mode 100644 index 00000000..d2a30fa8 --- /dev/null +++ b/src/L5Sharp/Enums/Radix.cs @@ -0,0 +1,754 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; + +// ReSharper disable StringLiteralTypo + +namespace L5Sharp.Core; + +/// +/// Represents a number base for a given value type or atomic type. +/// +public abstract class Radix : LogixEnum +{ + private Radix(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Null radix, or absence of a Radix value. + /// + /// + /// Only types have non-null Radix. types all have null Radix. + /// + public static readonly Radix Null = new NullRadix(); + + /// + /// Represents a Binary number base format. + /// + public static readonly Radix Binary = new BinaryRadix(); + + /// + /// Represents a Octal number base format. + /// + public static readonly Radix Octal = new OctalRadix(); + + /// + /// Represents a Decimal number base format. + /// + public static readonly Radix Decimal = new DecimalRadix(); + + /// + /// Represents a Hexadecimal number base format. + /// + public static readonly Radix Hex = new HexRadix(); + + /// + /// Represents a Exponential number base format. + /// + public static readonly Radix Exponential = new ExponentialRadix(); + + /// + /// Represents a Float number base format. + /// + public static readonly Radix Float = new FloatRadix(); + + /// + /// Represents a Ascii number base format. + /// + public static readonly Radix Ascii = new AsciiRadix(); + + /// + /// Represents a DateTime number base format. + /// + public static readonly Radix DateTime = new DateTimeRadix(); + + /// + /// Represents a DateTimeNs number base format. + /// + public static readonly Radix DateTimeNs = new DateTimeNsRadix(); + + /// + public override string ToString() => Value; + + /// + /// Gets the default value for the provided logix type. + /// + /// The logix type to evaluate. + /// + /// for all non atomic types. + /// for types. + /// for all other atomic types. + /// + public static Radix Default(LogixType type) + { + if (type is not AtomicType atomicType) + return Null; + + return atomicType is REAL or LREAL ? Float : Decimal; + } + + /// + /// Determines if the current supports the provided data type instance. + /// + /// The logix type instance to evaluate. + /// true if the current radix value is valid for the given data type instance; otherwise, false. + public bool SupportsType(LogixType type) + { + if (type is not AtomicType atomicType) + return Equals(Null); + + return atomicType switch + { + BOOL => Equals(Binary) || Equals(Octal) || Equals(Decimal) || Equals(Hex), + LINT => Equals(Binary) || Equals(Octal) || Equals(Decimal) || Equals(Hex) || Equals(Ascii) || + Equals(DateTime) || Equals(DateTimeNs), + REAL => Equals(Float) || Equals(Exponential), + LREAL => Equals(Float) || Equals(Exponential), + _ => Equals(Binary) || Equals(Octal) || Equals(Decimal) || Equals(Hex) || Equals(Ascii) + }; + } + + /// + /// Determines the radix format from a string representing a formatted atomic value. + /// + /// The string for which to infer the format. + /// A format enum value. + /// A radix can not be determined from the format of value. + public static Radix Infer(string input) + { + var name = Identifiers.FirstOrDefault(i => i.Value.Invoke(input)).Key; + + if (name is null) + throw new FormatException( + @$"Could not determine Radix from input '{input}'. + Verify that the input string is an accepted Radix format."); + + return FromName(name); + } + + /// + /// Tries to infer the radix format from a string representing a formatted atomic value. + /// + /// The string input for which to infer the radix format. + /// If parsed successfully, then the representing the format of the input; + /// Otherwise, null. + /// true if a Radix format was inferred from the string input; Otherwise, false. + public static bool TryInfer(string input, out Radix? radix) + { + var name = Identifiers.FirstOrDefault(i => i.Value.Invoke(input)).Key; + radix = name is not null ? FromName(name) : null; + return radix is not null; + } + + /// + /// The specifier prefix of the Radix string format. + /// + /// A representing the text that identifies the format of the Radix. + /// + /// Most will have a specifier prefix, such as '#2' for Binary, '#16' for Hex, and so on. + /// By default this property is used by to determine if an input string has the specified + /// Radix format, and further by to determine a Radix from a string value. + /// However, some Radix options are overriden as they do not have specifiers (e.g. Decimal, Float). + /// + protected abstract string Specifier { get; } + + /// + /// Returns an indication as to whether the current string input value has the format of the current Radix type. + /// + /// The input text value to examine. + /// true if input qualifies as a valid format for the Radix type; otherwise, false. + protected virtual bool HasFormat(string input) => !input.IsEmpty() && input.StartsWith(Specifier); + + /// + /// Converts an atomic value to the current radix base format. + /// + /// The current atomic type to convert. + /// + /// A string that represents the value of the atomic type in the current radix base number style. + /// + public abstract string Format(AtomicType atomic); + + /// + /// Parses a string input of a given Radix formatted value into an atomic value type. + /// + /// The string value to parse. + /// An representing the value of the formatted string. + public abstract AtomicType Parse(string input); + + /// + /// Converts the provided to the specified base number. + /// + /// The atomic type to convert. + /// The base of the return value, which must be 2, 8, 10, or 16. + /// A value representing the value in the specified base. + /// baseNumber is not 2, 8, 10, or 16. + private static string ToBase(AtomicType type, int baseNumber) + { + var bitsPerByte = baseNumber switch + { + 2 => 8, + 8 => 3, + _ => 2 + }; + + var bytes = type.GetBytes(); + var builder = new StringBuilder(); + + for (var ctr = bytes.GetUpperBound(0); ctr >= bytes.GetLowerBound(0); ctr--) + { + var byteString = Convert.ToString(bytes[ctr], baseNumber).PadLeft(bitsPerByte, '0'); + builder.Append(byteString); + } + + return builder.ToString(); + } + + /// + /// Converts the provided to a given the provided bitsPerByte and baseNumber. + /// + /// The string value to convert. + /// The number of chars in value that represented a single byte of data. + /// The base number of the return value, which must be 2, 8, 10, or 16. + /// A value representing the value in the specified base. + /// baseNumber is not 2, 8, 10, or 16. + private static AtomicType ToAtomic(string value, int charsPerByte, int baseNumber) + { + if (value.IsEmpty()) + throw new ArgumentException("Value can not be empty."); + + var byteLength = value.Length / charsPerByte; + + return byteLength switch + { + 0 => new BOOL(value == "1"), + > 0 and <= 1 => new SINT(Convert.ToSByte(value, baseNumber)), + > 1 and <= 2 => new INT(Convert.ToInt16(value, baseNumber)), + > 2 and <= 4 => new DINT(Convert.ToInt32(value, baseNumber)), + > 4 and <= 8 => new LINT(Convert.ToInt64(value, baseNumber)), + _ => throw new ArgumentOutOfRangeException(nameof(byteLength), + $"The provided value '{value}' is out of range for atomic conversion. Must be between 0 and 8 bytes.") + }; + } + + private void ValidateFormat(string input) + { + if (string.IsNullOrEmpty(input)) + throw new ArgumentException("Input can not be null or empty.", nameof(input)); + + if (!Identifiers[Name].Invoke(input)) + throw new FormatException($"Input '{input}' does not have expected {Name} format."); + } + + private void ValidateType(AtomicType atomic) + { + if (atomic is null) + throw new ArgumentNullException(nameof(atomic)); + + if (!SupportsType(atomic)) + throw new NotSupportedException($"{atomic.GetType()} is not supported by {Name} Radix."); + } + + private static readonly Dictionary> Identifiers = new() + { + { nameof(Binary), s => Binary.HasFormat(s) }, + { nameof(Octal), s => Octal.HasFormat(s) }, + { nameof(Decimal), s => Decimal.HasFormat(s) }, + { nameof(Hex), s => Hex.HasFormat(s) }, + { nameof(Float), s => Float.HasFormat(s) }, + { nameof(Exponential), s => Exponential.HasFormat(s) }, + { nameof(Ascii), s => Ascii.HasFormat(s) }, + { nameof(DateTime), s => DateTime.HasFormat(s) }, + { nameof(DateTimeNs), s => DateTimeNs.HasFormat(s) } + }; + + private class NullRadix : Radix + { + public NullRadix() : base("NullType", "NullType") + { + } + + protected override string Specifier => string.Empty; + + protected override bool HasFormat(string input) => + throw new NotSupportedException($"{Name} Radix does not support formatting atomic values"); + + public override string Format(AtomicType atomic) => + throw new NotSupportedException($"{Name} Radix does not support formatting atomic values"); + + public override AtomicType Parse(string input) => + throw new NotSupportedException($"{Name} Radix does not support parsing atomic values"); + } + + private class BinaryRadix : Radix + { + private const int BaseNumber = 2; + private const int CharsPerByte = 8; + private const string ByteSeparator = "_"; + private const string Pattern = @"(?<=\d)(?=(\d\d\d\d)+(?!\d))"; + + public BinaryRadix() : base(nameof(Binary), nameof(Binary)) + { + } + + protected override string Specifier => "2#"; + + protected override bool HasFormat(string input) => !input.IsEmpty() && input.StartsWith(Specifier); + + public override string Format(AtomicType atomic) + { + ValidateType(atomic); + + var converted = atomic is not BOOL b ? ToBase(atomic, BaseNumber) : b ? "1" : "0"; + + var formatted = Regex.Replace(converted, Pattern, ByteSeparator); + + return $"{Specifier}{formatted}"; + } + + public override AtomicType Parse(string input) + { + ValidateFormat(input); + + var value = input.Replace(Specifier, string.Empty).Replace(ByteSeparator, string.Empty); + + return ToAtomic(value, CharsPerByte, BaseNumber); + } + } + + private class OctalRadix : Radix + { + private const int BaseNumber = 8; + private const int CharsPerByte = 3; + private const string ByteSeparator = "_"; + private const string Pattern = @"(?<=\d)(?=(\d\d\d)+(?!\d))"; + + public OctalRadix() : base(nameof(Octal), nameof(Octal)) + { + } + + protected override string Specifier => "8#"; + + public override string Format(AtomicType atomic) + { + ValidateType(atomic); + + var converted = atomic is not BOOL b ? ToBase(atomic, BaseNumber) : b ? "1" : "0"; + + var formatted = Regex.Replace(converted, Pattern, ByteSeparator); + + return $"{Specifier}{formatted}"; + } + + public override AtomicType Parse(string input) + { + ValidateFormat(input); + + var value = input.Replace(Specifier, string.Empty).Replace(ByteSeparator, string.Empty); + + return ToAtomic(value, CharsPerByte, BaseNumber); + } + } + + private class DecimalRadix : Radix + { + public DecimalRadix() : base(nameof(Decimal), nameof(Decimal)) + { + } + + protected override string Specifier => string.Empty; + + protected override bool HasFormat(string input) + { + if (input.StartsWith("+") || input.StartsWith("-")) + { + input = input.Remove(0, 1); + } + + return !input.IsEmpty() && input.All(char.IsDigit); + } + + public override string Format(AtomicType atomic) + { + ValidateType(atomic); + + return atomic switch + { + BOOL v => v ? "1" : "0", + SINT v => ((sbyte)v).ToString(), + INT v => ((short)v).ToString(), + DINT v => ((int)v).ToString(), + LINT v => ((int)v).ToString(), + USINT v => ((byte)v).ToString(), + UINT v => ((ushort)v).ToString(), + UDINT v => ((uint)v).ToString(), + ULINT v => ((ulong)v).ToString(), + _ => throw new ArgumentOutOfRangeException(nameof(atomic), atomic, null) + }; + } + + public override AtomicType Parse(string input) + { + ValidateFormat(input); + + if (sbyte.TryParse(input, out var sbyteValue)) + return new SINT(sbyteValue, this); + + if (byte.TryParse(input, out var byteValue)) + return new USINT(byteValue, this); + + if (short.TryParse(input, out var shortValue)) + return new INT(shortValue, this); + + if (ushort.TryParse(input, out var ushortValue)) + return new UINT(ushortValue, this); + + if (int.TryParse(input, out var intValue)) + return new DINT(intValue, this); + + if (uint.TryParse(input, out var uintValue)) + return new UDINT(uintValue, this); + + if (long.TryParse(input, out var longValue)) + return new LINT(longValue, this); + + if (ulong.TryParse(input, out var ulongValue)) + return new ULINT(ulongValue, this); + + throw new ArgumentOutOfRangeException(nameof(input), + $"Input '{input}' is out of range for the {Name} Radix."); + } + } + + private class HexRadix : Radix + { + private const int BaseNumber = 16; + private const int BitsPerByte = 2; + private const string ByteSeparator = "_"; + private const string Pattern = @"(?<=\w)(?=(\w\w\w\w)+(?!\w))"; + + public HexRadix() : base(nameof(Hex), nameof(Hex)) + { + } + + protected override string Specifier => "16#"; + + public override string Format(AtomicType atomic) + { + ValidateType(atomic); + + var converted = atomic is not BOOL b ? ToBase(atomic, BaseNumber) : b ? "1" : "0"; + + var formatted = Regex.Replace(converted, Pattern, ByteSeparator); + + return $"{Specifier}{formatted}"; + } + + public override AtomicType Parse(string input) + { + ValidateFormat(input); + + var value = input.Replace(Specifier, string.Empty).Replace(ByteSeparator, string.Empty); + + return ToAtomic(value, BitsPerByte, BaseNumber); + } + } + + private class FloatRadix : Radix + { + public FloatRadix() : base(nameof(Float), nameof(Float)) + { + } + + protected override string Specifier => string.Empty; + + protected override bool HasFormat(string input) + { + //we don't care if it is positive or negative + if (input.StartsWith("+") || input.StartsWith("-")) + { + input = input.Remove(0, 1); + } + + return input.Contains('.') && input.Replace(".", string.Empty).All(char.IsDigit); + } + + public override string Format(AtomicType atomic) + { + ValidateType(atomic); + + if (atomic is LREAL lreal) + { + return ((double)lreal).ToString("0.0##############", CultureInfo.InvariantCulture); + } + + return ((float)(REAL)atomic).ToString("0.0######", CultureInfo.InvariantCulture); + } + + public override AtomicType Parse(string input) + { + ValidateFormat(input); + + return new REAL(float.Parse(input)); + } + } + + private class ExponentialRadix : Radix + { + public ExponentialRadix() : base(nameof(Exponential), nameof(Exponential)) + { + } + + protected override string Specifier => ""; + + protected override bool HasFormat(string input) + { + //we don't care if it is positive or negative, so remove it. + if (input.StartsWith("+") || input.StartsWith("-")) + { + input = input.Remove(0, 1); + } + + return !input.IsEmpty() && input.Contains(".") + && input.Contains("e", StringComparison.OrdinalIgnoreCase) + && ReplaceAll(input, new[] { ".", "e", "E", "+", "-" }, string.Empty) + .All(char.IsDigit); + } + + public override string Format(AtomicType atomic) + { + ValidateType(atomic); + + if (atomic is LREAL lreal) + { + return ((double)lreal).ToString("e16", CultureInfo.InvariantCulture); + } + + return ((float)(REAL)atomic).ToString("e8", CultureInfo.InvariantCulture); + } + + public override AtomicType Parse(string input) + { + ValidateFormat(input); + + return new REAL(float.Parse(input)); + } + + private static string ReplaceAll(string value, IEnumerable items, string replacement) => + items.Aggregate(value, (str, cItem) => str.Replace(cItem, replacement)); + } + + private class AsciiRadix : Radix + { + private const int BaseNumber = 16; + private const int BitsPerByte = 2; + private const char SpecifierChar = '\''; + private const char ByteSeparator = '$'; + private const string Pattern = @"\$[A-Fa-f0-9]{2}|\$[tlpr'$]{1}|[\x00-\x7F]"; + + private static readonly Dictionary SpecialCharacters = new() + { + { "$t", "09" }, + { "$l", "0A" }, + { "$p", "0C" }, + { "$r", "0D" }, + { "$$", "24" }, + { "$'", "27" } + }; + + public AsciiRadix() : base(nameof(Ascii), nameof(Ascii).ToUpper()) + { + } + + protected override string Specifier => "'"; + + protected override bool HasFormat(string input) => + input.StartsWith(Specifier) && input.EndsWith(Specifier) && Regex.IsMatch(input, Pattern); + + public override string Format(AtomicType atomic) + { + ValidateType(atomic); + + var converted = ToBase(atomic, BaseNumber); + + var formatted = GenerateAscii(converted); + + return $"{Specifier}{formatted}{Specifier}"; + } + + public override AtomicType Parse(string input) + { + ValidateFormat(input); + + var value = GenerateHex(TrimSingle(input, SpecifierChar)); + + return ToAtomic(value, BitsPerByte, BaseNumber); + } + + private static string TrimSingle(string value, char character) + { + if (value.StartsWith(character) && value.EndsWith(character)) + return value.Substring(1, value.Length - 2); + + if (value.StartsWith(character)) + return value.Substring(1, value.Length - 1); + + return value.EndsWith(character) ? value[..^2] : value; + } + + private static string GenerateAscii(string str) + { + var builder = new StringBuilder(); + + var segments = Segment(str, BitsPerByte); + + foreach (var segment in segments) + { + var character = Convert.ToChar(Convert.ToUInt16(segment, BaseNumber)); + + if (character == 9 || character == 10 || character == 12 || character == 13 || + character > 31 && character < 127) + { + builder.Append(character); + continue; + } + + builder.Append(ByteSeparator); + builder.Append(segment.ToUpper()); + } + + return builder.ToString(); + } + + private static string GenerateHex(string input) + { + var matches = Regex.Matches(input, Pattern); + + var builder = new StringBuilder(); + + foreach (Match match in matches) + { + var value = match.Value.Length switch + { + 3 => match.Value.TrimStart(ByteSeparator), + 2 => SpecialCharacters[match.Value], + 1 => $"{Convert.ToInt32(char.Parse(match.Value)):X}", + _ => string.Empty + }; + + builder.Append(value); + } + + return builder.ToString(); + } + + private static IEnumerable Segment(string input, int length) + { + if (input is null) + throw new ArgumentNullException(nameof(input)); + + if (length <= 0) + throw new ArgumentOutOfRangeException(nameof(length), "Length must be greater than 0"); + + for (var i = 0; i < input.Length; i += length) + yield return input.Substring(i, i + length < input.Length ? length : input.Length - i); + } + } + + private class DateTimeRadix : Radix + { + private const string Separator = "_"; + private const string Suffix = "Z"; + private const string InsertPattern = @"(?<=\d\d\d)(?=(\d\d\d)+(?!\d))"; + private const long TicksPerMicrosecond = TimeSpan.TicksPerMillisecond / 1000; + + public DateTimeRadix() : base(nameof(DateTime), "Date/Time") + { + } + + protected override string Specifier => "DT#"; + + public override string Format(AtomicType atomic) + { + ValidateType(atomic); + + var timestamp = (long)(LINT)atomic; + + var milliseconds = timestamp / 1000; + var microseconds = timestamp % 1000; + var ticks = microseconds * TicksPerMicrosecond; + + var time = DateTimeOffset.FromUnixTimeMilliseconds(milliseconds).AddTicks(ticks); + + var formatted = time.ToString("yyyy-MM-dd-HH:mm:ss.ffffff"); + + var str = Regex.Replace(formatted, InsertPattern, Separator); + + return $"{Specifier}{str}{Suffix}"; + } + + public override AtomicType Parse(string input) + { + ValidateFormat(input); + + var value = input.Replace(Specifier, string.Empty) + .Replace(Separator, string.Empty) + .Replace(Suffix, string.Empty); + + var time = System.DateTime.ParseExact(value, "yyyy-MM-dd-HH:mm:ss.ffffff", + CultureInfo.InvariantCulture); + + var timestamp = (time.Ticks - System.DateTime.UnixEpoch.Ticks) / TicksPerMicrosecond; + + return new LINT(timestamp); + } + } + + private class DateTimeNsRadix : Radix + { + private const string Separator = "_"; + private const string InsertPattern = @"(?<=\d\d\d)(?=(\d\d\d)+(?!\d))"; + + public DateTimeNsRadix() : base(nameof(DateTimeNs), "Date/Time (ns)") + { + } + + protected override string Specifier => "LDT#"; + + public override string Format(AtomicType atomic) + { + ValidateType(atomic); + + var timestamp = (long)(LINT)atomic; + + var milliseconds = timestamp / 1000000; + var microseconds = timestamp % 1000000; + var ticks = microseconds / 100; + + var localTime = DateTimeOffset.FromUnixTimeMilliseconds(milliseconds).AddTicks(ticks).ToLocalTime(); + + var formatted = localTime.ToString("yyyy-MM-dd-HH:mm:ss.fffffff00(UTCzzz)"); + + var str = Regex.Replace(formatted, InsertPattern, Separator); + + return $"{Specifier}{str}"; + } + + public override AtomicType Parse(string input) + { + ValidateFormat(input); + + var value = input.Replace(Specifier, string.Empty).Replace(Separator, string.Empty); + + var time = System.DateTime.ParseExact(value, "yyyy-MM-dd-HH:mm:ss.fffffff00(UTCzzz)", + CultureInfo.InvariantCulture).ToUniversalTime(); + + var timestamp = (time.Ticks - System.DateTime.UnixEpoch.Ticks) * 100; + + return new LINT(timestamp); + } + } +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/RoutineType.cs b/src/L5Sharp/Enums/RoutineType.cs new file mode 100644 index 00000000..4afba183 --- /dev/null +++ b/src/L5Sharp/Enums/RoutineType.cs @@ -0,0 +1,46 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all Logix Routine types. +/// +/// +/// Routine type indicates what programming style/language the object contains. +/// The type of routine will determine what is generated by the routine object. +/// +public sealed class RoutineType : LogixEnum +{ + private RoutineType(string name, string value) : base(name, value) + { + } + + /// + /// The name of the element content for the . + /// + /// A containing the name of the element. + public string ContentName => $"{Name}Content"; + + /// + /// Represents no value. + /// + public static readonly RoutineType Typeless = new(nameof(Typeless), nameof(Typeless)); + + /// + /// Represents a Relay Ladder Logic routine type. + /// + public static readonly RoutineType RLL = new(nameof(RLL), nameof(RLL)); + + /// + /// Represents a Function Block Diagram routine type. + /// + public static readonly RoutineType FBD = new(nameof(FBD), nameof(FBD)); + + /// + /// Represents a Sequential Function Chart routine type. + /// + public static readonly RoutineType SFC = new(nameof(SFC), nameof(SFC)); + + /// + /// Represents a Structured Text routine type. + /// + public static readonly RoutineType ST = new(nameof(ST), nameof(ST)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/RungType.cs b/src/L5Sharp/Enums/RungType.cs new file mode 100644 index 00000000..fc586edf --- /dev/null +++ b/src/L5Sharp/Enums/RungType.cs @@ -0,0 +1,66 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all Logix values for a given Rung. +/// +public class RungType : LogixEnum +{ + private RungType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Normal value. + /// + public static readonly RungType Normal = new(nameof(Normal), "N"); + + /// + /// Represents a Insert value. + /// + public static readonly RungType Insert = new(nameof(Insert), "I"); + + /// + /// Represents a Delete value. + /// + public static readonly RungType Delete = new(nameof(Delete), "D"); + + /// + /// Represents a Replace value. + /// + public static readonly RungType Replace = new(nameof(Replace), "R"); + + /// + /// Represents a InsertReplace value. + /// + public static readonly RungType InsertReplace = new(nameof(InsertReplace), "IR"); + + /// + /// Represents a PendingReplace value. + /// + public static readonly RungType PendingReplace = new(nameof(PendingReplace), "rR"); + + /// + /// Represents a PendingReplaceInsert value. + /// + public static readonly RungType PendingReplaceInsert = new(nameof(PendingReplaceInsert), "rI"); + + /// + /// Represents a PendingReplaceNormal value. + /// + public static readonly RungType PendingReplaceNormal = new(nameof(PendingReplaceNormal), "rN"); + + /// + /// Represents a PendingInsertRung value. + /// + public static readonly RungType PendingInsertRung = new(nameof(PendingInsertRung), "e"); + + /// + /// Represents a PendingReplaceRung value. + /// + public static readonly RungType PendingReplaceRung = new(nameof(PendingReplaceRung), "er"); + + /// + /// Represents a PendingDeleteRung value. + /// + public static readonly RungType PendingDeleteRung = new(nameof(PendingDeleteRung), "dN"); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/SFCExecutionControl.cs b/src/L5Sharp/Enums/SFCExecutionControl.cs new file mode 100644 index 00000000..eb219dc2 --- /dev/null +++ b/src/L5Sharp/Enums/SFCExecutionControl.cs @@ -0,0 +1,21 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public sealed class SFCExecutionControl : LogixEnum +{ + private SFCExecutionControl(string name, string value) : base(name, value) + { + } + + /// + /// Represents a 'CurrentActive' value. + /// + public static readonly SFCExecutionControl CurrentActive = new(nameof(CurrentActive), nameof(CurrentActive)); + + /// + /// Represents a 'UntilFalse' value. + /// + public static readonly SFCExecutionControl UntilFalse = new(nameof(UntilFalse), nameof(UntilFalse)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/SFCLastScan.cs b/src/L5Sharp/Enums/SFCLastScan.cs new file mode 100644 index 00000000..ad15fda3 --- /dev/null +++ b/src/L5Sharp/Enums/SFCLastScan.cs @@ -0,0 +1,26 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public sealed class SFCLastScan : LogixEnum +{ + private SFCLastScan(string name, string value) : base(name, value) + { + } + + /// + /// Represents a 'AutomaticReset' value. + /// + public static readonly SFCLastScan AutomaticReset = new(nameof(AutomaticReset), nameof(AutomaticReset)); + + /// + /// Represents a 'ProgrammaticReset' value. + /// + public static readonly SFCLastScan ProgrammaticReset = new(nameof(ProgrammaticReset), nameof(ProgrammaticReset)); + + /// + /// Represents a 'DontScan' value. + /// + public static readonly SFCLastScan DontScan = new(nameof(DontScan), nameof(DontScan)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/SFCRestartPosition.cs b/src/L5Sharp/Enums/SFCRestartPosition.cs new file mode 100644 index 00000000..2078b8a5 --- /dev/null +++ b/src/L5Sharp/Enums/SFCRestartPosition.cs @@ -0,0 +1,21 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public sealed class SFCRestartPosition : LogixEnum +{ + private SFCRestartPosition(string name, string value) : base(name, value) + { + } + + /// + /// Represents a 'MostRecent' value. + /// + public static readonly SFCRestartPosition MostRecent = new(nameof(MostRecent), nameof(MostRecent)); + + /// + /// Represents a 'InitialStep' value. + /// + public static readonly SFCRestartPosition InitialStep = new(nameof(InitialStep), nameof(InitialStep)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/SamplesType.cs b/src/L5Sharp/Enums/SamplesType.cs new file mode 100644 index 00000000..5ef91ced --- /dev/null +++ b/src/L5Sharp/Enums/SamplesType.cs @@ -0,0 +1,21 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public class SamplesType : LogixEnum +{ + private SamplesType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Samples value. + /// + public static readonly SamplesType Samples = new(nameof(Samples), "Samples"); + + /// + /// Represents a TimePeriod value. + /// + public static readonly SamplesType TimePeriod = new(nameof(TimePeriod), "Time Period"); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/Scope.cs b/src/L5Sharp/Enums/Scope.cs new file mode 100644 index 00000000..f60da8e5 --- /dev/null +++ b/src/L5Sharp/Enums/Scope.cs @@ -0,0 +1,100 @@ +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all Logix options. +/// +public class Scope : LogixEnum +{ + private Scope(string name, string value) : base(name, value) + { + } + + /// + /// Determines the of the provided by looking up the ancestral chain. + /// + /// The element for which to determine the scope of. + /// if the element has a Program element ancestor, + /// if the element has a Controller element ancestor, + /// if the element has a AddOnInstructionDefinition element ancestor, + /// otherwise. + /// + public static Scope Type(XElement element) + { + var ancestor = FindContainer(element)?.Name.LocalName; + + return ancestor switch + { + L5XName.Controller => Controller, + L5XName.Program => Program, + L5XName.AddOnInstructionDefinition => Instruction, + _ => Null + }; + } + + /// + /// Finds the container element in the ancestral chain and returns the logix name of the element. This will be either + /// the name of the program container or the name of the controller, depending on the scope of the element. + /// + /// The element for which to find the container name of. + /// A representing the name of the containing program, instruction, or controller + /// if found; Otherwise, an empty string. + public static string Container(XElement element) => FindContainer(element)?.LogixName() ?? string.Empty; + + /// + /// Finds the container element in the ancestral chain and returns the logix name of the element. This will be either + /// the name of the program container or the name of the controller, depending on the scope of the element. + /// + /// The element for which to find the container name of. + /// A representing the name of the containing program, instruction, or controller + /// if found; Otherwise, an empty string. + public static string Task(XElement element) => FindTask(element)?.LogixName() ?? string.Empty; + + /// + /// Represents a Null value. + /// + /// A Null scope will occur on elements objects that have not been added to a container. + public static readonly Scope Null = new(nameof(Null), "NullScope"); + + /// + /// Represents a Controller value. + /// + public static readonly Scope Controller = new(nameof(Controller), "ControllerScope"); + + /// + /// Represents a Program value. + /// + public static readonly Scope Program = new(nameof(Program), "ProgramScope"); + + /// + /// Represents a Program value. + /// + public static readonly Scope Instruction = new(nameof(Instruction), "InstructionScope"); + + /// + /// Finds the first ancestor element that is either a Program, Controller, or + /// AddOnInstructionDefinition element. + /// + /// The to examine. + /// The first matching ancestor if found or null. + private static XElement? FindContainer(XNode node) + { + return node.Ancestors().FirstOrDefault(e => e.Name.LocalName + is L5XName.Program or L5XName.Controller or L5XName.AddOnInstructionDefinition); + } + + /// + /// Finds the first Task element in the L5X document with the provided node's container name. + /// + /// The to examine. + /// The first matching ancestor if found or null. + private static XElement? FindTask(XNode node) + { + var container = FindContainer(node)?.LogixName() ?? string.Empty; + + return node.Ancestors(L5XName.RSLogix5000Content).Descendants(L5XName.Task) + .FirstOrDefault(e => e.Descendants(L5XName.ScheduledProgram).Any(p => p.LogixName() == container)); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/SheetOrientation.cs b/src/L5Sharp/Enums/SheetOrientation.cs new file mode 100644 index 00000000..86a0dd87 --- /dev/null +++ b/src/L5Sharp/Enums/SheetOrientation.cs @@ -0,0 +1,21 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public class SheetOrientation : LogixEnum +{ + private SheetOrientation(string name, string value) : base(name, value) + { + } + + /// + /// Represents a 'Landscape' value. + /// + public static readonly SheetOrientation Landscape = new(nameof(Landscape), nameof(Landscape)); + + /// + /// Represents a 'Portrait' value. + /// + public static readonly SheetOrientation Portrait = new(nameof(Portrait), nameof(Portrait)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/SheetSize.cs b/src/L5Sharp/Enums/SheetSize.cs new file mode 100644 index 00000000..32b55ed7 --- /dev/null +++ b/src/L5Sharp/Enums/SheetSize.cs @@ -0,0 +1,76 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public class SheetSize : LogixEnum +{ + private SheetSize(string name, string value) : base(name, value) + { + } + + /// + /// Represents a 'Letter - 8.5 x 11 in' value. + /// + public static readonly SheetSize Letter = new(nameof(Letter), "Letter - 8.5 x 11 in"); + + /// + /// Represents a 'Legal - 8.5 x 14 in' value. + /// + public static readonly SheetSize Legal = new(nameof(Legal), "Legal - 8.5 x 14 in"); + + /// + /// Represents a 'Tabloid - 11 x 17 in' value. + /// + public static readonly SheetSize Tabloid = new(nameof(Tabloid), "Tabloid - 11 x 17 in"); + + /// + /// Represents a 'A - 8.5 x 11 in' value. + /// + public static readonly SheetSize A = new(nameof(A), "A - 8.5 x 11 in"); + + /// + /// Represents a 'B - 11 x 17 in' value. + /// + public static readonly SheetSize B = new(nameof(B), "B - 11 x 17 in"); + + /// + /// Represents a 'A - C - 17 x 22 in' value. + /// + public static readonly SheetSize C = new(nameof(C), "C - 17 x 22 in"); + + /// + /// Represents a 'B - D - 22 x 34 in' value. + /// + public static readonly SheetSize D = new(nameof(D), "D - 22 x 34 in"); + + /// + /// Represents a 'B - E - 34 x 44 in' value. + /// + public static readonly SheetSize E = new(nameof(E), "E - 34 x 44 in"); + + /// + /// Represents a 'B - A4 - 210 x 297 mm' value. + /// + public static readonly SheetSize A4 = new(nameof(A4), "A4 - 210 x 297 mm"); + + /// + /// Represents a 'B - A3 - 297 x 420 mm' value. + /// + public static readonly SheetSize A3 = new(nameof(A3), "A3 - 297 x 420 mm"); + + /// + /// Represents a 'B - A2 - 420 x 594 mm' value. + /// + public static readonly SheetSize A2 = new(nameof(A2), "A2 - 420 x 594 mm"); + + /// + /// Represents a 'B - A1 - 594 x 841 mm' value. + /// + public static readonly SheetSize A1 = new(nameof(A1), "A1 - 594 x 841 mm"); + + /// + /// Represents a 'B - A0 - 841 x 1189 mm' value. + /// + public static readonly SheetSize A0 = new(nameof(A0), "A0 - 841 x 1189 mm"); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/TagType.cs b/src/L5Sharp/Enums/TagType.cs new file mode 100644 index 00000000..02f1f1dc --- /dev/null +++ b/src/L5Sharp/Enums/TagType.cs @@ -0,0 +1,31 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of Logix options for a given . +/// +public class TagType : LogixEnum +{ + private TagType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Base value. + /// + public static readonly TagType Base = new(nameof(Base), nameof(Base)); + + /// + /// Represents a Alias value. + /// + public static readonly TagType Alias = new(nameof(Alias), nameof(Alias)); + + /// + /// Represents a Produced value. + /// + public static readonly TagType Produced = new(nameof(Produced), nameof(Produced)); + + /// + /// Represents a Consumed value. + /// + public static readonly TagType Consumed = new(nameof(Consumed), nameof(Consumed)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/TagUsage.cs b/src/L5Sharp/Enums/TagUsage.cs new file mode 100644 index 00000000..c7bc9fdf --- /dev/null +++ b/src/L5Sharp/Enums/TagUsage.cs @@ -0,0 +1,62 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all Logix options for a given . +/// +public class TagUsage : LogixEnum +{ + private TagUsage(string name, string value) : base(name, value) + { + } + + /// + /// Gets the default type for the provided data type instance. + /// + /// The data type instance to evaluate. + /// + /// for atomic types. + /// for complex types. + /// + public static TagUsage Default(LogixType logixType) => logixType is AtomicType ? Input : InOut; + + + /// + /// Represents a Null value. + /// + public static readonly TagUsage Null = new(nameof(Null), nameof(Null)); + + /// + /// Represents a Normal value. + /// + public static readonly TagUsage Normal = new(nameof(Normal), nameof(Normal)); + + /// + /// Represents a Local value. + /// + public static readonly TagUsage Local = new(nameof(Local), nameof(Local)); + + /// + /// Represents a Public value. + /// + public static readonly TagUsage Public = new(nameof(Public), nameof(Public)); + + /// + /// Represents a Input value. + /// + public static readonly TagUsage Input = new(nameof(Input), nameof(Input)); + + /// + /// Represents a Output value. + /// + public static readonly TagUsage Output = new(nameof(Output), nameof(Output)); + + /// + /// Represents a InOut value. + /// + public static readonly TagUsage InOut = new(nameof(InOut), nameof(InOut)); + + /// + /// Represents a Static value. + /// + public static readonly TagUsage Static = new(nameof(Static), nameof(Static)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/TaskEventTrigger.cs b/src/L5Sharp/Enums/TaskEventTrigger.cs new file mode 100644 index 00000000..4c34415c --- /dev/null +++ b/src/L5Sharp/Enums/TaskEventTrigger.cs @@ -0,0 +1,61 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of all options for a given . +/// +public class TaskEventTrigger : LogixEnum +{ + private TaskEventTrigger(string name, string value) : base(name, value) + { + } + + /// + /// Represents an AxisHome value. + /// + public static readonly TaskEventTrigger AxisHome = new(nameof(AxisHome), "Axis Home"); + + /// + /// Represents an AxisWatch value. + /// + public static readonly TaskEventTrigger AxisWatch = new(nameof(AxisWatch), "Axis Watch"); + + /// + /// Represents an AxisRegistration1 value. + /// + public static readonly TaskEventTrigger AxisRegistration1 = + new(nameof(AxisRegistration1), "Axis Registration 1"); + + /// + /// Represents an AxisRegistration2 value. + /// + public static readonly TaskEventTrigger AxisRegistration2 = + new(nameof(AxisRegistration2), "Axis Registration 2"); + + /// + /// Represents an MotionGroupExecution value. + /// + public static readonly TaskEventTrigger MotionGroupExecution = + new(nameof(MotionGroupExecution), "Motion Group Execution"); + + /// + /// Represents an EventInstructionOnly value. + /// + public static readonly TaskEventTrigger EventInstructionOnly = + new(nameof(EventInstructionOnly), "EVENT Instruction Only"); + + /// + /// Represents an ModuleInputDataStateChange value. + /// + public static readonly TaskEventTrigger ModuleInputDataStateChange = + new(nameof(ModuleInputDataStateChange), "Module Input Data State Change"); + + /// + /// Represents an ConsumedTag value. + /// + public static readonly TaskEventTrigger ConsumedTag = new(nameof(ConsumedTag), "Consumed Tag"); + + /// + /// Represents an WindowsEvent value. + /// + public static readonly TaskEventTrigger WindowsEvent = new(nameof(WindowsEvent), "Windows Event"); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/TaskType.cs b/src/L5Sharp/Enums/TaskType.cs new file mode 100644 index 00000000..76fff28d --- /dev/null +++ b/src/L5Sharp/Enums/TaskType.cs @@ -0,0 +1,27 @@ +namespace L5Sharp.Core; + +/// +/// Provides an enumeration of all Logix Task types. +/// Value must be , , or . +/// +public sealed class TaskType : LogixEnum +{ + private TaskType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Continuous , or a task that is configured to run continuously. + /// + public static readonly TaskType Continuous = new(nameof(Continuous), nameof(Continuous).ToUpper()); + + /// + /// Represents a Periodic , or a task that is configured to run at a specified rate. + /// + public static readonly TaskType Periodic = new(nameof(Periodic), nameof(Periodic).ToUpper()); + + /// + /// Represents an Event , or a task that is configured to run when a specified event occurs. + /// + public static readonly TaskType Event = new(nameof(Event), nameof(Event).ToUpper()); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/TokenType.cs b/src/L5Sharp/Enums/TokenType.cs new file mode 100644 index 00000000..eeef982a --- /dev/null +++ b/src/L5Sharp/Enums/TokenType.cs @@ -0,0 +1,37 @@ +namespace L5Sharp.Core; + +/// +/// A set of all known token types for a NeutralText string. This is not an L5X type but an enum that is used +/// by the library to parse a NeutralText string into a collection. +/// +public sealed class TokenType : LogixEnum +{ + private TokenType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Keyword token type. + /// + public static readonly TokenType Keyword = new(nameof(Keyword), nameof(Keyword)); + + /// + /// Represents a Instruction token type. + /// + public static readonly TokenType Instruction = new(nameof(Instruction), nameof(Instruction)); + + /// + /// Represents a Operator token type. + /// + public static readonly TokenType Operator = new(nameof(Operator), nameof(Operator)); + + /// + /// Represents a TagName token type. + /// + public static readonly TokenType TagName = new(nameof(TagName), nameof(TagName)); + + /// + /// Represents a Immediate token type. + /// + public static readonly TokenType Immediate = new(nameof(Immediate), nameof(Immediate)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/TransmissionType.cs b/src/L5Sharp/Enums/TransmissionType.cs new file mode 100644 index 00000000..c4c9403e --- /dev/null +++ b/src/L5Sharp/Enums/TransmissionType.cs @@ -0,0 +1,26 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public sealed class TransmissionType : LogixEnum +{ + private TransmissionType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a Null value. + /// + public static readonly TransmissionType Null = new(nameof(Null), nameof(Null)); + + /// + /// Represents a Multicast value. + /// + public static readonly TransmissionType Multicast = new(nameof(Multicast), nameof(Multicast)); + + /// + /// Represents a Unicast value. + /// + public static readonly TransmissionType Unicast = new(nameof(Unicast), nameof(Unicast)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/TriggerOperation.cs b/src/L5Sharp/Enums/TriggerOperation.cs new file mode 100644 index 00000000..43597f2c --- /dev/null +++ b/src/L5Sharp/Enums/TriggerOperation.cs @@ -0,0 +1,102 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public class TriggerOperation : LogixEnum +{ + private TriggerOperation(string name, int value) : base(name, value) + { + } + + /// + /// Parses a string number into the corresponding . + /// + /// The string integer number to parse. + /// A value. + /// value is not a parsable integer. + public static TriggerOperation Parse(string value) + { + if (!int.TryParse(value, out var number)) + throw new FormatException($"Could not parse Trigger Operation value {value}"); + + return FromValue(number); + } + + /// + /// Represents a ExactEqual value. + /// + public static readonly TriggerOperation ExactEqual = new(nameof(ExactEqual), 0); + + /// + /// Represents a TriggerLevel value. + /// + public static readonly TriggerOperation TriggerLevel = new(nameof(TriggerLevel), 1); + + /// + /// Represents a NotEqual value. + /// + public static readonly TriggerOperation NotEqual = new(nameof(NotEqual), 2); + + /// + /// Represents a LessThan value. + /// + public static readonly TriggerOperation LessThan = new(nameof(LessThan), 3); + + /// + /// Represents a GreaterThan value. + /// + public static readonly TriggerOperation GreaterThan = new(nameof(GreaterThan), 4); + + /// + /// Represents a LessThanOrEqualTo value. + /// + public static readonly TriggerOperation LessThanOrEqualTo = new(nameof(LessThanOrEqualTo), 5); + + /// + /// Represents a GreaterThanOrEqualTo value. + /// + public static readonly TriggerOperation GreaterThanOrEqualTo = new(nameof(GreaterThanOrEqualTo), 6); + + /// + /// Represents a PositiveSlope value. + /// + public static readonly TriggerOperation PositiveSlope = new(nameof(PositiveSlope), 7); + + /// + /// Represents a NegativeSlope value. + /// + public static readonly TriggerOperation NegativeSlope = new(nameof(NegativeSlope), 8); + + /// + /// Represents a BitwiseOR value. + /// + public static readonly TriggerOperation BitwiseOR = new(nameof(BitwiseOR), 9); + + /// + /// Represents a BitwiseOR value. + /// + public static readonly TriggerOperation NotBitwiseOR = new(nameof(NotBitwiseOR), 10); + + /// + /// Represents a BitwiseAND value. + /// + public static readonly TriggerOperation BitwiseAND = new(nameof(BitwiseAND), 11); + + /// + /// Represents a BitwiseAND value. + /// + public static readonly TriggerOperation NotBitwiseAND = new(nameof(NotBitwiseAND), 12); + + /// + /// Represents a BitwiseXOR value. + /// + public static readonly TriggerOperation BitwiseXOR = new(nameof(BitwiseXOR), 13); + + /// + /// Represents a BitwiseXOR value. + /// + public static readonly TriggerOperation NotBitwiseXOR = new(nameof(NotBitwiseXOR), 14); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/TriggerTargetType.cs b/src/L5Sharp/Enums/TriggerTargetType.cs new file mode 100644 index 00000000..8c3e532e --- /dev/null +++ b/src/L5Sharp/Enums/TriggerTargetType.cs @@ -0,0 +1,21 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public class TriggerTargetType : LogixEnum +{ + private TriggerTargetType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a TargetValue value. + /// + public static readonly TriggerTargetType TargetValue = new(nameof(TargetValue), nameof(TargetValue)); + + /// + /// Represents a TargetTag value. + /// + public static readonly TriggerTargetType TargetTag = new(nameof(TargetTag), nameof(TargetTag)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/TriggerType.cs b/src/L5Sharp/Enums/TriggerType.cs new file mode 100644 index 00000000..b0e4580c --- /dev/null +++ b/src/L5Sharp/Enums/TriggerType.cs @@ -0,0 +1,21 @@ +namespace L5Sharp.Core; + +/// +/// Represents an enumeration of all options for a given . +/// +public class TriggerType : LogixEnum +{ + private TriggerType(string name, string value) : base(name, value) + { + } + + /// + /// Represents a NoTrigger value. + /// + public static readonly TriggerType NoTrigger = new(nameof(NoTrigger), nameof(NoTrigger)); + + /// + /// Represents a EventTrigger value. + /// + public static readonly TriggerType EventTrigger = new(nameof(EventTrigger), nameof(EventTrigger)); +} \ No newline at end of file diff --git a/src/L5Sharp/Enums/Use.cs b/src/L5Sharp/Enums/Use.cs new file mode 100644 index 00000000..860b3abe --- /dev/null +++ b/src/L5Sharp/Enums/Use.cs @@ -0,0 +1,66 @@ +namespace L5Sharp.Core; + +/// +/// An enumeration of options that specify the purpose of use of a L5X. +/// +public class Use : LogixEnum +{ + private Use(string name, string value) : base(name, value) + { + } + + /// + /// Represents a option that is invalid... + /// + public static readonly Use Invalid = new(nameof(Invalid), nameof(Invalid)); + + /// + /// Get the Context option. + /// + public static readonly Use Context = new(nameof(Context), nameof(Context)); + + /// + /// Get the Deserialize option. + /// + public static readonly Use Create = new(nameof(Create), nameof(Create)); + + /// + /// Get the Target option. + /// + public static readonly Use Target = new(nameof(Target), nameof(Target)); + + /// + /// Get the Update option. + /// + public static readonly Use Update = new(nameof(Update), nameof(Update)); + + /// + /// Get the Delete option. + /// + public static readonly Use Delete = new(nameof(Delete), nameof(Delete)); + + /// + /// Get the Insert option. + /// + public static readonly Use Insert = new(nameof(Insert), nameof(Insert)); + + /// + /// Get the Append option. + /// + public static readonly Use Append = new(nameof(Append), nameof(Append)); + + /// + /// Get the Redefine option. + /// + public static readonly Use Redefine = new(nameof(Redefine), nameof(Redefine)); + + /// + /// Get the Reference option. + /// + public static readonly Use Reference = new(nameof(Reference), nameof(Reference)); + + /// + /// Get the Overwrite option. + /// + public static readonly Use Overwrite = new(nameof(Overwrite), nameof(Overwrite)); +} \ No newline at end of file diff --git a/src/L5Sharp/ILogixReferencable.cs b/src/L5Sharp/ILogixReferencable.cs new file mode 100644 index 00000000..5b18d424 --- /dev/null +++ b/src/L5Sharp/ILogixReferencable.cs @@ -0,0 +1,20 @@ +using System.Collections.Generic; + +namespace L5Sharp.Core; + +/// +/// Provides a common interface for Logix elements that can be referenced by or refer to other elements. Implementing +/// this interface is signing the class up to determine it's references. +/// +public interface ILogixReferencable +{ + /// + /// Returns a collection of objects that indicate either references + /// in or to the implementing object. + /// + /// + /// A of objects with the relevant reference information. + /// + /// + IEnumerable References(); +} \ No newline at end of file diff --git a/src/L5Sharp/ILogixSerializable.cs b/src/L5Sharp/ILogixSerializable.cs new file mode 100644 index 00000000..22202cd4 --- /dev/null +++ b/src/L5Sharp/ILogixSerializable.cs @@ -0,0 +1,15 @@ +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A interface defining the method for serialization of an object to a . +/// +public interface ILogixSerializable +{ + /// + /// Returns a representing the serialized L5X data for a given object. + /// + /// A containing the XML data. + XElement Serialize(); +} \ No newline at end of file diff --git a/src/L5Sharp/L5Sharp.csproj b/src/L5Sharp/L5Sharp.csproj new file mode 100644 index 00000000..e8561713 --- /dev/null +++ b/src/L5Sharp/L5Sharp.csproj @@ -0,0 +1,42 @@ + + + + netstandard2.1 + 10 + enable + true + true + L5Sharp + Timothy Nunnink + 0.17.0 + 0.17.0 + 0.17.0.0 + A library for intuitively interacting with Rockwell's L5X import/export files. + https://github.com/tnunnink/L5Sharp + csharp allen-bradely l5x logix plc-programming rockwell-automation logix5000 + true + MIT + + git + Initial release. + Copyright (c) Timothy Nunnink 2022 + README.md + L5Sharp + L5Sharp + L5Sharp + + + + + + + + + + + + + + + + diff --git a/src/L5Sharp/L5Sharp.csproj.DotSettings b/src/L5Sharp/L5Sharp.csproj.DotSettings new file mode 100644 index 00000000..9cac40d5 --- /dev/null +++ b/src/L5Sharp/L5Sharp.csproj.DotSettings @@ -0,0 +1,10 @@ + + True + True + True + True + True + True + True + True + True \ No newline at end of file diff --git a/src/L5Sharp/L5X.cs b/src/L5Sharp/L5X.cs new file mode 100644 index 00000000..f7b48485 --- /dev/null +++ b/src/L5Sharp/L5X.cs @@ -0,0 +1,1245 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using System.Xml.Linq; +using JetBrains.Annotations; +using Task = L5Sharp.Core.Task; +using task = System.Threading.Tasks.Task; + + +namespace L5Sharp.Core; + +/// +/// This is the primary entry point for interacting with the L5X file. +/// Provides access to query and manipulate logix components, elements, containers, and more. +/// +/// +/// +[PublicAPI] +public class L5X : ILogixSerializable +{ + /// + /// The date/time format for the L5X content. + /// + public const string DateTimeFormat = "ddd MMM d HH:mm:ss yyyy"; + + /// + /// The underlying root RSLogix5000Content element of the L5X file. + /// + private readonly XElement _content; + + /// + /// An index of all logix components in the L5X file for fast lookups. This field is not loaded or constructed until + /// requested by an associated method call. + /// + private LogixIndex? _index; + + /// + /// An index of all logix components in the L5X file for fast lookups. + /// + private LogixIndex Index => _index ??= new LogixIndex(GetController()); + + /// + /// The list of top level component containers for a L5X content or controller element in order of which + /// they should appear in the L5X file. + /// + private static readonly List Containers = new() + { + L5XName.DataTypes, + L5XName.Modules, + L5XName.AddOnInstructionDefinitions, + L5XName.Tags, + L5XName.Programs, + L5XName.Tasks, + L5XName.ParameterConnections, + L5XName.Trends, + L5XName.QuickWatchLists + }; + + /// + /// Creates a new instance wrapping the provided content object. + /// + /// The root object representing the RSLogix5000Content element of the + /// L5X file. + /// content is null. + /// content name is not expected RSLogix5000Content. + /// + /// Although you create the L5X instance by providing a valid XML element, it is or more typical to use the + /// static factory methods to load a file from disc, to parse the XML from a string, + /// or to generate a new default instance initialized with + /// the root RSLogix5000Content element and attributes. Also note that each individual component can be exported to + /// generate a new L5X content file. + /// + public L5X(XElement content) + { + if (content is null) + throw new ArgumentNullException(nameof(content)); + + if (content.Name != L5XName.RSLogix5000Content) + throw new ArgumentException( + $"Expecting root element name of {L5XName.RSLogix5000Content} to initialize L5X."); + + _content = content; + + //We will "normalize" (ensure consistent root controller element and component containers) for all + //files so that we won't have issues getting top level containers. When saving we can remove unused containers. + NormalizeContent(); + + //This stores L5X object as in-memory object for the root XElement, + //allowing child elements to retrieve the object locally without creating a new instance and potentially + //reindexing of the XML content.This allows them to reference to root L5X for cross referencing or other operations. + _content.AddAnnotation(this); + } + + /// + /// The representing the L5X content export information. + /// + public L5XInfo Info => new(_content); + + /// + /// The root component of the L5X file. + /// + /// A component object. + /// If the L5X does not ContainContext, meaning it is a project export, this will contain all the + /// relevant controller properties and configurations. Otherwise most data will be null as the controller serves as + /// just a root container for other component objects. + public Controller Controller => new(GetController()); + + /// + /// The container collection of components found in the L5X file. + /// + /// A of components. + /// + public LogixContainer DataTypes => new(GetContainer(L5XName.DataTypes)); + + /// + /// Gets the collection of components found in the L5X file. + /// + /// A of components. + public LogixContainer Instructions => new(GetContainer(L5XName.AddOnInstructionDefinitions)); + + /// + /// Gets the collection of components found in the L5X file. + /// + /// A of components. + public LogixContainer Modules => new(GetContainer(L5XName.Modules)); + + /// + /// Gets the collection of Controller components found in the L5X file. + /// + /// A of components. + /// To access program specific tag collection user the collection. + public LogixContainer Tags => new(GetContainer(L5XName.Tags)); + + /// + /// Gets the collection of components found in the L5X file. + /// + /// A of components. + public LogixContainer Programs => new(GetContainer(L5XName.Programs)); + + /// + /// Gets the collection of components found in the L5X file. + /// + /// A of components. + public LogixContainer Tasks => new(GetContainer(L5XName.Tasks)); + + /// + /// The container collection of components found in the L5X file. + /// + /// A of components. + public LogixContainer ParameterConnections => + new(GetContainer(L5XName.ParameterConnections)); + + /// + /// The container collection of components found in the L5X file. + /// + /// A of components. + public LogixContainer Trends => new(GetContainer(L5XName.Trends)); + + /// + /// The container collection of components found in the L5X file. + /// + /// A of components. + public LogixContainer WatchLists => new(GetContainer(L5XName.QuickWatchLists)); + + /// + /// Creates a new by loading the contents of the provide file name. + /// + /// The full path, including file name, to the L5X file to load. + /// A new containing the contents of the specified file. + /// The string is null or empty. + /// + /// This factory method uses the to load the contents of the XML file into + /// memory. This means that this method is subject to the same exceptions that could be generated by loading the + /// XElement. + /// + public static L5X Load(string fileName) => new(XElement.Load(fileName)); + + /// + /// + /// + /// + /// + /// + public static async Task LoadAsync(string fileName, CancellationToken token) + { + await using var stream = new FileStream(fileName, FileMode.Open); + var element = await XElement.LoadAsync(stream, LoadOptions.SetLineInfo, token); + var l5X = await task.Run(() => new L5X(element), token); + return l5X; + } + + /// + /// Creates a new blank file with the standard root content and controller elements, and configures them + /// with the provided controller name, processor, and revision. + /// + /// The name of the controller. + /// The processor type of the controller. + /// The optional software revision of the processor. + /// A new instance with the specified controller properties. + /// or is null or empty. + public static L5X New(string name, string processor, Revision? revision = null) + { + if (string.IsNullOrEmpty(name)) throw new ArgumentException("Name can not be null or empty."); + if (string.IsNullOrEmpty(processor)) throw new ArgumentException("Name can not be null or empty."); + + var content = NewContent(name, nameof(Controller), revision); + + var controller = new XElement(L5XName.Controller, + new XAttribute(L5XName.Name, name), + new XAttribute(L5XName.ProcessorType, processor) + ); + + content.Add(controller); + return new L5X(content); + } + + /// + /// Creates a new file with the provided object instance as the export + /// target of the file. + /// + /// The component that will serve as the target for the new L5X. + /// The software revision of the L5X file. + /// A new with the default root and target component as the content of the file. + /// is null. + public static L5X New(LogixComponent component, Revision? revision = null) + { + if (component is null) throw new ArgumentNullException(nameof(component)); + var content = NewContent(component.Name, component.L5XType, revision); + var file = new L5X(content); + file.Add(component); + return file; + } + + /// + /// Creates a new with the provided L5X string content. + /// + /// The string that contains the L5X content to parse. + /// A new containing the contents of the specified string. + /// The string is null or empty. + /// + /// This factory method uses the to load the contents of the XML file into + /// memory. This means that this method is subject to the same exceptions that could be generated by parsing the + /// XElement. + /// + public static L5X Parse(string text) => new(XElement.Parse(text)); + + /// + /// Finds element across the entire L5X with the provided type as a flat collection of object. + /// + /// The type name or element name to retrieve. + /// A containing all found object with the provided type name. + /// type is null. + /// + /// + /// This methods provides a flexible and simple way to query the entire L5X for a specific type. This method is allows + /// specifying the type at runtime as opposed the generic type but sacrifices the strong type querying of the + /// generic counterpart. This method does not make use of any optimized searching, so if you want to find items quickly, + /// see <c>FindComponent</c> or <c>FindTag</c> method. + /// + /// + /// Also note that this will call L5XType extension internally which returns all configured + /// element name for the provided type. This means the query will return all elements that the type supports. + /// If you want specific components look at the container properties. + /// For example for controller scoped tag components only. + /// + /// + public IEnumerable Query(string type) + { + if (string.IsNullOrEmpty(type)) + throw new ArgumentNullException(nameof(type), "Type is required to retrieve elements from the L5X"); + + return _content.Descendants(type).Select(e => e.Deserialize()); + } + + /// + /// Finds element across the entire L5X with the provided type as a flat collection of object. + /// + /// The type of the element type to retrieve. + /// A containing all found object with the provided type name. + /// type is null. + /// + /// + /// This methods provides a flexible and simple way to query the entire L5X for a specific type. This method is allows + /// specifying the type at runtime as opposed the generic type but sacrifices the strong type querying of the + /// generic counterpart. This method does not make use of any optimized searching, so if you want to find items quickly, + /// see <c>FindComponent</c> or <c>FindTag</c> method. + /// + /// + /// Also note that this will call L5XType extension internally which returns all configured + /// element name for the provided type. This means the query will return all elements that the type supports. + /// If you want specific components look at the container properties. + /// For example for controller scoped tag components only. + /// + /// + public IEnumerable Query(Type type) + { + if (type is null) + throw new ArgumentNullException(nameof(type), "Type is required to retrieve elements from the L5X"); + + var typeNames = type.L5XTypes().ToList(); + + return _content.Descendants() + .Where(e => typeNames.Any(n => n.IsEquivalent(e.L5XType()))) + .Select(e => e.Deserialize()); + } + + /// + /// Finds elements of the specified type across the entire L5X as a flat collection of objects. + /// + /// The element type to find. + /// A containing all found objects of the specified type. + /// + /// This methods provides a flexible and simple way to query the entire L5X for a specific type. Since + /// it returns an , you can make use of LINQ and the strongly typed objects to build + /// more complex queries. + /// + public IEnumerable Query() where TElement : LogixElement + { + var typeNames = typeof(TElement).L5XTypes().ToList(); + + return _content.Descendants() + .Where(e => typeNames.Any(n => n.IsEquivalent(e.L5XType()))) + .Select(e => e.Deserialize()); + } + + /// + /// Adds the provided to the first found container within the L5X file. + /// + /// The component to add to the L5X. + /// + /// This provides a more dynamic way to add content to an L5X file, and since most components have a single top level + /// container, it will work for most types. However, note that this only adds to the first container found of the specific type. + /// If you are adding scoped components such as or you should be doing so in the context + /// of a specific component, or use the overload accepting a specific container. + /// + /// + public void Add(LogixComponent component) + { + var containerType = component.GetType().L5XContainer(); + var container = GetContainer(containerType); + container.Add(component.Serialize()); + } + + /// + /// Adds the provided to the specified container within the L5X file. + /// + /// The component to add to the L5X. + /// The container name (controller, program, or instruction) in which to add the component. + /// No container was found in the L5X for the specified type and container name. + /// + /// This provides a more dynamic way to add content to an L5X file. This method will look for a container element + /// within the specified container/scope name. Therefore it is important to be sure you specify the correct type and + /// container name. For example, if you try to add a Module to a Program, this will fail even if a program with the + /// container name exists, because there is no Modules container in a Program component. + /// + public void Add(LogixComponent component, string container) + { + var type = component.GetType().L5XContainer(); + var target = _content.Descendants(type).FirstOrDefault(e => Scope.Container(e) == container); + if (target is null) + throw new InvalidOperationException($"Not container with name '{container}' was found in the L5X."); + target.Add(component.Serialize()); + } + + /// + /// Retrieves all components with the specified key. + /// + /// The to search for. + /// A collection of having the specified type/name composite key. + /// + /// + /// Note that this method returns all components with the type/name pair. This is typically a single component, + /// but types like Tag can have same name across different scopes, so it may be multiple different objects. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + /// + /// + public IEnumerable All(ComponentKey key) + { + return Index.Components.TryGetValue(key, out var components) + ? components.Select(c => c.Value.Deserialize()) + : Enumerable.Empty(); + } + + /// + /// Retrieves all components with the specified name nad type. + /// + /// The name of the components to retrieve. + /// The type of components to retrieve. + /// A collection of having the specified type/name composite key. + /// is null or empty. + /// + /// + /// Note that this method returns all components with the type/name pair. This is typically a single component, + /// but types like Tag or Routine can have same name across different scopes, + /// so it may be multiple different objects. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + /// + /// + public IEnumerable All(string name) where TComponent : LogixComponent + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Name can not be null or empty.", nameof(name)); + + var key = new ComponentKey(typeof(TComponent).L5XType(), name); + + return Index.Components.TryGetValue(key, out var components) + ? components.Select(c => c.Value.Deserialize()) + : Enumerable.Empty(); + } + + /// + /// Retrieves all tags with the given tag name across the entire L5X file. + /// + /// The name of the tag to search for. + /// An of objects that match the given tag name. + /// Note that this could be multiple tags from different containers or scopes. + /// + /// tagName parameter is null. + /// + /// + /// The provided tag name can be a top level tag name or a nested (dot-down) tag name path. This method will + /// find the root tag and then reach down the tag hierarchy as specified. Note that this method can return + /// multiple tags if the specified tag name exists in different scopes. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + /// + public IEnumerable All(TagName tagName) + { + if (tagName is null) throw new ArgumentNullException(nameof(tagName)); + + var key = new ComponentKey(nameof(Tag), tagName.Root); + + return Index.Components.TryGetValue(key, out var components) + ? components.Values.Select(t => new Tag(t).Member(tagName.Path)).Where(t => t is not null).Cast() + : Enumerable.Empty(); + } + + /// + /// Checks if a component object with the specified type and name composite key exists in the L5X file. + /// + /// The to check. + /// true if the component key exists in the L5X; otherwise, false. + /// This + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + public bool Contains(ComponentKey key) => Index.Components.ContainsKey(key); + + /// + /// Checks if a component object with the specified type and name composite key exists in the L5X file. + /// + /// The name of the component to check for existence. + /// The type of component to check for existence. + /// true if the component key exists in the L5X; otherwise, false. + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + public bool Contains(string name) => + Index.Components.ContainsKey(new ComponentKey(typeof(TComponent).L5XType(), name)); + + /// + /// Returns the number of elements of the specified type in the L5X. + /// + /// The logix element type to get the count for. + /// A representing the number of elements of the specified type. + public int Count() where TElement : LogixElement => + _content.Descendants(typeof(TElement).L5XType()).Count(); + + /// + /// Finds the first component in the L5X file having the provided composite type/name key. + /// + /// The to search for. + /// The found if it exists, otherwise returns null. + /// + /// + /// If there are multiple different scoped components with the same component key, this method will return the first + /// found object. For types like Tag, this will typically be the controller scoped instance (as it is indexed first). + /// For non-scoped components, there should be only one component anyway. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + /// + public LogixComponent? Find(ComponentKey key) + { + return Index.Components.TryGetValue(key, out var components) + ? components.Values.First()?.Deserialize() + : default; + } + + /// + /// Finds a component in the L5X file having the provided composite type/name key and container name. + /// + /// The to search for. + /// The name of the container (controller, program, instruction) in which the + /// component should be found. + /// The found if it exists, otherwise returns null. + /// is null or empty. + /// + /// + /// This method allows the caller to further specify which scoped component they would like to retrieve, instead of + /// just getting the first found component object. Container represents the name of the controller, program, or + /// instruction the scoped component is/should be contained in. If no component is found in that scope, + /// this method returns null. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + public LogixComponent? Find(ComponentKey key, string container) + { + if (string.IsNullOrEmpty(container)) + throw new ArgumentException("Container can not be null or empty.", nameof(container)); + + if (Index.Components.TryGetValue(key, out var components) + && components.TryGetValue(container, out var component)) + return component.Deserialize(); + + return default; + } + + /// + /// Finds the first component in the L5X file having the provided type and name. + /// + /// The name of the component to find. + /// The type of component to find. + /// The found if it exists, otherwise returns null. + /// is null or empty. + /// + /// + /// If there are multiple different scoped components with the same type and name, this method will return the first + /// found object. For types like Tag, this will be the controller scoped instance (as it is indexed first). + /// For non-scoped components, there should be only one component anyway. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + public TComponent? Find(string name) where TComponent : LogixComponent + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Name can not be null or empty.", nameof(name)); + + var key = new ComponentKey(typeof(TComponent).L5XType(), name); + + return Index.Components.TryGetValue(key, out var components) + ? components.Values.First()?.Deserialize() + : default; + } + + /// + /// Finds a component in the L5X file having the provided composite type/name key and container name. + /// + /// The name of the component to find. + /// The name of the container (controller, program, instruction) in which the + /// component should be found. + /// The type of component to find. + /// The found if it exists, otherwise returns null. + /// or is null or empty. + /// + /// + /// This method allows the caller to further specify which scoped component they would like to retrieve, instead of + /// just getting the first found component object. Container represents the name of the controller, program, or + /// instruction the scoped component is/should be contained in. If no component is found in that scope, + /// this method returns null. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + public TComponent? Find(string name, string container) where TComponent : LogixComponent + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Name can not be null or empty.", nameof(name)); + if (string.IsNullOrEmpty(container)) + throw new ArgumentException("Container can not be null or empty.", nameof(container)); + + var key = new ComponentKey(typeof(TComponent).L5XType(), name); + + if (Index.Components.TryGetValue(key, out var components) + && components.TryGetValue(container, out var component)) + return component.Deserialize(); + + return default; + } + + /// + /// Finds the first in the L5X file having the specified tag name. + /// + /// The of the tag to find. + /// The first found if it exists, otherwise returns null. + /// tagName is null. + /// + /// + /// This method only differs from the more generic Find methods in that it also returns a nested tag member if + /// specified by the path. This means the caller can directly and efficiently get either + /// a root tag component, or one of it's nested child members. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + /// + /// + /// + public Tag? Find(TagName tagName) + { + if (tagName is null) throw new ArgumentNullException(nameof(tagName)); + + var key = new ComponentKey(nameof(Tag), tagName.Root); + + return Index.Components.TryGetValue(key, out var components) + ? components.Values.First().Deserialize().Member(tagName.Path) + : default; + } + + /// + /// Finds a single in the L5X file having the specified tag and container name. + /// + /// The of the tag to find. + /// The name of the container (controller, program, instruction) in which the tag + /// should be found. + /// The found if it exists, otherwise returns null. + /// tagName is null -or- is null or empty. + /// + /// + /// This method only differs from the more generic Find methods in that it also returns a nested tag member if + /// specified by the path. This means the caller can directly and efficiently get either + /// a root tag component, or one of it's nested child members. + /// + /// + /// This method allows the caller to further specify which scoped component they would like to retrieve, instead of + /// just getting the first found component object. Container represents the name of the controller, program, or + /// instruction the scoped component is/should be contained in. If no component is found in that scope, + /// this method returns null. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + /// + /// + /// + public Tag? Find(TagName tagName, string container) + { + if (tagName is null) throw new ArgumentNullException(nameof(tagName)); + + if (string.IsNullOrEmpty(container)) + throw new ArgumentException("Container can not be null or empty.", nameof(container)); + + var key = new ComponentKey(nameof(Tag), tagName.Root); + + return Index.Components.TryGetValue(key, out var components) && components.TryGetValue(container, out var tag) + ? tag.Deserialize().Member(tagName.Path) + : default; + } + + /// + /// Gets the first component in the L5X file having the provided composite type/name key. + /// + /// The to search for. + /// The with the specified key. If no component is found, then an exception + /// is thrown. + /// No component is found with the provided composite key. + /// + /// + /// If there are multiple different scoped components with the same component key, this method will return the first + /// found object. For types like Tag, this will be the controller scoped instance (as it is indexed first). + /// For non-scoped components, there should be only one component anyway. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + /// + public LogixComponent Get(ComponentKey key) + { + return Index.Components.TryGetValue(key, out var components) + ? components.Values.Single().Deserialize() + : throw new KeyNotFoundException($"Component not found in L5X: {key}"); + } + + /// + /// Gets a component in the L5X file having the provided composite type/name key and container name. + /// + /// The to search for. + /// The name of the container (controller, program, instruction) in which the + /// component should be found. + /// The with the specified key. If no component is found, then an exception + /// is thrown. + /// is null or empty. + /// No component is found with the provided composite key. + /// + /// + /// This method allows the caller to further specify which scoped component they would like to retrieve, instead of + /// just getting the first found component object. Container represents the name of the controller, program, or + /// instruction the scoped component is/should be contained in. If no component is found in that scope, + /// this method returns null. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + public LogixComponent Get(ComponentKey key, string container) + { + if (string.IsNullOrEmpty(container)) + throw new ArgumentException("Container can not be null or empty.", nameof(container)); + + return Index.Components.TryGetValue(key, out var scoped) && scoped.TryGetValue(container, out var component) + ? component.Deserialize() + : throw new KeyNotFoundException($"Component not found in L5X: {key}"); + } + + /// + /// Gets the first component in the L5X file having the provided type and name. + /// + /// The name of the component to find. + /// The type of component to find. + /// The with the specified key. If no component is found, then an exception + /// is thrown. + /// is null or empty. + /// No component is found with the provided composite key. + /// + /// + /// If there are multiple different scoped components with the same type and name, this method will return the first + /// found object. For types like Tag, there may be multiple components with the same name across difference scopes. + /// For non-scoped components, there should be only one "controller scoped" component. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + public TComponent Get(string name) where TComponent : LogixComponent + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Name can not be null or empty.", nameof(name)); + + var key = new ComponentKey(typeof(TComponent).L5XType(), name); + + return Index.Components.TryGetValue(key, out var components) + ? components.Values.First().Deserialize() + : throw new KeyNotFoundException($"Component not found in L5X: {key}"); + } + + /// + /// Gets the first component in the L5X file having the provided type and name. + /// + /// The name of the component to find. + /// The name of the container (controller, program, instruction) in which the + /// component should be found. + /// The type of component to find. + /// The with the specified key. If no component is found, then an exception + /// is thrown. + /// or is null or empty. + /// No component is found with the provided composite key. + /// + /// + /// If there are multiple different scoped components with the same type and name, this method will return the first + /// found object. For types like Tag, there may be multiple components with the same name across difference scopes. + /// For non-scoped components, there should be only one "controller scoped" component. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + public TComponent Get(string name, string container) where TComponent : LogixComponent + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Name can not be null or empty.", nameof(name)); + if (string.IsNullOrEmpty(container)) + throw new ArgumentException("Container can not be null or empty.", nameof(container)); + + var key = new ComponentKey(typeof(TComponent).L5XType(), name); + + return Index.Components.TryGetValue(key, out var scoped) && scoped.TryGetValue(container, out var component) + ? component.Deserialize() + : throw new KeyNotFoundException($"Component not found in L5X: {key}"); + } + + /// + /// Gets the first in the L5X file having the specified tag name. + /// + /// The of the tag to get. + /// The with the specified key. If a multiple scoped tags with the same name exist, this + /// will return the first tag instance found. If not tag is found, then an exception is thrown. + /// tagName is null. + /// No tag with the provided tag name was found. + /// + /// + /// This method only differs from the more generic Get methods in that it also returns a nested tag member if + /// specified by the path. This means the caller can directly and efficiently get either + /// a root tag component, or one of it's nested child members. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + /// + /// + /// + public Tag Get(TagName tagName) + { + if (tagName is null) throw new ArgumentNullException(nameof(tagName)); + + var key = new ComponentKey(nameof(Tag), tagName.Root); + + return Index.Components.TryGetValue(key, out var components) + ? components.Values.First().Deserialize()[tagName.Path] + : throw new KeyNotFoundException($"Tag not found in L5X: {tagName}"); + } + + /// + /// Gets the first in the L5X file having the specified tag name. + /// + /// The of the tag to get. + /// The name of the container (controller, program, instruction) in which the + /// tag should be found. + /// The with the specified key. If a multiple scoped tags with the same name exist, this + /// will return the first tag instance found. If no tag is found, then an exception is thrown. + /// tagName is null. + /// No tag with the provided tag name was found. + /// + /// + /// This method only differs from the more generic Get methods in that it also returns a nested tag member if + /// specified by the path. This means the caller can directly and efficiently get either + /// a root tag component, or one of it's nested child members. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + /// + /// + /// + public Tag Get(TagName tagName, string container) + { + if (tagName is null) throw new ArgumentNullException(nameof(tagName)); + + if (string.IsNullOrEmpty(container)) + throw new ArgumentException("Container can not be null or empty.", nameof(container)); + + var key = new ComponentKey(nameof(Tag), tagName.Root); + + return Index.Components.TryGetValue(key, out var components) && components.TryGetValue(container, out var tag) + ? tag.Deserialize()[tagName.Path] + : throw new KeyNotFoundException($"Tag not found in L5X: {tagName}"); + } + + /// + /// Removes the component(s) with the specified composite type/name key from the L5X file. + /// + /// The of the component to remove. + /// + /// + /// Note that this will remove all components with the type/name pair. For scoped components types such as + /// Tag and Routine this may be more than a single component. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + public void Remove(ComponentKey key) + { + if (!Index.Components.TryGetValue(key, out var components)) return; + foreach (var component in components) + component.Value.Remove(); + } + + /// + /// Removes a single component with the specified composite type/name key and container from the L5X file. + /// + /// The of the component to remove. + /// The container name of the component to remove. + /// + /// + /// This method should only remove a single component as it allows the caller to further define the container + /// or scope the component should be found in. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + public void Remove(ComponentKey key, string container) + { + if (string.IsNullOrEmpty(container)) + throw new ArgumentException("Container can not be null or empty.", nameof(container)); + + if (Index.Components.TryGetValue(key, out var components) && + components.TryGetValue(container, out var element)) + { + element.Remove(); + } + } + + /// + /// Removes the component(s) with the specified type/name from the L5X file. + /// + /// The name of the component to remove. + /// The type of the component to remove. + /// is null or empty. + /// + /// + /// Note that this will remove all components with the type/name pair. For scoped components types such as + /// Tag and Routine this may be more than a single component. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + public void Remove(string name) where TComponent : LogixComponent + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Name can not be null or empty.", nameof(name)); + + var key = new ComponentKey(typeof(TComponent).L5XType(), name); + + if (!Index.Components.TryGetValue(key, out var components)) return; + foreach (var component in components) + component.Value.Remove(); + } + + /// + /// Removes a single component with the specified type/name pair and container from the L5X file. + /// + /// The name of the component to remove. + /// The container of the component to remove. + /// The type of the component to remove. + /// or is null or empty. + /// + /// + /// This method should only remove a single component as it allows the caller to further define the container + /// or scope the component should be found in. + /// + /// + /// This method makes use of the internal component index to find a component efficiently. + /// Since components have (mostly) unique type/name pairs, we can index them and find them quickly. + /// This is needed for operations that might be more computationally complex, such as iterating many components + /// and finding references or dependents. + /// + /// + public void Remove(string name, string container) where TComponent : LogixComponent + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Name can not be null or empty.", nameof(name)); + if (string.IsNullOrEmpty(container)) + throw new ArgumentException("Container can not be null or empty.", nameof(container)); + + var key = new ComponentKey(typeof(TComponent).L5XType(), name); + + if (Index.Components.TryGetValue(key, out var components) && + components.TryGetValue(container, out var element)) + { + element.Remove(); + } + } + + /// + /// Retrieves a collection of objects that reference the specified . + /// + /// The to retrieve references for. + /// A collection of objects that reference the specified . + /// Thrown if is null. + /// + /// + /// A cross reference object contains information about the element and location of the object that has a reference + /// to a given component. This library has a built in mechanism for parsing and indexing both tag and logic references + /// to various components for efficient lookup. This can allow the caller to find references for many objects at a time + /// without having to iterate the L5X multiple times. + /// + /// + public IEnumerable ReferencesTo(LogixComponent component) + { + if (component is null) throw new ArgumentNullException(nameof(component)); + + return Index.References.TryGetValue(component.Key, out var references) + ? references + : Enumerable.Empty(); + } + + /// + /// /// Retrieves a collection of objects that reference the specified component + /// type and name. + /// + /// The name of the component to retrieve references for. + /// The type of component to retrieve references for. + /// A collection of objects that reference the specified . + /// Throw if is null or empty. + /// + /// + /// A cross reference object contains information about the element and location of the object that has a reference + /// to a given component. This library has a built in mechanism for parsing and indexing both tag and logic references + /// to various components for efficient lookup. This can allow the caller to find references for many objects at a time + /// without having to iterate the L5X multiple times. + /// + /// + public IEnumerable ReferencesTo(string name) where TComponent : LogixComponent + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Name can not be null or empty.", nameof(name)); + + var key = new ComponentKey(typeof(TComponent).L5XType(), name); + + return Index.References.TryGetValue(key, out var references) ? references : Enumerable.Empty(); + } + + /// + /// Merges the specified L5X file with the current L5X by adding or overwriting logix components. + /// + /// The file name of L5X to merge. + /// A bit indicating whether to overwrite incoming components of the same name. + /// fileName is null or empty. + public void Import(string fileName, bool overwrite = true) + { + if (string.IsNullOrEmpty(fileName)) + throw new ArgumentException("FileName can not be null or empty.", nameof(fileName)); + var content = Load(fileName); + MergeContent(content, overwrite); + } + + /// + /// Merges another file into the current L5X by adding or overwriting logix components. + /// + /// The to merge. + /// A bit indicating whether to overwrite incoming components of the same name. + /// content is null. + public void Import(L5X content, bool overwrite = true) + { + if (content is null) throw new ArgumentNullException(nameof(content)); + MergeContent(content, overwrite); + } + + /// + /// Serialize this to a file, overwriting an existing file, if it exists. + /// + /// A string that contains the name of the file. + public void Save(string fileName) => SaveContent(fileName); + + /// + public XElement Serialize() => _content; + + /// + public override string ToString() => _content.ToString(); + + #region Internal + + /// + /// Gets a top level container element from the root controller element of the L5X. + /// + /// The name of the container to retrieve. + /// A representing the container with the provided name. + /// The element does not exist. + private XElement GetContainer(string name) => GetController().Element(name) ?? throw _content.L5XError(name); + + /// + /// Gets all primary/top level L5X component containers in the current L5X file. + /// + /// A of representing the L5X component containers. + private IEnumerable GetContainers() => Containers.Select(name => GetController().Element(name)).ToList(); + + /// + /// Gets the root controller element of the L5X file. We expect this to always exist if the L5X is constructed + /// due to the normalization process. + /// + private XElement GetController() => + _content.Element(L5XName.Controller) ?? throw _content.L5XError(L5XName.Controller); + + /// + /// Gets the name of the controller element of the L5X file. Will default to empty string if not found. + /// + private string GetControllerName() => GetController().LogixName(); + + /// + /// Merges all top level containers and their immediate child elements between the current L5X content and the + /// provided L5X content. Will overwrite if specified. + /// + /// The L5X element to merge with the current target element. + /// A flag to indicate whether to overwrite child elements of matching name. + private void MergeContent(L5X l5X, bool overwrite) + { + if (l5X is null) throw new ArgumentNullException(nameof(l5X)); + + var containerPairs = GetContainers() + .Join(l5X.GetContainers(), e => e.Name, e => e.Name, (a, b) => new { a, b }) + .ToList(); + + foreach (var pair in containerPairs) + MergeContainers(pair.a, pair.b, overwrite); + } + + /// + /// Given to top level containers, adds or replaces all child elements matching based on the logix name of the elements. + /// + private static void MergeContainers(XContainer target, XContainer source, bool overwrite) + { + foreach (var element in source.Elements()) + { + var match = target.Elements().FirstOrDefault(e => e.LogixName() == element.LogixName()); + + if (match is null) + { + target.Add(element); + continue; + } + + if (overwrite) + match.ReplaceWith(element); + } + } + + /// + /// Creates a new default content element for a new instance of an L5X file given the provided target name and type. + /// + private static XElement NewContent(string targetName, string targetType, Revision? softwareRevision) + { + var content = new XElement(L5XName.RSLogix5000Content); + content.Add(new XAttribute(L5XName.SchemaRevision, new Revision())); + if (softwareRevision is not null) content.Add(new XAttribute(L5XName.SoftwareRevision, softwareRevision)); + content.Add(new XAttribute(L5XName.TargetName, targetName)); + content.Add(new XAttribute(L5XName.TargetType, targetType)); + content.Add(new XAttribute(L5XName.ContainsContext, targetType != nameof(Controller))); + content.Add(new XAttribute(L5XName.Owner, Environment.UserName)); + content.Add(new XAttribute(L5XName.ExportDate, DateTime.Now.ToString(DateTimeFormat))); + + return content; + } + + /// + /// If no root controller element exists, adds new context controller and moves all root elements into that controller + /// element. Then adds missing top level containers to ensure consistent structure of the root L5X. + /// + private void NormalizeContent() + { + if (_content.Element(L5XName.Controller) is null) + { + var context = new XElement(L5XName.Controller, new XAttribute(L5XName.Use, Use.Context)); + context.Add(_content.Elements()); + _content.RemoveNodes(); + _content.Add(context); + } + + var controller = _content.Element(L5XName.Controller)!; + + foreach (var container in from container in Containers + let existing = controller.Element(container) + where existing is null + select container) + { + controller.Add(new XElement(container)); + } + } + + /// + /// Create document, adds default declaration, and saves the current L5X content to the specified file name. + /// + /// A string that contains the name of the file. + private void SaveContent(string fileName) + { + //This will sanitize containers that were perhaps added when normalizing that went unused. + foreach (var container in GetContainers().Where(c => !c.HasElements)) + container.Remove(); + + var declaration = new XDeclaration("1.0", "UTF-8", "yes"); + var document = new XDocument(declaration); + document.Add(_content); + document.Save(fileName); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/L5XInfo.cs b/src/L5Sharp/L5XInfo.cs new file mode 100644 index 00000000..5f4495cf --- /dev/null +++ b/src/L5Sharp/L5XInfo.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A class containing information regarding the L5X export file. This information is found on the root +/// RSLogix5000Content element, and is used by the Logix software to determine the context of the L5X file. +/// +public class L5XInfo +{ + private readonly XElement _element; + + internal L5XInfo(XElement element) + { + _element = element; + } + + /// + /// Gets the value of the schema revision for the current L5X content. + /// + /// A type that represent the major/minor revision of the L5X schema. + /// This is always 1.0. If the R + public Revision? SchemaRevision + { + get => _element.Attribute(L5XName.SchemaRevision)?.Value.Parse(); + set => _element.SetAttributeValue(L5XName.SchemaRevision, value); + } + + /// + /// Gets the value of the software revision for the current L5X content. + /// + /// A type that represent the major/minor revision of the software. + public Revision? SoftwareRevision + { + get => _element.Attribute(L5XName.SoftwareRevision)?.Value.Parse(); + set => _element.SetAttributeValue(L5XName.SoftwareRevision, value); + } + + /// + /// Gets the name of the Logix component that is the target of the current L5X context. + /// + public string? TargetName + { + get => _element.Attribute(L5XName.TargetName)?.Value; + set => _element.SetAttributeValue(L5XName.TargetName, value); + } + + /// + /// Gets the type of Logix component that is the target of the current L5X context. + /// + public string? TargetType + { + get => _element.Attribute(L5XName.TargetType)?.Value; + set => _element.SetAttributeValue(L5XName.TargetType, value); + } + + /// + /// Gets the value indicating whether the current L5X is contextual.. + /// + public bool? ContainsContext + { + get => _element.Attribute(L5XName.ContainsContext)?.Value.Parse(); + set => _element.SetAttributeValue(L5XName.ContainsContext, value); + } + + /// + /// Gets the owner that exported the current L5X file. + /// + public string? Owner + { + get => _element.Attribute(L5XName.Owner)?.Value; + set => _element.SetAttributeValue(L5XName.Owner, value); + } + + /// + /// Gets the date time that the L5X file was exported. + /// + public DateTime? ExportDate => _element.Attribute(L5XName.ExportDate)?.Value is not null + ? DateTime.ParseExact(_element.Attribute(L5XName.ExportDate)?.Value, L5X.DateTimeFormat, null) + : default; + + /// + /// Gets the set of configured export options for the L5X file. + /// + /// A collection of indicating the option values. + public IEnumerable ExportOptions => _element.Attribute(L5XName.ExportOptions)?.Value + .Split(' ', StringSplitOptions.RemoveEmptyEntries) ?? Enumerable.Empty(); +} \ No newline at end of file diff --git a/src/L5Sharp/Logix.cs b/src/L5Sharp/Logix.cs new file mode 100644 index 00000000..9570da9b --- /dev/null +++ b/src/L5Sharp/Logix.cs @@ -0,0 +1,14 @@ +namespace L5Sharp.Core; + +/// +/// A static factory class for the library. I wanted a nicer name that L5X for loading files +/// +public static class Logix +{ + /// + /// Loads an L5X file and returns its content. + /// + /// The path to the L5X file. + /// The content of the L5X file as an object. + public static L5X Load(string fileName) => L5X.Load(fileName); +} \ No newline at end of file diff --git a/src/L5Sharp/LogixCode.cs b/src/L5Sharp/LogixCode.cs new file mode 100644 index 00000000..d0e941d3 --- /dev/null +++ b/src/L5Sharp/LogixCode.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// An abstract representation of a segment of Logix code found within the content portion of a logix Routine. +/// +/// +/// +/// This class is meant to specify a common set of properties and functions that all code elements, regardless +/// of programming language type (RLL, ST, FBD, SFC) should provide so that we can retrieve information about the contents +/// and location of the code within the L5X file. It is also here to help constrain the type of element that the caller +/// can query from the content of a given routine type. +/// +/// +/// This class overrides the default equality implementation to determine code equality by it's location within the L5X tree. +/// In other words, two instances of code are equal if they are in the same program/instruction, routine, and have the +/// same number. +/// +/// +public abstract class LogixCode : LogixElement, ILogixReferencable +{ + /// + /// Creates a new instance with default values. + /// + protected LogixCode() + { + } + + /// + /// Creates a new initialized with the provided . + /// + /// The to initialize the type with. + /// element is null. + protected LogixCode(XElement element) : base(element) + { + } + + /// + /// The zero based number indicating the position of the code within the containing Routine. + /// + /// A representing the zero-based order. + /// Logix ignores the these number identifiers upon importing, and only considers the order within the + /// containing Routine. This makes the property somewhat useless, but is here all the same as it is + /// inherent to the underlying XMl, and can help identify code elements from deserialized L5X documents. + public virtual int Number + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// + /// + public string Location => $"{L5XType} {Number}".Trim(); + + /// + /// The the parent component for the current LogixCode element. + /// + /// A containing the name of the Routine if found; Otherwise, and empty string. + public Routine? Routine + { + get + { + var routine = Element.Ancestors(L5XName.Routine).FirstOrDefault(); + return routine is not null ? new Routine(routine) : default; + } + } + + /// + /// Returns a collection of objects found within this code element. + /// + /// A of values contained by this code. + public abstract IEnumerable References(); +} \ No newline at end of file diff --git a/src/L5Sharp/LogixComponent.cs b/src/L5Sharp/LogixComponent.cs new file mode 100644 index 00000000..6baebcca --- /dev/null +++ b/src/L5Sharp/LogixComponent.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A common logix element that is able to be identified by name. +/// +/// +/// +/// This is the base class for all logix component classes. All components can be identified by a unique name that +/// is typically subject to the some naming constraints defined by a Rockwell. Logix internally may create +/// components that do not adhere to the naming constraints, which is why the property is a simple string. +/// Names should be unique any attempt to create duplicated names should fail. +/// All components also contain a simple string description and to identify the +/// purpose of the component. +/// +/// +/// All components also contain some common functionality, such as the ability to find dependencies and references, and +/// to be exported individually as a new L5X component file. The default equality implementation is also overridden +/// to determine equality by the component type, name, and scope within the L5X tree. +/// +/// +/// +public abstract class LogixComponent : LogixElement, ILogixReferencable +{ + /// + protected LogixComponent() + { + Element.SetAttributeValue(L5XName.Name, string.Empty); + } + + /// + protected LogixComponent(XElement element) : base(element) + { + } + + /// + /// The of the component within the L5X file. + /// + /// + /// Typically used when exporting individual components (DataType, AoiBlock, Module) to indicate whether the component + /// is the target of the L5X content, or exists solely as a context or dependency of the target component. When + /// saving a project as an L5X, the top level controller component is the target, and all other components will + /// not have this property. + /// + public Use? Use + { + get => GetValue(); + set => SetValue(value); + } + + /// + /// The unique name of the component. + /// + /// A representing the component name. + /// + /// The name servers as a unique identifier for various types of components. + /// In most cases, the component name should satisfy Logix naming constraints of alphanumeric and + /// underscore ('_') characters, start with a letter, and be between 1 and 40 characters. + /// Validation is not performed by this library, so importing components with invalid names may fail. + /// + public virtual string Name + { + get => GetRequiredValue(); + set => SetRequiredValue(value); + } + + /// + /// The description of the component. + /// + /// A containing the component description if it exists; Otherwise, null. + public virtual string? Description + { + get => GetProperty(); + set => SetDescription(value); + } + + /// + /// The global unique identifier of the component. + /// + /// + /// A value representing composite set of properties that identify this component + /// within an L5X tree. + /// + public ComponentKey Key => new(GetType().L5XType(), Name); + + /// + /// Returns a collection of that this component depends on to be valid within a given + /// L5X file. + /// + /// A containing all distinct objects this + /// component depends on. + /// + /// This is primarily useful for exporting individual components to a new L5X file. It allows them to also + /// bring along all the other components they would need to be successfully importing into a logix project file. + /// Each derived component implements this method since the dependencies are different for each type. + /// + public virtual IEnumerable Dependencies() => Enumerable.Empty(); + + /// + /// Deletes this component and it's references from the current attached L5X file. + /// + /// + /// This method can be helpful for completely scrubbing a L5X file of the specific component, which means removing + /// references to it as well as the component itself. This is on contrast to + /// which will simply remove this element from the parent container. If this component is not attached to an L5X + /// then it will simply return and not throw any exceptions. Obviously, use this with caution as you will not be able + /// to undo the process except for the fact that you have reference to component being deleted. + /// + public virtual void Delete() + { + if (Element.Parent is null || !IsAttached) return; + + var references = References(); + + foreach (var reference in references) + { + reference.Element.Remove(); + } + + Element.Remove(); + } + + /// + /// Creates a new with the provided logix component as the target type. + /// + /// The optional software revision, or version of Studio to export the component as. + /// A containing the component as the target of the L5X. + public virtual L5X Export(Revision? softwareRevision = null) + { + Use = Use.Target; + softwareRevision ??= L5X?.Info.SoftwareRevision; + + var content = L5X.New(this, softwareRevision); + + var dependencies = Dependencies().ToList(); + foreach (var dependency in dependencies) + { + dependency.Use = Use.Context; + content.Add(dependency); + } + + return content; + } + + /// + /// Returns a collection of all objects that reference this component by name. + /// + /// + /// A containing objects that have + /// at least one property value referencing this component's name. + /// + public IEnumerable References() => + L5X is not null ? L5X.ReferencesTo(this) : Enumerable.Empty(); + + /// + /// This override returns the component name of the type. + public override string ToString() => Name; + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(this, obj)) return true; + + return obj switch + { + LogixComponent other => Equals(Key, other.Key), + _ => false + }; + } + + /// + public override int GetHashCode() => Key.GetHashCode(); +} \ No newline at end of file diff --git a/src/L5Sharp/LogixContainer.cs b/src/L5Sharp/LogixContainer.cs new file mode 100644 index 00000000..c36592dc --- /dev/null +++ b/src/L5Sharp/LogixContainer.cs @@ -0,0 +1,359 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A generic collection that provides operations over an underlying container +/// of objects. +/// +/// The type inheriting . +/// +/// +/// This class represents a wrapper around a L5X element that contains a sequence of child elements of the same type. +/// This class exposes collection methods for querying and modifying child elements of the container. +/// Note that a container could potentially contain more than one type of child element, but this container class will +/// only operate over the specified element type. +/// +/// +/// The class is designed to only offer very basic operations, allowing it to be applicable to all container type elements, +/// However, the user can extended the API for any container type using extension methods and +/// to get the underlying container object. See for examples. +/// +/// +public class LogixContainer : IEnumerable, ILogixSerializable where TElement : LogixElement +{ + /// + /// The underlying representing the backing data for the container. Use this object to store + /// and retrieve data for the the collection. + /// + private readonly XElement _element; + + /// + /// The type name of the child elements in the container. This is needed as we support containers with different + /// element types, so we need to know the name of the correct type to return and deserialize. + /// This also allows types with secondary element names to be synced as they are added to the container. + /// + private readonly XName _type; + + /// + /// Creates a empty with the default type name. + /// + public LogixContainer() + { + _element = new XElement(typeof(TElement).L5XContainer()); + _type = typeof(TElement).L5XType(); + } + + /// + /// Creates a new initialized with the provided . + /// + /// The containing a collection of elements. + /// Optionally the type name of the child elements of the container. Will default to the + /// configured L5XType name if not provided. + /// container is null. + public LogixContainer(XElement container, XName? type = null) + { + _element = container ?? throw new ArgumentNullException(nameof(container)); + _type = type ?? typeof(TElement).L5XType(); + } + + /// + /// Creates a empty with the specified type name. + /// + /// The name of the container element. + /// Optionally the type name of the child elements of the container. Will default to the + /// configured L5XType name if not provided. + public LogixContainer(XName name, XName? type = null) + { + _element = new XElement(name); + _type = type ?? typeof(TElement).L5XType(); + } + + /// + /// Creates a new initialized with the provided collection. + /// + /// The collection of elements to initialize. + public LogixContainer(IEnumerable elements) : this() + { + if (elements is null) + throw new ArgumentNullException(nameof(elements)); + + foreach (var element in elements) + { + if (element is null) + throw new ArgumentNullException(nameof(element)); + + var xml = element.L5XType == _type + ? element.Serialize() + : element.Convert(_type.LocalName).Serialize(); + + _element.Add(xml); + } + } + + /// + /// Indicates whether this container is attached to a L5X document. + /// + /// true if this is an attached container; Otherwise, false. + /// + /// This simply looks to see if the element has a ancestor with the root RSLogix5000Content element or not. + /// If so we will assume this element is attached to an overall L5X document. + /// + public bool IsAttached => _element.Ancestors(L5XName.RSLogix5000Content).Any(); + + /// + /// Returns the instance this is attached to if it is attached. + /// + /// + /// If the current element is attached to a L5X document (i.e. has the root content element), + /// then the instance; Otherwise, null. + /// + /// + /// This allows attached logix elements to reach up to the L5X file in order to traverse or retrieve + /// other elements in the L5X. This is helpful/used for other extensions and cross referencing functions. + /// + public L5X? L5X => _element.Ancestors(L5XName.RSLogix5000Content).FirstOrDefault()?.Annotation(); + + /// + /// Accesses a single element at the specified index of the container collection. + /// + /// The zero based index of the element to retrieve. + /// The element at the specified position in the source container. + /// index is less than zero or greater than or equal to the + /// number of elements in the collection. + /// value is null when setting index. + public TElement this[int index] + { + get => _element.Elements(_type).ElementAt(index).Deserialize(); + set + { + if (value is null) + throw new ArgumentNullException(nameof(value), + $"Can not set container element of type {typeof(TElement)} null instance."); + + var xml = value.L5XType == _type + ? value.Serialize() + : value.Convert(_type.LocalName).Serialize(); + + _element.Elements(_type).ElementAt(index).ReplaceWith(xml); + } + } + + /// + /// Adds the provided element to the logix container at the end of the collection. + /// + /// The element to add. + /// element is null. + public void Add(TElement element) + { + if (element is null) + throw new ArgumentNullException(nameof(element)); + + var xml = element.L5XType == _type + ? element.Serialize() + : element.Convert(_type.LocalName).Serialize(); + + var last = _element.Elements(_type).LastOrDefault(); + + if (last is null) + { + _element.Add(xml); + return; + } + + last.AddAfterSelf(xml); + } + + /// + /// Adds the provided elements to the logix container at the end of the collection. + /// + /// The collection of elements to add. + /// elements or any element in elements is null. + public void AddRange(IEnumerable elements) + { + if (elements is null) + throw new ArgumentNullException(nameof(elements)); + + foreach (var element in elements) + { + if (element is null) + throw new ArgumentNullException(nameof(element)); + + var xml = element.L5XType == _type + ? element.Serialize() + : element.Convert(_type.LocalName).Serialize(); + + var last = _element.Elements(_type).LastOrDefault(); + + if (last is null) + { + _element.Add(xml); + continue; + } + + last.AddAfterSelf(xml); + } + } + + /// + /// Gets the number of elements in the collection. + /// + /// A representing the number of elements in the collection. + public int Count() => _element.Elements(_type).Count(); + + /// + /// Inserts the provided element at the specified index of the container collection. + /// + /// The zero based index at which to insert the element. + /// The element to insert. + /// element is null. + /// index is less than zero or greater than or equal to the + /// number of elements in the collection. + public void Insert(int index, TElement element) + { + if (element is null) + throw new ArgumentNullException(nameof(element)); + + var xml = element.L5XType == _type + ? element.Serialize() + : element.Convert(_type.LocalName).Serialize(); + + _element.Elements(_type).ElementAt(index).AddBeforeSelf(xml); + } + + /// + /// Removes all elements in the container collection. + /// + public void RemoveAll() => _element.Elements(_type).Remove(); + + /// + /// Removes all elements that satisfy the provided condition predicate. + /// + /// The condition for which to remove elements. + /// condition is null. + public void RemoveAll(Func condition) + { + if (condition is null) throw new ArgumentNullException(nameof(condition)); + _element.Elements(_type).Where(e => condition.Invoke(e.Deserialize())).Remove(); + } + + /// + /// Removes a element at the specified index of the collection. + /// + /// The zero based index of the element to remove. + /// index is less than zero or greater than or equal to the + /// number of elements in the collection. + public void RemoveAt(int index) + { + _element.Elements(_type).ElementAt(index).Remove(); + } + + /// + /// Updates all elements in the container by applying the provided update action delegate. + /// + /// A update to apply to each element. + /// update is null. + public void Update(Action update) + { + if (update is null) throw new ArgumentNullException(nameof(update)); + + foreach (var child in _element.Elements(_type)) + { + var element = child.Deserialize(); + update.Invoke(element); + } + } + + /// + /// Updates all elements in the container that satisfy the provided condition predicate by applying the provided + /// update action delegate. + /// + /// A update to apply to each element. + /// The condition for which to update elements. + /// update or condition is null. + public void Update(Action update, Func condition) + { + if (update is null) throw new ArgumentNullException(nameof(update)); + if (condition is null) throw new ArgumentNullException(nameof(condition)); + + foreach (var child in _element.Elements(_type)) + { + var element = child.Deserialize(); + if (condition.Invoke(element)) + update.Invoke(element); + } + } + + /// + public XElement Serialize() => _element; + + /// + public IEnumerator GetEnumerator() => + _element.Elements(_type).Select(e => e.Deserialize()).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} + +/// +/// Extensions methods to the class. +/// +public static class ContainerExtensions +{ + /// + /// Determines if a component with the specified name exists in the container. + /// + /// The logix container of component objets. + /// The name of the component to find. + /// true if a component with the specified name exists; otherwise, false. + public static bool Contains(this LogixContainer container, string name) + where TComponent : LogixComponent + { + return container.Serialize().Elements().Any(e => e.LogixName() == name); + } + + /// + /// Returns a component with the specified name if it exists in the container, otherwise returns null. + /// + /// The logix container of component objets. + /// The name of the component to find. + /// The component type to return. + /// A of the specified type if found; Otherwise, null. + public static TComponent? Find(this LogixContainer container, string name) + where TComponent : LogixComponent + { + return container.Serialize().Elements().FirstOrDefault(e => e.LogixName() == name)?.Deserialize(); + } + + /// + /// Returns a component with the specified name from the container. + /// + /// The logix container of component objets. + /// The name of the component to find. + /// The component type to return. + /// A of the specified type. + /// No component having name exists in the container. + public static TComponent Get(this LogixContainer container, string name) + where TComponent : LogixComponent + { + var element = container.Serialize(); + var component = element.Elements().SingleOrDefault(e => e.LogixName() == name); + return component is not null + ? component.Deserialize() + : throw new InvalidOperationException($"No component with name {name} was found in container."); + } + + /// + /// Removes a component with the specified name from the container. + /// + /// The logix container of component objets. + /// The name of the component to remove. + public static void Remove(this LogixContainer container, string name) + where TComponent : LogixComponent + { + container.Serialize().Elements().SingleOrDefault(c => string.Equals(c.LogixName(), name))?.Remove(); + } +} \ No newline at end of file diff --git a/src/L5Sharp/LogixData.cs b/src/L5Sharp/LogixData.cs new file mode 100644 index 00000000..b519ceb8 --- /dev/null +++ b/src/L5Sharp/LogixData.cs @@ -0,0 +1,279 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A specialized static factory for deserializing objects from . +/// +/// +/// This class is built to specifically find and create concrete instances of atomic, predefined, and custom +/// user defined logix types at runtime. This allows the user to cast a given logix type down to the most specific +/// type as it is deserialized from the L5X tag data structure. +/// +public static class LogixData +{ + /// + /// A system wide lookup of all objects by L5XType name obtained using reflection. This does + /// not include the internal generic types such as StructureType, ComplexType, StringType, ArrayType, or ArrayType{T}, + /// or NullType, but rather types that can be instantiated (atomic or complex) for which we may need to create a generic + /// array of. + /// + private static readonly Dictionary Lookup = + AppDomain.CurrentDomain.GetAssemblies().SelectMany(FindLogixTypes).ToDictionary(k => k.L5XType(), v => v); + + /// + /// The global cache for all and object deserializer + /// delegate functions. This is what we are using to create strongly typed logix type objects at runtime. + /// + private static readonly Lazy>> Deserializers = new(() => + AppDomain.CurrentDomain.GetAssemblies().Distinct().SelectMany(Introspect) + .ToDictionary(k => k.Key, k => k.Value), LazyThreadSafetyMode.ExecutionAndPublication); + + /// + /// Returns the singleton null object. + /// + public static LogixType Null => NullType.Instance; + + /// + /// Deserializes an into a . + /// + /// The element to deserialize. + /// A representing the data value, structure, or array or the provided element. + /// element is null. + /// element has a name that is not supported for deserialization. + public static LogixType Deserialize(XElement element) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + + return element.Name.ToString() switch + { + L5XName.Tag => DeserializeData(element), + L5XName.LocalTag => DeserializeData(element), + L5XName.Parameter => DeserializeData(element), + L5XName.ConfigTag => DeserializeData(element), + L5XName.InputTag => DeserializeData(element), + L5XName.OutputTag => DeserializeData(element), + L5XName.Data => DeserializeFormatted(element), + L5XName.DefaultData => DeserializeFormatted(element), + L5XName.DataValue => DeserializeAtomic(element), + L5XName.DataValueMember => DeserializeAtomic(element), + L5XName.Element => DeserializeElement(element), + L5XName.Array => DeserializeArray(element), + L5XName.ArrayMember => DeserializeArray(element), + L5XName.Structure => DeserializeStructure(element), + L5XName.StructureMember => DeserializeStructure(element), + L5XName.AlarmAnalogParameters => new ALARM_ANALOG(element), + L5XName.AlarmDigitalParameters => new ALARM_DIGITAL(element), + L5XName.MessageParameters => new MESSAGE(element), + _ => throw new NotSupportedException( + $"The element name '{element.Name}' is not able to be used to deserialize a logix type.") + }; + } + + /// + /// Determines if a type with the specified type name is registered for deserialization. + /// + /// The data type name to check. + /// true if the type is registered; otherwise, false. + public static bool IsRegistered(string dataType) => Deserializers.Value.ContainsKey(dataType); + + /// + /// Register a custom so that it may be instantiated during deserialization of a L5X. + /// The type must implement a constructor accepting a single argument. + /// + /// The type of the logix type. + /// TStructure is abstract -or- does not have a public constructor + /// accepting a single object. + /// TStructure is already registered. + public static void Register() where TStructure : StructureType + { + var type = typeof(TStructure); + var key = type.L5XType(); + var deserializer = type.Deserializer(); + + if (!Deserializers.Value.TryAdd(key, deserializer)) + throw new InvalidOperationException($"The type {key} is already registered."); + } + + /// + /// Scans the provided assembly using reflection for public non-abstract types inheriting + /// that have the required deserialization constructor and registers the type so that it may be instantiated during + /// deserialization of a L5X. + /// + /// The assembly to scan. + /// A type is already registered. + /// + /// This is to assist with easily registering types within a specific assembly. + /// + public static void Scan(Assembly assembly) + { + foreach (var pair in Introspect(assembly).Where(pair => !Deserializers.Value.TryAdd(pair.Key, pair.Value))) + throw new InvalidOperationException($"The type {pair.Key} is already registered."); + } + + /// + /// Handles deserializing the data element of the root tag. This method will forward call down the chain + /// based on the format of the data structure. + /// + private static LogixType DeserializeData(XContainer element) + { + var data = element.Elements() + .FirstOrDefault(e => DataFormat.Supported.Any(f => f == e.Attribute(L5XName.Format)?.Value)); + + return data is not null ? Deserialize(data) : Null; + } + + /// + /// Handles deserializing a formatted data element to a logix type. This method will forward call down the chain + /// based on the format of the data structure. + /// + private static LogixType DeserializeFormatted(XElement element) + { + DataFormat.TryFromName(element.Attribute(L5XName.Format)?.Value, out var format); + if (format is null) return Null; + if (format == DataFormat.String) return DeserializeString(element); + return element.FirstNode is XElement root ? Deserialize(root) : Null; + } + + /// + /// Handles deserializing an element to a atomic value type logix type. + /// This method will call upon to generate the known concrete atomic type we need. + /// This implementation ignores any specified Radix because we have found cases where the Radix does not match + /// the actual format of the value, therefore, we always infer the format, and then parse. + /// + private static LogixType DeserializeAtomic(XElement element) + { + var dataType = element.Get(L5XName.DataType); + var value = element.Get(L5XName.Value); + return Atomic.Parse(dataType, value); + } + + /// + /// Handles deserializing an array element to a ... + /// + private static LogixType DeserializeArray(XElement element) + { + var dataType = element.Get(L5XName.DataType); + + //We either know the type (atomic or registered), or we create the generic string or complex type. + var type = Lookup.TryGetValue(dataType, out var known) ? known + : HasStringStructure(element) ? typeof(StringType) + : typeof(ComplexType); + + var arrayType = typeof(ArrayType<>).MakeGenericType(type); + + if (Deserializers.Value.TryGetValue(arrayType.FullName, out var cached)) + return cached.Invoke(element); + + var deserializer = arrayType.Deserializer(); + Deserializers.Value.Add(arrayType.FullName, deserializer); + return deserializer.Invoke(element); + } + + /// + /// Handles deserializing an array index element to a logix type, either atomic, string, or structure, + /// depending on the state of the element. + /// + private static LogixType DeserializeElement(XElement element) + { + var dataType = element.Parent?.Get(L5XName.DataType) ?? throw element.L5XError(L5XName.DataType); + var value = element.Attribute(L5XName.Value); + var structure = element.Element(L5XName.Structure); + + return value is not null ? Atomic.Parse(dataType, value.Value) + : structure is not null ? DeserializeStructure(structure) + : throw element.L5XError(L5XName.Element); + } + + /// + /// Handles deserializing an element to a logix type. + /// Will check for registered types to create the concrete type if available. + /// Otherwise we resort to a more generic of string element structures or + /// for everything else. + /// + private static LogixType DeserializeStructure(XElement element) + { + var dataType = element.Get(L5XName.DataType); + if (IsRegistered(dataType)) return Deserializers.Value[dataType].Invoke(element); + return HasStringStructure(element) ? new StringType(element) : new ComplexType(element); + } + + /// + /// Handles deserializing an element to a logix type. + /// Will check for registered type to create the concrete type if available. + /// Otherwise we resort to a more generic object. + /// + private static LogixType DeserializeString(XElement element) + { + var dataType = element.Parent?.Get(L5XName.DataType) ?? throw element.L5XError(L5XName.DataType); + return IsRegistered(dataType) ? Deserializers.Value[dataType].Invoke(element) : new StringType(element); + } + + /// + /// Determines if the provided element has a structure that represents a structure, + /// structure member, array, or array member. This is needed to determine if we are deserializing a complex type + /// or string type. String structure is unique in that it will have a data value member called DATA with a ASCII + /// radix, a non-null element value, and a data type attribute value equal to that of the parent structure element attribute. + /// + private static bool HasStringStructure(XElement element) + { + //If this is a structure or structure member it could potentially be the string structure. + if (element.Name == L5XName.Structure || element.Name == L5XName.StructureMember) + { + return element.Elements(L5XName.DataValueMember).Any(e => + e?.Value is not null + && e.Attribute(L5XName.Name)?.Value == "DATA" + && e.Attribute(L5XName.DataType)?.Value == e.Parent?.Attribute(L5XName.DataType)?.Value + && e.Attribute(L5XName.Radix)?.Value == "ASCII"); + } + + //If this is an array or array member, we need to get elements and check if they are all string structure or not. + if (element.Name == L5XName.Array || element.Name == L5XName.ArrayMember) + { + return element.Elements().Select(e => e.Element(L5XName.Structure)).All(HasStringStructure); + } + + return false; + } + + /// + /// Performs reflection scanning of provided to get all public non abstract types + /// inheriting from or that have the supported + /// deserialization constructor, and returns the L5XType and compiled deserialization delegate pair. + /// This is used to initialize the set of concrete deserializer functions for all know predefined and user defined + /// logix type objects. + /// + private static IEnumerable>> Introspect(Assembly assembly) + { + var types = assembly.GetTypes().Where(t => + (typeof(StructureType).IsAssignableFrom(t) || typeof(StringType).IsAssignableFrom(t)) + && t != typeof(ComplexType) && t != typeof(StringType) + && t is {IsAbstract: false, IsPublic: true} + && t.GetConstructor(new[] {typeof(XElement)}) is not null); + + foreach (var type in types) + { + var deserializer = type.Deserializer(); + yield return new KeyValuePair>(type.L5XType(), deserializer); + } + } + + /// + /// Performs reflection scanning of provided to get all public non abstract types + /// inheriting from . + /// + private static IEnumerable FindLogixTypes(Assembly assembly) + { + return assembly.GetTypes().Where(t => + typeof(LogixType).IsAssignableFrom(t) + && t is {IsAbstract: false, IsPublic: true} + && t != typeof(ComplexType) && t != typeof(StringType) + && t != typeof(ArrayType) && t != typeof(ArrayType<>) + && t != typeof(NullType)); + } +} \ No newline at end of file diff --git a/src/L5Sharp/LogixElement.cs b/src/L5Sharp/LogixElement.cs new file mode 100644 index 00000000..2b7258c0 --- /dev/null +++ b/src/L5Sharp/LogixElement.cs @@ -0,0 +1,767 @@ +using System; +using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Xml.Linq; +using JetBrains.Annotations; + +namespace L5Sharp.Core; + +/// +/// A base class for all types that can be serialized and deserialized from a L5X file. This abstract class enforces +/// the interface and a constructor taking a for initialization +/// of and underlying element object. Deriving classes will have access to the underlying element and +/// methods for easily getting and setting data. Implementing classes need to also provide at least a constructor taking +/// a single and pass it to the base constructor to be deserializable by the library. +/// +[PublicAPI] +public abstract class LogixElement : ILogixSerializable +{ + /// + /// Creates a new default initialized with an having the + /// L5XType name of the element. + /// + protected LogixElement() + { + Element = new XElement(GetType().L5XType()); + } + + /// + /// Initializes a new with the provided + /// + /// The L5X to initialize the entity with. + /// element is null. + /// element name does not match any configured L5XType for this class + /// type. These are defined via the on the derived classes. + protected LogixElement(XElement element) + { + Element = element ?? throw new ArgumentNullException(nameof(element)); + } + + /// + /// The underlying representing the backing data for the entity. Use this object to store + /// and retrieve data for the component. This property is the basis for serialization and deserialization of + /// L5X data. + /// + protected readonly XElement Element; + + /// + /// Indicates whether this element is attached to a L5X document. + /// + /// true if this is an attached element; Otherwise, false. + /// + /// This simply looks to see if the element has a ancestor with the root RSLogix5000Content element or not. + /// If so we will assume this element is attached to an overall L5X document. + /// + public bool IsAttached => Element.Ancestors(L5XName.RSLogix5000Content).Any(); + + /// + /// Returns the instance this is attached to if it is attached. + /// + /// + /// If the current element is attached to a L5X document (i.e. has the root content element), + /// then the instance; Otherwise, null. + /// + /// + /// This allows attached logix elements to reach up to the L5X file in order to traverse or retrieve + /// other elements in the L5X. This is helpful/used for other extensions and cross referencing functions. + /// + public L5X? L5X => Element.Ancestors(L5XName.RSLogix5000Content).FirstOrDefault()?.Annotation(); + + /// + /// Returns the name of the L5XType for this object. + /// + /// A representing the name of the L5X element type. + /// + /// The "L5XType" is nothing more than the name of the underlying for the object. + /// Most L5X element names correspond to a class type of the library with the same or similar name. + /// However, each class type in this library can also support multiple element types (e.g. Tag/LocalTag/ConfigTag). + /// This property will indicate the actual type of the underlying element as opposed to the actual class type of + /// the object. + /// + public string L5XType => Element.Name.LocalName; + + /// + /// The scope of the element, indicating whether it is a globally scoped controller element, + /// a locally scoped program or instruction element, or neither (not attached to L5X tree). + /// + /// A option indicating the scope type for the element. + /// + /// + /// The scope of an element is determined from the ancestors of the underlying . + /// If the ancestor is a program element first, it is Program scoped. + /// If the ancestor is a AddOnInstructionDefinition element first, it is Instruction scoped. + /// If the ancestor is a controller element first, it is Controller scoped. + /// If no ancestor is found, we assume the component has Null scope, meaning it is not attached to a L5X tree. + /// + /// + /// This property is not inherent in the underlying XML (not serialized), but one that adds a lot of + /// value as it helps uniquely identify elements within the L5X file, especially + /// Tag and Routine components. + /// + /// + public Scope Scope => Scope.Type(Element); + + /// + /// The logix name of an ancestral element, indicating the program, instruction, or controller + /// for which this element is contained. This is essentially the scope name for a given logix element. + /// + /// + /// A representing the name of the program, instruction, or controller in which this component + /// is contained. If the component has scope, then an string. + /// + /// + /// + /// This value is retrieved from the ancestors of the underlying element. If no ancestors exists, meaning this + /// element is not attached to a L5X tree, then this returns an empty string. + /// + /// + /// This property is not inherent in the underlying XML (not serialized), but one that adds a lot of + /// value as it helps uniquely identify elements within the L5X file, especially scoped components with same name. + /// + /// + public string Container => Scope.Container(Element); + + /// + /// Adds a new element of the same type directly after this element in the L5X document. + /// + /// The logix element to add. + /// element is null. + /// No parent exists for the underlying element -or- + /// the provided logix element is not the same type or convertable to the type of this logix element. + /// + /// + /// This method requires the component be attached to the , as it will + /// access the parent of the underlying to perform the function. + /// It will also automatically perform the "type conversion" of the provided element if possible. + /// This just means it will attempt to change the element name to match this element name so that the + /// underlying element type will have the correct sequence name. This is used primarily for types that support + /// multiple elements (i.e. Tags). + /// + public void AddAfter(LogixElement element) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + + if (Element.Parent is null) + throw new InvalidOperationException( + "Can only perform operation for L5X attached elements. Add this element to the logix content before invoking."); + + if (element.L5XType != L5XType) + { + element = element.Convert(L5XType); + } + + Element.AddAfterSelf(element.Serialize()); + } + + /// + /// Adds a new element of the same type directly before this element in the L5X document. + /// + /// The logix element to add. + /// element is null. + /// No parent exists for the underlying element -or- + /// the provided logix element is not the same type or convertable to the type of this logix element. + /// + /// + /// This method requires the component be attached to the , as it will + /// access the parent of the underlying to perform the function. + /// It will also automatically perform the "type conversion" of the provided element if possible. + /// This just means it will attempt to change the element name to match this element name so that the + /// underlying element type will have the correct sequence name. This is used primarily for types that support + /// multiple elements (i.e. Tags). + /// + public void AddBefore(LogixElement element) + { + if (element is null) + throw new ArgumentNullException(nameof(element)); + + if (Element.Parent is null) + throw new InvalidOperationException( + "Can only perform operation for L5X attached elements. Add this element to the L5X before invoking."); + + if (element.L5XType != L5XType) + { + element = element.Convert(L5XType); + } + + Element.AddBeforeSelf(element.Serialize()); + } + + /// + /// Returns a new deep cloned instance of the current type. + /// + /// A new type with the same property values. + /// The object being cloned does not have a constructor accepting a + /// single argument. + /// This method will simply deserialize a new instance using the current underlying element data. + public LogixElement Clone() => new XElement(Element).Deserialize(); + + /// + /// Returns a new deep cloned instance as the specified type. + /// + /// The type to cast to. + /// A new instance of the specified element type with the same property values. + /// The object being cloned does not have a constructor accepting a + /// single argument. + /// The deserialized type can not be cast to the specified generic type parameter. + /// This method will simply deserialize a new instance using the current underlying element data. + public TElement Clone() where TElement : LogixElement => new XElement(Element).Deserialize(); + + /// + /// Converts this element to the specified element type name. + /// + /// The name of the element type to convert this logix element to. + /// A new instance with the converted element name. + /// typeName is null or empty. + /// The specified type name is not supported by this class type. + /// + /// This simply updates the underlying element name to the provided name if it is a supported type name for this + /// logix element class type, which is configured via the for the derived type. This + /// is primarily needed so we can ensure adding elements of types that support multiple element names can be updated + /// to the correct element name and ensure a proper L5X sequence within the document. For example, adding a Tag + /// to a collection of AOI LocalTags needs the underlying element name updated to LocalTag in that case. + /// + public LogixElement Convert(string typeName) + { + if (string.IsNullOrEmpty(typeName)) + throw new ArgumentException("Type name can not be null or empty to perform conversion", nameof(typeName)); + + if (!GetType().L5XTypes().Contains(typeName)) + throw new InvalidOperationException($"Can not convert element type '{L5XType}' to type '{typeName}'."); + + if (L5XType == typeName) return this; + + Element.Name = typeName; + return Element.Deserialize(); + } + + /// + /// Converts this element to the specified element type name. + /// + /// The name of the element type to convert this logix element to. This will default to the + /// first configured L5XType of the specified logix type parameter, but can optionally be provided if the type + /// to convert to is not the default L5XType name. + /// The type to convert this element type to. + /// A new of the specified generic type with the converted element name. + /// The specified type name is not supported by this class type. + /// + /// This simply updates the underlying element name to the provided name if it is a supported type name for this + /// logix element class type, which is configured via the for the derived type. This + /// is primarily needed so we can ensure adding elements of types that support multiple element names can be updated + /// to the correct element name and ensure a proper L5X sequence within the document. For example, adding a Tag + /// to a collection of AOI LocalTags needs the underlying element name updated to LocalTag in that case. + /// + public TElement Convert(string? typeName = null) where TElement : LogixElement + { + typeName ??= typeof(TElement).L5XType(); + + if (!GetType().L5XTypes().Contains(typeName)) + throw new InvalidOperationException($"Can not convert element type '{L5XType}' to type '{typeName}'."); + + if (L5XType == typeName) return (TElement)this; + + Element.Name = typeName; + return Element.Deserialize(); + } + + /// + /// Removes the element from it's parent container. + /// + /// No parent exists for the underlying element. + /// This could happen if the component was created in memory and not yet added to the L5X. + /// + /// + /// This method requires the element be attached to the , or at least have a parent + /// containing element as it will access the parent of the underlying to perform the function. + /// + public void Remove() + { + if (Element.Parent is null) + throw new InvalidOperationException( + "Can only perform operation for L5X attached elements. Add this element to the L5X before invoking."); + + Element.Remove(); + } + + /// + /// Replaces the element instance with a new instance of the same type. + /// + /// The new logix element to replace this element with. + /// element is null. + /// No parent exists for the underlying element -or- + /// the provided logix element is not the same type or convertable to the type of this logix element. + /// + /// + /// This method requires the component be attached to the , as it will + /// access the parent of the underlying to perform the function. + /// It will also automatically perform the "type conversion" of the provided element if possible. + /// This just means it will attempt to change the element name to match this element name so that the + /// underlying element type will have the correct sequence name. This is used primarily for types that support + /// multiple elements (i.e. Tags). + /// + public void Replace(LogixElement element) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + + if (Element.Parent is null) + throw new InvalidOperationException( + "Can only perform operation for L5X attached elements. Add this element to the L5X before invoking."); + + if (element.L5XType != L5XType) + { + element = element.Convert(L5XType); + } + + Element.ReplaceWith(element.Serialize()); + } + + /// + /// Returns the underlying for the . + /// + /// A representing the serialized logix element. + /// + /// + /// All logix elements are backed by an underlying through which derived classes + /// get and set properties. This means all classes in this library can be viewed as wrapper around an + /// or segment of XMl, and use deferred execution for retrieving and setting data. + /// + /// + /// This method exposes the underlying element for extension and serialization purposes. + /// Take care not to mutate the underlying element in a way that makes the schema invalid and non-importable. + /// + /// + public virtual XElement Serialize() => Element; + + /// + /// Gets the value of the specified attribute name from the element parsed as the specified generic type parameter if it exists. + /// If the attribute does not exist, returns default value of the generic type parameter. + /// + /// The name of the attribute. + /// The return type of the value. + /// + /// If found, the value of attribute parsed as the generic type parameter. + /// If not found, returns default. + /// + /// + /// This method makes getting/setting data on as concise as possible from derived classes. + /// This method uses the so the deriving classes don't have to specify + /// the property name (assuming its the name matches the underlying element property). + /// + protected T? GetValue([CallerMemberName] string? name = null) + { + var value = Element.Attribute(name)?.Value; + return value is not null ? value.Parse() : default; + } + + /// + /// Gets the value of the selected attribute parsed as the specified generic type parameter if it exists. + /// If the attribute does not exist, returns default value of the generic type parameter. + /// + /// A selection delegate that allows custom selection of a element relative to . + /// Use this to reach down the element hierarchy for nested values. + /// The name of the attribute. + /// The return type of the value. + /// + /// If found, the value of attribute parsed as the generic type parameter. + /// If not found, returns default. + /// + /// + /// This method makes getting/setting data on as concise as possible from derived classes. + /// This method uses the so the deriving classes don't have to specify + /// the property name (assuming its the name matches the underlying element property). + /// + protected T? GetValue(Func selector, [CallerMemberName] string? name = null) + { + var value = selector.Invoke(Element)?.Attribute(name)?.Value; + return value is not null ? value.Parse() : default; + } + + /// + /// Gets the value of a child element attribute parsed as the specified generic type parameter if it exists. + /// If the attribute does not exist, returns default value of the generic type parameter. + /// + /// The name of the child element containing the attribute value to retrieve. + /// The name of the attribute. + /// The return type of the value. + /// + /// If found, the value of attribute parsed as the generic type parameter. + /// If not found, returns default. + /// + /// + /// This method makes getting/setting data on as concise as possible from derived classes. + /// This method uses the so the deriving classes don't have to specify + /// the property name (assuming its the name matches the underlying element property). + /// + protected T? GetValue(XName child, [CallerMemberName] string? name = null) + { + var value = Element.Element(child)?.Attribute(name)?.Value; + return value is not null ? value.Parse() : default; + } + + /// + /// Gets the value of the specified attribute name from the element parsed as the specified generic type parameter. + /// + /// The name of the attribute. + /// The return type of the value. + /// The value of attribute parsed as the generic type parameter. + /// No attribute with the provided name was found on . + /// + /// This method makes getting/setting data on as concise as possible from derived classes. + /// This method uses the so the deriving classes don't have to specify + /// the property name (assuming its the name matches the underlying element property). + /// + protected T GetRequiredValue([CallerMemberName] string? name = null) + { + var value = Element.Attribute(name)?.Value; + return value is not null ? value.Parse() : throw Element.L5XError(name!); + } + + /// + /// Gets the value of the specified child element parsed as the specified generic type parameter if it exists. + /// If the element does not exist, returns default value of the generic type parameter. + /// + /// The name of the child element. + /// The return type of the value. + /// + /// If found, the value of child element parsed as the generic type parameter. + /// If not found, returns default. + /// + /// + /// This method makes getting/setting data on as concise as possible from derived classes. + /// This method uses the so the deriving classes don't have to specify + /// the property name (assuming its the name matches the underlying element property). + /// + protected T? GetProperty([CallerMemberName] string? name = null) + { + var value = Element.Element(name)?.Value; + return value is not null ? value.Parse() : default; + } + + /// + /// Gets a immediate child element of the specified member name if it exists and deserializes it as the + /// specific generic type parameter. If the element does not exist, returns default. + /// + /// The name of the child element. + /// The return type of the element. + /// + /// If found, the value of child element deserialized as the generic type parameter. + /// If not found, returns default. + /// + /// + /// This method makes getting/setting data on as concise as possible for derived classes. + /// This method uses the so the deriving classes don't have to specify + /// the property name (assuming its the name matches the underlying element property). + /// + protected T? GetComplex([CallerMemberName] string? name = null) where T : LogixElement + { + return Element.Element(name)?.Deserialize(); + } + + /// + /// Gets a child with the specified element name, representing the root of a + /// collection of contained elements. + /// + /// The name of the child container collection (e.g. Members). + /// The child element type. + /// A containing all the child elements of the specified type. + /// A child element with name does not exist. + /// + /// This method makes getting/setting data on as concise as possible from derived classes. + /// This method uses the so the deriving classes don't have to specify + /// the property name (assuming its the name matches the underlying element property). + /// + protected LogixContainer GetContainer([CallerMemberName] string? name = null) + where TChild : LogixElement + { + var container = Element.Element(name); + if (container is null) throw Element.L5XError(name); + return new LogixContainer(container); + } + + /// + /// Gets the first parent element of the current underlying element object with the specified name, and returns the + /// a new deserialized instance of the parent type if found. If not found, returns null. + /// + /// The name of the parent element to return. If not provided will use the default configured + /// L5XType for the specified element type. + /// The element type of the parent to return. + /// A representing the specified parent element if found; + /// Otherwise, null. + /// + /// This makes getting parent types more concise for derived element types. If the element is not attached + /// to a L5X document then this will return null. Note that we only get parent but don't set it. A parent is + /// defined by adding a given logix element to the corresponding parent logix container. + /// + protected TElement? GetAncestor(string? ancestor = null) where TElement : LogixElement + { + ancestor ??= typeof(TElement).L5XType(); + return Element.Ancestors(ancestor).FirstOrDefault()?.Deserialize(); + } + + /// + /// Gets the date/time value of the specified attribute name from the current element if it exists. + /// If the attribute does not exist, returns default value. + /// + /// The name of the date time attribute. + /// The format of the date time. + /// If not provided will default to 'ddd MMM d HH:mm:ss yyyy' which is a typical L5X date time format. + /// The parsed value of the attribute. + /// + /// This is a specialized helper since the date time formats are different for different component + /// properties, we need to allow that to be specified. + /// + protected DateTime? GetDateTime(string? format = null, [CallerMemberName] string? name = null) + { + format ??= "ddd MMM d HH:mm:ss yyyy"; + + var attribute = Element.Attribute(name); + + return attribute is not null + ? DateTime.ParseExact(attribute.Value, format, CultureInfo.CurrentCulture) + : null; + } + + /// + /// Sets the value of an attribute, adds an attribute, or removes an attribute. + /// + /// The name of the attribute to set. + /// The value to assign to the attribute. The attribute is removed if the value is null. + /// Otherwise, the value is converted to its string representation and assigned to the Value property of the attribute. + /// The value type. + /// + /// This method makes getting/setting data on as concise as possible from derived classes. + /// This method uses the so the deriving classes don't have to specify + /// the property name (assuming its the name matches the underlying element property). + /// + protected void SetValue(T? value, [CallerMemberName] string? name = null) + { + Element.SetAttributeValue(name, value); + } + + /// + /// Sets the value of an attribute, adds an attribute, or removes an attribute for a element obtained using the + /// provided selector delegate. + /// + /// + /// The value to assign to the attribute. The attribute is removed if the value is null. + /// Otherwise, the value is converted to its string representation and assigned to the Value property of the attribute. + /// + /// A selection delegate that allows custom selection of a element relative to . + /// Use this to reach down the element hierarchy for nested values. + /// The name of the attribute to set. + /// The value type. + /// + /// This method helps make getting/setting data on as concise as possible from derived classes. + /// This method uses the so the deriving classes don't have to specify + /// the property name (assuming its the name matches the underlying element property). + /// + protected void SetValue(T? value, Func selector, [CallerMemberName] string? name = null) + { + var element = selector.Invoke(Element); + if (element is null) throw Element.L5XError(name!); + element.SetAttributeValue(name, value); + } + + /// + /// Sets the value of an attribute, adds an attribute, or removes an attribute for a nested element + /// specified by provided element name. + /// + /// The name of the attribute to set. + /// The name of the child for which to set the attribute. + /// If the element does not exist and attribute is not null, will create the element and add to the parent . + /// The value to assign to the attribute. The attribute is removed if the value is null. + /// Otherwise, the value is converted to its string representation and assigned to the Value property of the attribute. + /// The value type. + /// + /// This method makes getting/setting data on as concise as possible from derived classes. + /// This method uses the so the deriving classes don't have to specify + /// the property name (assuming its the name matches the underlying element property). + /// + protected void SetValue(T? value, XName child, [CallerMemberName] string? name = null) + { + if (value is null) + { + Element.Element(child)?.Attribute(name)?.Remove(); + return; + } + + var element = Element.Element(child); + if (element is null) + { + element = new XElement(child); + Element.Add(element); + } + + element.SetAttributeValue(name, value); + } + + /// + /// Sets or adds the value of an attribute on the underlying element. + /// + /// The name of the attribute to set. + /// The value to assign to the attribute. If null, an exception will be thrown. + /// The value type. + /// value is null. + /// + /// This method it only available to make getting/setting data on as concise + /// as possible from derived classes. This method uses the so the deriving + /// classes don't have to specify the property name (assuming its the name matches the underlying element property). + /// This method will throw an exception if the value is null. + /// + protected void SetRequiredValue(T value, [CallerMemberName] string? name = null) + { + if (value is null) + throw new ArgumentNullException(nameof(value), $"Property {name} can not be null."); + + Element.SetAttributeValue(name, value); + } + + /// + /// Sets the value of a child element, adds a child element, or removes a child element. + /// + /// The name of the element to set. + /// The value to assign to the child element. The child element is removed if the value is null. + /// Otherwise, the value is converted to its string representation, wrapped in a object, + /// and assigned to the Value property of the child element. + /// The value type. + /// + /// This method it only available to make getting/setting data on as concise + /// as possible from derived classes. This method uses the so the deriving + /// classes don't have to specify the property name (assuming its the name matches the underlying element property). + /// + protected void SetProperty(T value, [CallerMemberName] string? name = null) + { + var element = Element.Element(name); + + if (value is null) + { + element?.Remove(); + return; + } + + if (element is null) + { + Element.Add(new XElement(name, new XCData(value.ToString()))); + return; + } + + element.ReplaceWith(new XElement(name, new XCData(value.ToString()))); + } + + /// + /// Sets the complex type object of a child element, adds a child element, or removes a child element. + /// + /// The name of the element to set. + /// The complex type to assign to the child element. + /// The child element is removed if the value is null. + /// Otherwise, the value is serialized and added as a child element to the current type's element. + /// The value type. + /// + /// This method it only available to make getting/setting data on as concise + /// as possible from derived classes. This method uses the so the deriving + /// classes don't have to specify the property name (assuming its the name matches the underlying element property). + /// + protected void SetComplex(T? value, [CallerMemberName] string? name = null) where T : ILogixSerializable + { + var element = Element.Element(name); + + if (value is null) + { + element?.Remove(); + return; + } + + if (element is null) + { + Element.Add(value.Serialize()); + return; + } + + element.ReplaceWith(value.Serialize()); + } + + /// + /// Sets the value of a child container, adds a child container, or removes a child container. + /// + /// The value to set. The child container is removed + /// if the value is null. Otherwise, the value is serialized and added (or replaces the existing) to underlying parent element. + /// The name of the child container collection (e.g. Members). + /// The container type parameter. + /// + /// This method it only available to make getting/setting data on as concise + /// as possible from derived classes. This method uses the so the deriving + /// classes don't have to specify the property name (assuming its the name matches the underlying element property). + /// + protected void SetContainer(LogixContainer? value, [CallerMemberName] string? name = null) + where TChild : LogixElement + { + var element = Element.Element(name); + + if (value is null) + { + element?.Remove(); + return; + } + + if (element is null) + { + Element.Add(value.Serialize()); + return; + } + + element.ReplaceWith(value.Serialize()); + } + + /// + /// Sets the date/time value of a attribute, adds a attribute, or removes a attribute if null. + /// + /// The value to set. + /// The format of the date time. + /// If not provided will default to 'ddd MMM d HH:mm:ss yyyy' which is a typical L5X date time format. + /// The name of the date time attribute. + /// + /// This is a specialized helper since the date time formats are different for different component + /// properties, we should allow that to be specified. + /// + protected void SetDateTime(DateTime? value, string? format = null, [CallerMemberName] string? name = null) + { + if (value is null) + { + Element.Attribute(name)?.Remove(); + return; + } + + format ??= "ddd MMM d HH:mm:ss yyyy"; + var formatted = value.Value.ToString(format); + Element.SetAttributeValue(name, formatted); + } + + /// + /// Adds, removes, or updates the common logix description child element on the current underlying element object. + /// If null, will remove the child element. If not null, will either add as the first child element or replace the + /// existing child element. + /// + /// The description value to set. + /// + /// This is a specialized helper to make setting the description value as concise as possible for derived + /// classes. Many logix elements will have a description element. + /// + protected void SetDescription(string? value = null) + { + if (value is null) + { + Element.Element(L5XName.Description)?.Remove(); + return; + } + + var description = Element.Element(L5XName.Description); + + if (description is null) + { + Element.AddFirst(new XElement(L5XName.Description, new XCData(value))); + return; + } + + description.ReplaceWith(new XElement(L5XName.Description, new XCData(value))); + } +} \ No newline at end of file diff --git a/src/L5Sharp/LogixEnum.cs b/src/L5Sharp/LogixEnum.cs new file mode 100644 index 00000000..7525d41f --- /dev/null +++ b/src/L5Sharp/LogixEnum.cs @@ -0,0 +1,336 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; + +namespace L5Sharp.Core; + +/// +/// A base class for all logix enumeration types. +/// +/// +/// This code was taken from https://github.com/ardalis/SmartEnum and modified to suit needs of this library. +/// Wanted to remove and external dependencies and not rely on other packages. +/// This class provided some base functionality for working with a logix enum type. +/// This includes methods for retrieving all enums of a specified type, and parsing enums from a name or value. +/// +/// The type that is inheriting from this class. +/// The type of the inner value. +public abstract class LogixEnum : + IEquatable>, + IComparable> + where TEnum : LogixEnum + where TValue : IEquatable, IComparable +{ + /// + /// Creates an enumeration with the specified name and value. + /// + /// The common name of the enumeration option. + /// The corresponding value of the enumeration option. + /// name or value are null. + protected LogixEnum(string name, TValue value) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + Value = value ?? throw new ArgumentNullException(nameof(value)); + } + + /// + /// The display name of the enumeration type. + /// + /// A common enumeration field name. + public string Name { get; } + + /// + /// The value of the enumeration type. + /// + /// A value of the specified enumeration type. + public TValue Value { get; } + + /// + /// Returns all enumeration options for the specified enumeration type. + /// + /// An containing all enumeration values of the specified type. + public static IEnumerable All() => FromNamOptions.Value.Values.ToList().AsReadOnly(); + + /// + /// Gets the item associated with the specified name. + /// + /// The name of the item to get. + /// true to ignore case during the comparison; otherwise, false. + /// + /// The item associated with the specified name. + /// If the specified name is not found, throws a . + /// + /// is null. + /// does not exist. + /// + /// + public static TEnum FromName(string name, bool ignoreCase = false) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Argument can not be null or empty.", name); + + return GetFromName(ignoreCase ? FromNameIgnoreCaseOptions.Value : FromNamOptions.Value); + + TEnum GetFromName(IReadOnlyDictionary dictionary) + { + if (!dictionary.TryGetValue(name, out var result)) + throw new KeyNotFoundException($"No {typeof(TEnum).Name} with Name \\\"{name}\\\" found."); + + return result; + } + } + + /// + /// Gets the item associated with the specified name. + /// + /// The name of the item to get. + /// + /// When this method returns, contains the item associated with the specified name, if the key is found; + /// otherwise, null. This parameter is passed uninitialized. + /// + /// true if the contains an item with the specified name; otherwise, false. + /// + /// is null. + /// + /// + public static bool TryFromName(string? name, out TEnum? result) => TryFromName(name, false, out result); + + /// + /// Gets the item associated with the specified name. + /// + /// The name of the item to get. + /// true to ignore case during the comparison; otherwise, false. + /// + /// When this method returns, contains the item associated with the specified name, if the name is found; + /// otherwise, null. This parameter is passed uninitialized. + /// + /// true if the contains an item with the specified name; otherwise, false. + /// + /// is null. + /// + /// + public static bool TryFromName(string? name, bool ignoreCase, out TEnum? result) + { + if (!string.IsNullOrEmpty(name)) + return ignoreCase + ? FromNameIgnoreCaseOptions.Value.TryGetValue(name, out result) + : FromNamOptions.Value.TryGetValue(name, out result); + + result = default; + return false; + } + + /// + /// Gets an item associated with the specified value. + /// + /// The value of the item to get. + /// + /// The first item found that is associated with the specified value. + /// If the specified value is not found, throws a . + /// + /// does not exist. + /// + /// + public static TEnum FromValue(TValue value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + if (!FromValueOptions.Value.TryGetValue(value, out var result)) + throw new KeyNotFoundException($"No {typeof(TEnum).Name} with Value {value} found."); + + return result; + } + + /// + /// Gets an item associated with the specified value. + /// + /// The value of the item to get. + /// The value to return when item not found. + /// + /// The first item found that is associated with the specified value. + /// If the specified value is not found, returns . + /// + /// + /// + public static TEnum FromValue(TValue value, TEnum defaultValue) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + return !FromValueOptions.Value.TryGetValue(value, out var result) ? defaultValue : result; + } + + /// + /// Gets an item associated with the specified value. + /// + /// The value of the item to get. + /// + /// When this method returns, contains the item associated with the specified value, if the value is found; + /// otherwise, null. This parameter is passed uninitialized. + /// + /// true if the contains an item with the specified name; otherwise, false. + /// + /// + /// + public static bool TryFromValue(TValue? value, out TEnum? result) + { + if (value is not null) + return FromValueOptions.Value.TryGetValue(value, out result); + + result = default; + return false; + } + + + /// + public override string ToString() => Value.ToString(); + + /// + public override bool Equals(object? obj) + { + if (obj is not LogixEnum logixEnum) return false; + return GetType() == obj.GetType() && Equals(Value, logixEnum.Value); + } + + /// + public override int GetHashCode() => Value.GetHashCode(); + + /// + /// Returns a value indicating whether this instance is equal to a specified value. + /// + /// An value to compare to this instance. + /// true if has the same value as this instance; otherwise, false. + public virtual bool Equals(LogixEnum? other) + { + if (ReferenceEquals(this, other)) return true; + return other is not null && Value.Equals(other.Value); + } + + /// + /// Performs equality check on the provided types. + /// + /// An instance of the enumeration to check. + /// An instance of the enumeration to check. + /// true if the types are equal; otherwise, false. + public static bool operator ==(LogixEnum left, LogixEnum right) => + Equals(left, right); + + /// + /// Performs equality check on the provided types. + /// + /// An instance of the enumeration to check. + /// An instance of the enumeration to check. + /// true if the types are NOT equal; otherwise, false. + public static bool operator !=(LogixEnum left, LogixEnum right) => + !Equals(left, right); + + /// + /// Compares this instance to a specified and returns an indication of + /// their relative values. + /// + /// An value to compare to this instance. + /// A signed number indicating the relative values of this instance and . + public virtual int CompareTo(LogixEnum other) => + Value.CompareTo(other.Value); + + + /// + /// Compares this instance to a specified and returns an indication if + /// left is less than right. + /// + /// An instance of to compare. + /// An instance of to compare. + /// true if left is less than right. + public static bool operator <(LogixEnum left, LogixEnum right) => + left.CompareTo(right) < 0; + + + /// + /// Compares this instance to a specified and returns an indication if + /// left is less than or equal to right. + /// + /// An instance of to compare. + /// An instance of to compare. + /// true if left is less than or equal to right. + public static bool operator <=(LogixEnum left, LogixEnum right) => + left.CompareTo(right) <= 0; + + /// + /// Compares this instance to a specified and returns an indication if + /// left is greater than right. + /// + /// An instance of to compare. + /// An instance of to compare. + /// true if left is greater than right. + public static bool operator >(LogixEnum left, LogixEnum right) => + left.CompareTo(right) > 0; + + /// + /// Compares this instance to a specified and returns an indication if + /// left is greater than or equal to right. + /// + /// An instance of to compare. + /// An instance of to compare. + /// true if left is greater than or equal to right. + public static bool operator >=(LogixEnum left, LogixEnum right) => + left.CompareTo(right) >= 0; + + + /// + /// Implicitly converts the provided to the underlying value. + /// + /// The enumeration type. + /// A value representing the + public static implicit operator TValue(LogixEnum logixEnum) => logixEnum.Value; + + + /// + /// + /// + /// + /// + public static explicit operator LogixEnum(TValue value) => FromValue(value); + + + private static readonly Lazy EnumOptions = new(GetOptions, LazyThreadSafetyMode.ExecutionAndPublication); + + private static readonly Lazy> FromNamOptions = new(() => + EnumOptions.Value.ToDictionary(item => item.Name)); + + private static readonly Lazy> FromNameIgnoreCaseOptions = new(() => + EnumOptions.Value.ToDictionary(item => item.Name, StringComparer.OrdinalIgnoreCase)); + + private static readonly Lazy> FromValueOptions = new(() => + { + var dictionary = new Dictionary(); + foreach (var item in EnumOptions.Value) + { + dictionary.TryAdd(item.Value, item); + } + + return dictionary; + }); + + + private static TEnum[] GetOptions() + { + var baseType = typeof(TEnum); + return Assembly.GetAssembly(baseType) + .GetTypes() + .Where(t => baseType.IsAssignableFrom(t)) + .SelectMany(GetFieldsOfType) + .OrderBy(t => t.Name) + .ToArray(); + } + + private static List GetFieldsOfType(Type type) + { + return type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy) + .Where(f => type.IsAssignableFrom(f.FieldType)) + .Select(f => (TFieldType)f.GetValue(null)) + .ToList(); + } +} \ No newline at end of file diff --git a/src/L5Sharp/LogixIndex.cs b/src/L5Sharp/LogixIndex.cs new file mode 100644 index 00000000..560e9197 --- /dev/null +++ b/src/L5Sharp/LogixIndex.cs @@ -0,0 +1,385 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// The internal implementation that indexes components and references and stores them in the local dictionaries. +/// This class is then used by to find components, tags, and references quickly. +/// +internal class LogixIndex +{ + /// + /// The root controller element of the project. + /// + private readonly XElement _content; + + /// + /// An index of all logix components in the L5X file for fast lookups. + /// + public readonly Dictionary> Components = new(); + + /// + /// An index of all references to a logix component in the L5X file for fast lookups. + /// + public readonly Dictionary> References = new(); + + public LogixIndex(XElement content) + { + _content = content; + + //Initialize the dictionaries. + IndexComponents(); + IndexReferences(); + + //Detect changes to keep index up to date. + _content.Changing += OnContentChanging; + _content.Changed += OnContentChanged; + } + + /// + /// Handles adding a component to the index. This requires the element to be attached to the L5X tree + /// for it to determine the scope of the element. + /// + private void AddComponent(XElement element) + { + var key = new ComponentKey(element.L5XType(), element.LogixName()); + var container = Scope.Container(element); + + if (Components.TryAdd(key, new Dictionary { { container, element } })) return; + Components[key].TryAdd(container, element); + } + + /// + /// Handles adding the provided reference to the index. If no reference exists for the provided key, a new + /// entry is created, otherwise the reference is added to the existing collection. + /// + private void AddReference(CrossReference reference) + { + if (!References.TryAdd(reference.Key, new List { reference })) + References[reference.Key].Add(reference); + } + + /// + /// Adds the provided collection of references. This is a convenience method to add multiple references. + /// + private void AddReferences(IEnumerable references) + { + foreach (var reference in references) + { + AddReference(reference); + } + } + + /// + /// Handles adding all references to the index that are associated with the provided element. + /// This is a convenience method since we need to parse the element as an to + /// obtain the references to add. + /// + private void AddReferences(XElement element) + { + if (element.Deserialize() is not ILogixReferencable referencable) return; + var references = referencable.References().ToList(); + AddReferences(references); + } + + /// + /// Triggered when any content of the L5X is about to change. We need to know if the object changing is an element + /// we are maintaining state for in the component or reference index. If so, we need to perform the necessary actions. + /// Prior to the object changing and if the change action is a remove or value change, we need to remove the applicable + /// components or references from the index. This is because after the object has changed we no longer have access + /// to the previous state. + /// + private void OnContentChanging(object sender, XObjectChangeEventArgs e) + { + if (e.ObjectChange is XObjectChange.Remove) + { + if (IsComponentElement(sender)) RemoveComponent((XElement)sender); + if (IsCodeElement(sender)) RemoveReferences((XElement)sender); + } + + if (e.ObjectChange is not XObjectChange.Value) return; + + if (IsNameProperty(sender)) RemoveComponent(((XAttribute)sender).Parent!); + if (IsDataTypeProperty(sender)) + { + var attribute = (XAttribute)sender; + var reference = new CrossReference(attribute.Parent!, L5XName.DataType, attribute.Value); + RemoveReference(reference); + } + + if (IsCodeProperty(sender)) RemoveReferences(((XAttribute)sender).Parent!); + } + + /// + /// Triggered when any content of the L5X has changed. We need to know if the object that changed is an element + /// we are maintaining state for in the component or reference index. If so, we need to perform the necessary actions. + /// Once the object has changed, the sender will hold the new state. If the change action is an add or value change, + /// and the element or property value is one that would refer to an indexed object, we will update the state of the index + /// to ensure consistency. + /// + private void OnContentChanged(object sender, XObjectChangeEventArgs e) + { + if (e.ObjectChange is XObjectChange.Add) + { + if (IsComponentElement(sender)) AddComponent((XElement)sender); + if (IsCodeElement(sender)) AddReferences((XElement)sender); + } + + if (e.ObjectChange is not XObjectChange.Value) return; + + if (IsNameProperty(sender)) AddComponent(((XAttribute)sender).Parent!); + if (IsDataTypeProperty(sender)) + { + var attribute = (XAttribute)sender; + var reference = new CrossReference(attribute.Parent!, L5XName.DataType, attribute.Value); + AddReference(reference); + } + + if (IsCodeProperty(sender)) AddReferences(((XAttribute)sender).Parent!); + } + + /// + /// Finds all logix component elements and indexes them into a local dictionary for fast lookups. + /// + private void IndexComponents() + { + IndexControllerScopedComponents(); + IndexProgramScopedComponents(); + IndexModuleDefinedTagComponents(); + } + + /// + /// Finds all controller scoped top level components to index. This includes all components except for program tags, + /// routines, and module defined IO tags, which are handled separately in the following methods. + /// + private void IndexControllerScopedComponents() + { + //The scope for all controller scoped components will be the name of the controller. + var scope = _content.LogixName(); + + //Only consider component elements with a valid name attribute. Some components don't have and name and we + //can't possibly index them. + var components = _content.Elements() + .SelectMany(c => c.Elements().Where(e => e.Attribute(L5XName.Name) is not null)); + + foreach (var component in components) + { + var key = new ComponentKey(component.L5XType(), component.LogixName()); + if (!Components.TryAdd(key, new Dictionary { { scope, component } })) + Components[key].TryAdd(scope, component); + } + } + + /// + /// Handles iterating each program component element in the L5X and indexes each tag and routine + /// with the corresponding program scope. + /// + private void IndexProgramScopedComponents() + { + var programs = _content.Descendants(L5XName.Programs).Elements(); + + foreach (var program in programs) + { + var scope = program.LogixName(); + + foreach (var component in program.Descendants() + .Where(d => d.Name.LocalName is L5XName.Tag or L5XName.Routine)) + { + var key = new ComponentKey(component.L5XType(), component.LogixName()); + if (!Components.TryAdd(key, new Dictionary { { scope, component } })) + Components[key].TryAdd(scope, component); + } + } + } + + /// + /// Handles iterating each module defined tag component element in the L5X and indexes each tag. + /// + private void IndexModuleDefinedTagComponents() + { + var scope = _content.LogixName(); + + foreach (var component in _content.Descendants(L5XName.Modules).Descendants().Where(e => + e.L5XType() is L5XName.ConfigTag or L5XName.InputTag or L5XName.OutputTag)) + { + var key = new ComponentKey(L5XName.Tag, component.ModuleTagName()); + if (!Components.TryAdd(key, new Dictionary { { scope, component } })) + Components[key].TryAdd(scope, component); + } + } + + /// + /// Finds all logix reference elements and indexes them into a local dictionary for fast lookups. + /// + private void IndexReferences() + { + IndexDataTypeReferences(); + IndexCodeReferences(); + } + + /// + /// Finds all elements with a data type attribute and indexes them into a local reference index for fast lookup. + /// This will include technically any type, predefined, atomic, user defined, or add on instruction. + /// + private void IndexDataTypeReferences() + { + var targets = _content.Descendants().Where(d => d.Attribute(L5XName.DataType) is not null); + foreach (var target in targets) + { + var componentName = target.Attribute(L5XName.DataType)!.Value; + var reference = new CrossReference(target, L5XName.DataType, componentName); + if (!References.TryAdd(reference.Key, new List { reference })) + References[reference.Key].Add(reference); + } + } + + /// + /// Finds all routine content elements, iterates each "code" element, and delegates the retrieval or references to the + /// materialized logix element object. Then adds each set of references to the reference index for fast lookup. + /// + private void IndexCodeReferences() + { + var routines = _content.Descendants(L5XName.Program).SelectMany(p => p.Descendants(L5XName.Routine)); + + foreach (var routine in routines) + { + var type = routine.Attribute(L5XName.Type)?.Value.Parse().ContentName; + + switch (type) + { + case L5XName.RLLContent: + AddReferences(routine.Descendants(L5XName.Rung).SelectMany(x => new Rung(x).References())); + break; + case L5XName.STContent: + AddReferences(routine.Descendants(L5XName.Line).SelectMany(x => new Line(x).References())); + break; + case L5XName.FBDContent: + AddReferences(routine.Descendants(L5XName.Sheet).SelectMany(x => new Sheet(x).References())); + break; + case L5XName.SFCContent: + AddReferences(new Chart(routine).References()); + break; + } + } + } + + /// + /// Determines if the provided object is a component element, for which we need to reindex the component. + /// + private static bool IsComponentElement(object sender) + { + if (sender is not XElement element) return false; + var componentTypes = ComponentType.All().Select(c => c.Value).ToList(); + return componentTypes.Contains(element.Name.LocalName); + } + + /// + /// Determines if the provided object is a attribute or property is a component name, for which we need to + /// reindex the component. + /// + private static bool IsNameProperty(object sender) + { + if (sender is not XAttribute attribute) return false; + if (attribute.Name.LocalName is not L5XName.Name) return false; + if (attribute.Parent is null) return false; + var componentTypes = ComponentType.All().Select(c => c.Value).ToList(); + return componentTypes.Contains(attribute.Parent.Name.LocalName); + } + + /// + /// Determines if the provided object is a attribute or property is a data type reference, for which we need to + /// reindex references. + /// + private static bool IsDataTypeProperty(object sender) + { + if (sender is not XAttribute attribute) return false; + if (attribute.Name.LocalName is not L5XName.DataType) return false; + if (attribute.Parent is null) return false; + var componentTypes = ComponentType.All().Select(c => c.Value).ToList(); + return componentTypes.Contains(attribute.Parent.Name.LocalName); + } + + /// + /// Determines if the provided object is a a logix code element, for which we need to reindex references. + /// + private static bool IsCodeElement(object sender) + { + if (sender is not XElement element) return false; + var contentTypes = RoutineType.All().Select(r => r.ContentName).ToList(); + return element.Ancestors().Any(a => contentTypes.Contains(a.Name.LocalName)); + } + + /// + /// Determines if the provided object is a attribute or property of a logix code element, for which we need to + /// reindex references. + /// + private static bool IsCodeProperty(object sender) + { + if (sender is not XAttribute attribute) return false; + if (attribute.Name.LocalName is not + (L5XName.Operand or L5XName.Argument or L5XName.Routine or L5XName.Type or L5XName.Name)) return false; + return attribute.Parent is not null && IsCodeElement(attribute.Parent); + } + + /// + /// Handles removing a component matching the current attached element from the index. This requires the element + /// to be attached to the L5X tree for it to determine the scope of the element. + /// + private void RemoveComponent(XElement element) + { + var key = new ComponentKey(element.Name.LocalName, element.LogixName()); + var scope = Scope.Container(element); + + if (!Components.TryGetValue(key, out var components)) return; + + if (components.Count == 1) + { + Components.Remove(key); + return; + } + + components.Remove(scope); + } + + /// + /// Handles removing the provided reference from the index. If only a single reference exists for the provided key, + /// the entire reference is removed, otherwise we iterate the references and remove all that match the provided + /// reference's location and type. + /// + private void RemoveReference(CrossReference reference) + { + if (!References.TryGetValue(reference.Key, out var results)) return; + if (results.Count == 1) + { + References.Remove(reference.Key); + return; + } + + results.RemoveAll(r => r.Equals(reference)); + } + + /// + /// Removes the provided collection of references. This is a convenience method to remove multiple references. + /// + private void RemoveReferences(IEnumerable references) + { + foreach (var reference in references) + { + RemoveReference(reference); + } + } + + /// + /// Handles removing all references from the index that are associated with the provided element. + /// This is a convenience method since we need to parse the element as an to + /// obtain the references to remove. + /// + private void RemoveReferences(XElement element) + { + if (element.Deserialize() is not ILogixReferencable referencable) return; + var references = referencable.References().ToList(); + RemoveReferences(references); + } +} \ No newline at end of file diff --git a/src/L5Sharp/LogixMember.cs b/src/L5Sharp/LogixMember.cs new file mode 100644 index 00000000..459fda68 --- /dev/null +++ b/src/L5Sharp/LogixMember.cs @@ -0,0 +1,295 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A component of a that defines the structure or hierarchy of the type. +/// +/// +/// +/// Members are used to define the structure of other objects. +/// Since each member holds a strongly typed reference to it's data type, +/// the structure forms a hierarchical tree of nested members and types. +/// +/// +/// This class can deserialize elements such as DataValueMember, StructureMember, and ArrayMember, as well as the root +/// Data element and even the Tag elements. This class makes use of for deserialization. +/// This class only defines, name and data type, since Dimension, Radix, and ExternalAccess are all either +/// members or the specific , or not inherent in the data structure when serialized or +/// deserialized. +/// +/// +/// LogixMember also contains an event called which triggers when is +/// set. It also subscribes to the data change event of in order to route events up the +/// type/member hierarchy. This allows the root member to notify of a change so subscribers (Tag) can update the L5X. +/// +/// +/// +public class LogixMember : ILogixSerializable +{ + private LogixType _dataType; + + /// + /// Creates a new object with the provided name and logix type. + /// + /// The name of the member. If null will default to an empty string. + /// The representing the member's data. If null + /// will default to . + public LogixMember(string? name, LogixType? type) + { + Name = name ?? string.Empty; + _dataType = type ?? LogixData.Null; + _dataType.DataChanged += OnDataTypeChanged; + } + + /// + /// Creates a new initialized from the provided data. + /// + /// The element to parse as the new member object. + /// element is null. + /// element does not have required attributes or child elements. + public LogixMember(XElement element) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + Name = element.Attribute(L5XName.Name)?.Value ?? (element.Attribute(L5XName.Index)?.Value ?? string.Empty); + _dataType = LogixData.Deserialize(element); + _dataType.DataChanged += OnDataTypeChanged; + } + + /// + /// The name of the . + /// + /// A representing the member name. + /// + /// Member name can represent the member name, array element index name, or tag name of an L5X element. + /// + public string Name { get; } + + /// + /// The logix type of the . + /// + /// A representing the member data type. + /// value is null. + /// + /// The data type creates property the hierarchical structure of complex types. + /// This type can be atomic, structure, string, or array. + /// + public LogixType DataType + { + get => _dataType; + set + { + _dataType.DataChanged -= OnDataTypeChanged; + _dataType = Set(value); + _dataType.DataChanged += OnDataTypeChanged; + RaiseDataChanged(this); + } + } + + /// + /// An event that triggers when the property changes or is set. + /// + /// + /// This event is allowing us to detect when the value of a immediate or nested logix type member changes. + /// This is important for tag so that it can know when to update the underlying XML data structure represented + /// by the in-memory data structure. + /// + public event EventHandler? DataChanged; + + /// + public XElement Serialize() + { + return _dataType switch + { + AtomicType atomicType => SerializeValueMember(Name, atomicType), + ArrayType arrayType => SerializeArrayMember(Name, arrayType), + StringType stringType => SerializeStringMember(Name, stringType), + StructureType structureType => SerializeStructureMember(Name, structureType), + _ => throw new NotSupportedException($"Can not serialize member of type {DataType.Name}.") + }; + } + + /// + /// Handles generating a new data type value using the current and provided logix type values. + /// + /// The logix type to update the underlying member data type with. + /// A new with the update values. + /// The provided logix type is not supported + /// + /// This method is the basis for how underlying values are set in memory. + /// Atomic and String type objects will set actual value (but return new type), whereas Structure and Array + /// types will join members and delegate the set down the type/member hierarchy. + /// + private LogixType Set(LogixType? type) + { + if (type is null) return LogixData.Null; + + return _dataType switch + { + NullType => type, + AtomicType atomicType => SetAtomic(atomicType, type), + ArrayType arrayType => SetMembers(arrayType, type), + StringType stringType => SetMembers(stringType, type), + StructureType structureType => SetMembers(structureType, type), + _ => throw new NotSupportedException($"Member does not support setting DataType to type {type.GetType()}.") + }; + } + + /// + /// Handles creating a new atomic type with the updated value. This requires converting the incoming value to the + /// same type as the current type, as we only want to change the value but not the actual atomic type itself. + /// + private static LogixType SetAtomic(LogixType current, LogixType type) + { + return current switch + { + BOOL value => new BOOL((bool)Convert.ChangeType(type, typeof(bool)), value.Radix), + DINT value => new DINT((int)Convert.ChangeType(type, typeof(int)), value.Radix), + INT value => new INT((short)Convert.ChangeType(type, typeof(short)), value.Radix), + LINT value => new LINT((long)Convert.ChangeType(type, typeof(long)), value.Radix), + LREAL value => new LREAL((double)Convert.ChangeType(type, typeof(double)), value.Radix), + REAL value => new REAL((float)Convert.ChangeType(type, typeof(float)), value.Radix), + SINT value => new SINT((sbyte)Convert.ChangeType(type, typeof(sbyte)), value.Radix), + UDINT value => new UDINT((uint)Convert.ChangeType(type, typeof(uint)), value.Radix), + UINT value => new UINT((ushort)Convert.ChangeType(type, typeof(ushort)), value.Radix), + ULINT value => new ULINT((ulong)Convert.ChangeType(type, typeof(ulong)), value.Radix), + USINT value => new USINT((byte)Convert.ChangeType(type, typeof(byte)), value.Radix), + _ => throw new ArgumentException($"Can not set {current.Name} with type {type.Name}.") + }; + } + + /// + /// Handles cloning and setting members of any given logix type that is considered a structure, array, or string type. + /// This simply joins members on name and delegates the set of each member down the type/member hierarchy, + /// eventually arriving at atomic type values. + /// + private static LogixType SetMembers(LogixType current, LogixType type) + { + if (type is AtomicType) + throw new ArgumentException($"Can not set {current.Name} with atomic type {type.Name}."); + + var clone = current.Clone(); + + var pairs = clone.Members.Join(type.Members, m => m.Name, m => m.Name, + (t, s) => new { Target = t, Source = s }); + + foreach (var pair in pairs) + pair.Target.DataType = pair.Source.DataType; + + return clone; + } + + /// + /// Handles serializing a as a ValueMember element. + /// + private static XElement SerializeValueMember(string name, AtomicType type) + { + var element = new XElement(L5XName.DataValueMember); + element.Add(new XAttribute(L5XName.Name, name)); + element.Add(new XAttribute(L5XName.DataType, type.Name)); + if (type is not BOOL) element.Add(new XAttribute(L5XName.Radix, type.Radix)); + element.Add(new XAttribute(L5XName.Value, type.ToString())); + return element; + } + + /// + /// Handles serializing a as a ArrayMember element. + /// + private static XElement SerializeArrayMember(string name, ArrayType type) + { + var element = new XElement(L5XName.ArrayMember); + element.Add(new XAttribute(L5XName.Name, name)); + element.Add(new XAttribute(L5XName.DataType, type.TypeName)); + element.Add(new XAttribute(L5XName.Dimensions, type.Dimensions)); + if (type.Radix != Radix.Null) element.Add(new XAttribute(L5XName.Radix, type.Radix)); + element.Add(type.Members.Select(m => + { + var index = new XElement(L5XName.Element, new XAttribute(L5XName.Index, m.Name)); + switch (m.DataType) + { + case AtomicType atomicType: + var value = type.Radix != Radix.Null ? atomicType.ToString(type.Radix) : atomicType.ToString(); + index.Add(new XAttribute(L5XName.Value, value)); + break; + case StringType stringType: + index.Add(stringType.SerializeStructure()); + break; + case StructureType structureType: + index.Add(structureType.Serialize()); + break; + } + + return index; + })); + return element; + } + + /// + /// Handles serializing a as a StructureMember element. + /// + private static XElement SerializeStructureMember(string name, LogixType type) + { + var element = new XElement(L5XName.StructureMember); + element.Add(new XAttribute(L5XName.Name, name)); + element.Add(new XAttribute(L5XName.DataType, type.Name)); + element.Add(type.Members.Select(m => m.Serialize())); + return element; + } + + /// + /// Handles serializing a as a StructureMember element. + /// + private static XElement SerializeStringMember(string name, StringType type) + { + var element = new XElement(L5XName.StructureMember); + element.Add(new XAttribute(L5XName.Name, name)); + element.Add(new XAttribute(L5XName.DataType, type.Name)); + + var len = new XElement(L5XName.DataValueMember); + len.Add(new XAttribute(L5XName.Name, nameof(type.LEN))); + len.Add(new XAttribute(L5XName.DataType, type.LEN.Name)); + len.Add(new XAttribute(L5XName.Radix, Radix.Decimal)); + len.Add(new XAttribute(L5XName.Value, type.LEN)); + element.Add(len); + + var data = new XElement(L5XName.DataValueMember); + data.Add(new XAttribute(L5XName.Name, nameof(type.DATA))); + data.Add(new XAttribute(L5XName.DataType, type.Name)); + data.Add(new XAttribute(L5XName.Radix, Radix.Ascii)); + data.Add(new XCData($"'{type}'")); + element.Add(data); + + return element; + } + + /// + /// Handles raising the event for the member. + /// + private void RaiseDataChanged(object sender) => DataChanged?.Invoke(sender, EventArgs.Empty); + + /// + /// Captures nested data type data change event and in turn fires local data changed event. + /// This routes/bubbles up the event sender to the root of the type/member hierarchy. + /// + private void OnDataTypeChanged(object sender, EventArgs e) + { + //If the sender is an atomic type (which is intercepted by atomic types) + //then we realize this is a value change and need to replace the member type with the new value. + //This is the only way we can update the atomic value on the L5X for a bit member change, since + //atomic bit members don't exist in the L5X data structure. + //Note that setting DataType this way will in turn trigger the member's data changed event. + if (sender is AtomicType atomicType) + { + DataType = atomicType; + return; + } + + //Otherwise the member change was already handled and we just forward the sender up the chain. + RaiseDataChanged(sender); + } +} \ No newline at end of file diff --git a/src/L5Sharp/LogixSerializer.cs b/src/L5Sharp/LogixSerializer.cs new file mode 100644 index 00000000..12ee4922 --- /dev/null +++ b/src/L5Sharp/LogixSerializer.cs @@ -0,0 +1,140 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A static deserialization class for objects and their derivatives. +/// This class uses a dictionary to cache deserialization functions for types deriving from LogixElement. +/// We are using compiled expression functions as they are more performant that invoking constructors via reflection. +/// We are also caching them for reuse so we don't have to build them each time we call . +/// Users can register custom implementations of a LogixElement using , which will +/// allow the type to be deserialized as calls to the type are made throughout the library. +/// +public static class LogixSerializer +{ + /// + /// The global cache for all object deserializer delegate functions. + /// + private static readonly Lazy>> Deserializers = new(() => + Introspect(typeof(LogixSerializer).Assembly).ToDictionary(k => k.Key, v => v.Value), + LazyThreadSafetyMode.ExecutionAndPublication); + + /// + /// Deserializes a into the specified object type. + /// + /// The to deserialize. + /// The return type of the deserialized element. + /// A new object of the specified type representing the deserialized element. + /// + /// The return object must specify a public constructor accepting a parameter for + /// deserialization to work. + /// + public static TElement Deserialize(this XElement element) where TElement : LogixElement => + (TElement)Deserialize(element); + + /// + /// Deserializes a into the first matching type found in the + /// element hierarchy. + /// + /// The XML element to deserialize. + /// If the element is or has a parent of a known deserializable type, then a new + /// of the first found type in the XML tree; Otherwise, null. + /// + /// This method will traverse the XML tree until it reaches a XElement with a name matching + /// a known deserializable logic element type. Once it finds that type/element pair, it will deserialize it as that + /// type and return the result. This is important for any feature that would require deserialization of + /// logix elements when the type is not known at compile type or perhaps returns a collection of different element + /// types. It is up to the caller to infer or cast the resulting element type appropriately. + /// + public static LogixElement Deserialize(this XElement element) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + + while (true) + { + if (Deserializers.Value.TryGetValue(element.L5XType(), out var deserializer)) + return deserializer.Invoke(element); + element = element.Parent ?? + throw new InvalidOperationException( + $"Could not find deserializable type for element {element.Name}."); + } + } + + /// + /// Registers the specified logix element type to the global logix serialization cache to be used for + /// deserializing the type. + /// + /// The type to register. + /// The specified type is not deserializable, meaning it does not inherit + /// from , is not public, is abstract, or does not have a constructor taking a single + /// parameter. + /// A type with the same name (L5XType) is already registered. + /// + /// Not that this is common, but this gives external users a means to register custom + /// derived types with the library so that they can be deserialized and used thought other parts of the library. + /// + public static void Register() where TElement : LogixElement + { + var type = typeof(TElement); + + if (!IsDeserializableType(type)) + { + var explanation = + $"The type must derive from {typeof(LogixElement)}, be public, non-abstract," + + $" and have a constructor accepting a single {typeof(XElement)}"; + throw new ArgumentException( + $"Type '{typeof(TElement)} is not a valid deserializable logix type. {explanation}"); + } + + var deserializer = type.Deserializer(); + var deserializers = type.L5XTypes() + .Select(t => new KeyValuePair>(t, deserializer)); + + foreach (var pair in deserializers) + { + if (!Deserializers.Value.TryAdd(pair.Key, pair.Value)) + throw new InvalidOperationException($"Type '{pair.Key}' is already registered with another function."); + } + } + + /// + /// Performs reflection scanning of provided to get all public non abstract types + /// inheriting from that have the supported deserialization constructor, + /// and returns the L5XType and compiled deserialization delegate pair. This is used to initialize the + /// set of concrete deserializer functions for all known logix element objects. + /// + private static IEnumerable>> Introspect(Assembly assembly) + { + var deserializers = new List>>(); + + var types = assembly.GetTypes().Where(IsDeserializableType); + + foreach (var type in types) + { + var deserializer = type.Deserializer(); + deserializers.AddRange(type.L5XTypes() + .Select(t => new KeyValuePair>(t, deserializer))); + } + + return deserializers; + } + + /// + /// Checks whether the type is deserializable by this library. This means that it inherits , + /// is a public non-abstract type, and has a constructor accepting a single parameter. + /// + /// The type to check. + /// true if the type is deserializable; otherwise, false. + private static bool IsDeserializableType(Type type) + { + return typeof(LogixElement).IsAssignableFrom(type) && + type is { IsAbstract: false, IsPublic: true } && + type.GetConstructor(BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(XElement) }, + null) is not null; + } +} \ No newline at end of file diff --git a/src/L5Sharp/LogixType.cs b/src/L5Sharp/LogixType.cs new file mode 100644 index 00000000..078415d5 --- /dev/null +++ b/src/L5Sharp/LogixType.cs @@ -0,0 +1,363 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// The base class for all logix type classes, which represent the value or data structure of a logix tag component. +/// +/// +/// +/// This class exposes the common data type properties, such as , , +/// , and . This class also provides common implicit conversions between .NET +/// base types and LogixType classes so that the tag values may be set in a concise way. +/// +/// +/// +/// +/// +/// +/// +/// +public abstract class LogixType : ILogixSerializable +{ + /// + /// The name of the logix type. + /// + /// A name identifying the logix type. + public abstract string Name { get; } + + /// + /// The family (string or none) of the type. + /// + /// A option representing the family value. + public abstract DataTypeFamily Family { get; } + + /// + /// The class (atomic, predefined, user-defined) that the type belongs to. + /// + /// A option representing the class type. + public abstract DataTypeClass Class { get; } + + /// + /// The collection of objects that make up the structure of the type. + /// + /// A containing objects. + /// + /// All logix types, with the exception of a BOOL and REAL/LREAL, have what can be considered + /// members. Every derived type must implement this property for which it returns a collection of members, forming + /// the type/member hierarchy of the logix type. + /// + public abstract IEnumerable Members { get; } + + /// + /// An event that triggers when the members collection changes. + /// + /// + /// This event is allowing us to detect when the value of a immediate or nested logix type changes. This is important + /// for tag so that it can know when to update the underlying XML data structure represented by the in-memory data structure. + /// + public event EventHandler? DataChanged; + + /// + /// Casts the to the type of the generic parameter. + /// + /// The logix type to cast to. + /// The object can not be casted to the specified type. + /// The logix type object casted as the specified generic type parameter. + public TLogixType As() where TLogixType : LogixType => (TLogixType)this; + + /// + /// Returns a new deep cloned instance of the current type. + /// + /// A new instance of the specified logix type with the same property values. + /// The object being cloned does not have a constructor accepting a single argument. + /// This method will simply deserialize a new instance using the current underlying element data. + public LogixType Clone() => LogixData.Deserialize(new XElement(Serialize())); + + /// + public override bool Equals(object? obj) + { + if (ReferenceEquals(null, obj)) return false; + if (ReferenceEquals(this, obj)) return true; + if (obj is not LogixType type) return false; + return Name == type.Name; + } + + /// + public override int GetHashCode() => Name.GetHashCode(); + + /// + /// Gets a with the specified name if it exists for the ; + /// Otherwise, returns null. + /// + /// The name of the member to get. + /// A with the specified name if found; Otherwise, null. + /// This performs a case insensitive comparison for the member name. + public LogixMember? Member(string name) => + Members.SingleOrDefault(m => string.Equals(m.Name, name, StringComparison.OrdinalIgnoreCase)); + + /// + /// Raising the event for the type with the provided object sender. + /// + /// The objet initiating the data changed event. This could be this object, or a descendent + /// member or type in the data hierarchy. + protected void RaiseDataChanged(object sender) => DataChanged?.Invoke(sender, EventArgs.Empty); + + /// + public override string ToString() => Name; + + /// + public abstract XElement Serialize(); + + /// + /// Determines whether the values are equal. + /// + /// An logix type to compare. + /// An logix type to compare. + /// true if the objects are equal, otherwise, false. + public static bool operator ==(LogixType left, LogixType right) => Equals(left, right); + + /// + /// Determines whether the values are not equal. + /// + /// An logix type to compare. + /// An logix type to compare. + /// true if the objects are not equal, otherwise, false. + public static bool operator !=(LogixType left, LogixType right) => !Equals(left, right); + + /// + /// Compares two objects and determines if a is greater than b. + /// + /// An logix type to compare. + /// An logix type to compare. + /// true if a is greater than b, otherwise, false. + public static bool operator >(LogixType a, LogixType b) + { + if (a is not IComparable comparable) + throw new ArgumentException($"Type {a.GetType()} does not implement {typeof(IComparable)}."); + + return comparable.CompareTo(b) > 0; + } + + /// + /// Compares two objects and determines if a is less than b. + /// + /// An logix type to compare. + /// An logix type to compare. + /// true if a is less than b, otherwise, false. + public static bool operator <(LogixType a, LogixType b) + { + if (a is not IComparable comparable) + throw new ArgumentException($"Type {a.GetType()} does not implement {typeof(IComparable)}."); + + return comparable.CompareTo(b) < 0; + } + + /// + /// Compares two objects and determines if a is greater or equal to than b. + /// + /// An logix type to compare. + /// An logix type to compare. + /// true if a is greater than or equal to b, otherwise, false. + public static bool operator >=(LogixType a, LogixType b) + { + if (a is not IComparable comparable) + throw new ArgumentException($"Type {a.GetType()} does not implement {typeof(IComparable)}."); + + return comparable.CompareTo(b) >= 0; + } + + /// + /// Compares two objects and determines if a is less than or equal to b. + /// + /// An logix type to compare. + /// An logix type to compare. + /// true if a is less than or equal to b, otherwise, false. + public static bool operator <=(LogixType a, LogixType b) + { + if (a is not IComparable comparable) + throw new ArgumentException($"Type {a.GetType()} does not implement {typeof(IComparable)}."); + + return comparable.CompareTo(b) <= 0; + } + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(bool value) => new BOOL(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(sbyte value) => new SINT(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(short value) => new INT(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(int value) => new DINT(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(long value) => new LINT(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(float value) => new REAL(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(double value) => new LREAL(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(byte value) => new USINT(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(ushort value) => new UINT(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(uint value) => new UDINT(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(ulong value) => new ULINT(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(string value) => new STRING(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(LogixType[] value) => ToArrayType(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(LogixType[,] value) =>ToArrayType(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(LogixType[,,] value) => ToArrayType(value); + + /// + /// Converts the provided to a . + /// + /// The value to convert. + /// A representing the converted value. + public static implicit operator LogixType(Dictionary value) => + new ComplexType(nameof(ComplexType), value.Select(m => new LogixMember(m.Key, m.Value))); + + /// + /// Converts the current array object to a of the concrete logix type by + /// inspecting the first element's type in the array. + /// + /// The array to convert. + /// + /// An representing the containing all the elements of the array object as + /// the the concrete generic type array. + /// + /// array is null. + /// + /// This uses reflection and compiled lambda functions to create the concrete array type + /// from the current logix type array, and is used by logix type for implicitly converting arrays to a logix type. + /// + private static ArrayType ToArrayType(Array array) + { + if (array is null) throw new ArgumentNullException(nameof(array)); + var type = array.Cast().First().GetType(); + var arrayType = typeof(ArrayType<>).MakeGenericType(type); + var parameterType = typeof(Array); + var constructor = arrayType.GetConstructor(new[] { parameterType })!; + var parameter = Expression.Parameter(parameterType, "array"); + var creator = Expression.New(constructor, parameter); + var lambda = Expression.Lambda>(creator, parameter); + return lambda.Compile().Invoke(array); + } +} + +/// +/// A static class containing extension methods for the class. +/// +public static class LogixTypeExtensions +{ + /// + /// Converts the current one dimensional array of logix type objects to a + /// of the concrete logix type. + /// + /// The array to convert. + /// The logix type of the elements of the array. + /// An representing the current array containing all the elements + /// of the array. + /// array is null. + /// + /// This extension uses reflection and compiled lambda functions to create the concrete generic array type + /// from the current logix type array, and is used by logix type for implicitly converting arrays to a logix type. + /// + public static ArrayType ToArrayType(this IEnumerable array) + where TLogixType : LogixType + { + if (array is null) throw new ArgumentNullException(nameof(array)); + var arrayType = typeof(ArrayType<>).MakeGenericType(typeof(TLogixType)); + var parameterType = typeof(TLogixType[]); + var constructor = arrayType.GetConstructor(new[] { parameterType })!; + var parameter = Expression.Parameter(parameterType, "array"); + var creator = Expression.New(constructor, parameter); + var lambda = Expression.Lambda>>(creator, parameter); + var func = lambda.Compile(); + return func.Invoke(array.ToArray()); + } +} diff --git a/src/L5Sharp/Types/ArrayType.cs b/src/L5Sharp/Types/ArrayType.cs new file mode 100644 index 00000000..0cb8c357 --- /dev/null +++ b/src/L5Sharp/Types/ArrayType.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A abstract that represents an array of other logix types (either atomic or structure). +/// +/// +/// +/// This class implements some of the base array type functionality for the generic derived class +/// , but is abstract to force instantiation of the generic class. +/// +/// +public abstract class ArrayType : LogixType, IEnumerable +{ + /// + /// The array's collection of objects representing the elements of the array type. + /// + private readonly List _elements; + + /// + /// Creates a new array type from the provided array object. + /// + /// An array of logix types. Array can not be empty, contain null items, + /// or objects of different logix type. + /// + /// array is null. + /// array contains no elements, null or NullType elements, + /// or objects with different logix type names. + /// array rank is greater than 3 dimensions. + protected internal ArrayType(Array array) + { + Dimensions = Dimensions.FromArray(array); + + var collection = array.Cast().ToList(); + + if (collection.Count == 0) + throw new ArgumentException("Array can not be initialized with no items.", nameof(array)); + if (collection.Any(t => t is null or NullType)) + throw new ArgumentException("Array can not be initialized with null items.", nameof(array)); + if (collection.Select(t => t.GetType()).Distinct().Count() != 1) + throw new ArgumentException("Array can not be initialized with different logix type items.", nameof(array)); + + _elements = Dimensions.Indices().Zip(collection, (i, t) => + { + var member = new LogixMember(i, t); + member.DataChanged += OnMemberDataChanged; + return member; + }).ToList(); + } + + /// + /// Creates a new initialized from the provided data. + /// + /// The element to parse. + /// element is null. + /// element does not have required attributes or child elements. + protected internal ArrayType(XElement element) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + + Dimensions = element.Attribute(L5XName.Dimensions)?.Value.Parse() ?? + throw element.L5XError(L5XName.Dimensions); + + _elements = element.Elements().Select(e => + { + var member = new LogixMember(e); + member.DataChanged += OnMemberDataChanged; + return member; + }).ToList(); + } + + /// + /// The name of the logix type the array contains. + /// + /// A containing the text name of the logix type for which the array contains. + /// This is used to delineate from the property of the array type + /// which is the type name and the dimensions index concatenated. The type name is used for serialization. + public string TypeName => _elements.First().DataType.Name; + + /// + /// + /// The name of an array type will be the name of it's contained element types with the dimensions index + /// text appended. This helps differentiate types when querying so we don't return both the base type and arrays of + /// the specified base type. This is also similar to how the name appears from the logix designer. + /// + public override string Name => $"{_elements.First().DataType.Name}{Dimensions.ToIndex()}"; + + /// + public override DataTypeFamily Family => _elements.First().DataType.Family; + + /// + public override DataTypeClass Class => _elements.First().DataType.Class; + + /// + /// The dimensions of the the array, which define the length and rank of the array's elements. + /// + /// A value representing the array dimensions. + /// Array type must have non-empty dimensions to be constructed. + public Dimensions Dimensions { get; } + + /// + /// Gets the radix format of the the array type elements. + /// + /// A format if the array is an atomic type array; + /// otherwise, the radix format. + public Radix Radix => _elements.First().DataType is AtomicType atomicType ? atomicType.Radix : Radix.Null; + + /// + public override IEnumerable Members => _elements.AsEnumerable(); + + /// + /// Gets the instance at the specified index. + /// + /// The index of the array element + /// index is out of range of the array. + public LogixType this[ushort x] + { + get => GetIndex($"[{x}]"); + set => SetIndex($"[{x}]", value); + } + + /// + /// Gets the instance at the specified index. + /// + /// The x index of the array element + /// The y index of the array element + /// index is out of range of the array. + public LogixType this[ushort x, ushort y] + { + get => GetIndex($"[{x},{y}]"); + set => SetIndex($"[{x},{y}]", value); + } + + /// + /// Gets the instance at the specified index. + /// + /// The x index of the array element + /// The y index of the array element + /// The z index of the array element + /// index is out of range of the array. + public LogixType this[ushort x, ushort y, ushort z] + { + get => GetIndex($"[{x},{y},{z}]"); + set => SetIndex($"[{x},{y},{z}]", value); + } + + /// + /// Creates a new of the specified type with the length of the provided dimensions. + /// + /// The dimensions of the array to create. + /// The logix type for which to create. + /// Must have a default parameterless constructor in order to generate instances. + /// A of the specified dimensions containing new objects of the specified type. + public static ArrayType New(Dimensions dimensions) where TLogixType : LogixType, new() + { + if (dimensions is null) + throw new ArgumentNullException(nameof(dimensions)); + + switch (dimensions.Rank) + { + case 1: + { + var array = new TLogixType[dimensions.X]; + for (var i = 0; i < array.GetLength(0); i++) + array[i] = new TLogixType(); + return new ArrayType(array); + } + case 2: + { + var array = new TLogixType[dimensions.X, dimensions.Y]; + for (var i = 0; i < array.GetLength(0); i++) + for (var j = 0; j < array.GetLength(1); j++) + array[i, j] = new TLogixType(); + return new ArrayType(array); + } + case 3: + { + var array = new TLogixType[dimensions.X, dimensions.Y, dimensions.Z]; + for (var i = 0; i < array.GetLength(0); i++) + for (var j = 0; j < array.GetLength(1); j++) + for (var k = 0; k < array.GetLength(2); k++) + array[i, j, k] = new TLogixType(); + return new ArrayType(array); + } + default: + throw new ArgumentException($"The Rank {dimensions.Rank} is not valid."); + } + } + + /// + /// Casts the current to an of the specified logix + /// type generic parameter. + /// + /// The logix type to cast. + /// A of the specified. + public ArrayType Of() where TLogixType : LogixType => this.Cast().ToArray(); + + /// + public override XElement Serialize() + { + var element = new XElement(L5XName.Array); + element.Add(new XAttribute(L5XName.DataType, TypeName)); + element.Add(new XAttribute(L5XName.Dimensions, Dimensions)); + if (Radix != Radix.Null) element.Add(new XAttribute(L5XName.Radix, Radix)); + element.Add(_elements.Select(e => + { + var index = new XElement(L5XName.Element, new XAttribute(L5XName.Index, e.Name)); + + switch (e.DataType) + { + case AtomicType atomicType: + var value = Radix != Radix.Null ? atomicType.ToString(Radix) : atomicType.ToString(); + index.Add(new XAttribute(L5XName.Value, value)); + break; + case StringType stringType: + index.Add(stringType.SerializeStructure()); + break; + case StructureType structureType: + index.Add(structureType.Serialize()); + break; + } + + return index; + })); + + return element; + } + + /// + /// Handles getting the logix type at the specified index of the current array from the underlying member collection. + /// + /// The index at which to get the type. + /// A at the specified index. + /// index is out of range of the array. + protected TLogixType GetIndex(string index) where TLogixType : LogixType + { + var member = _elements.SingleOrDefault(m => m.Name == index); + + if (member is null) + throw new ArgumentOutOfRangeException(nameof(index), + $"The index '{index}' is outside the bound of the array."); + + return member.DataType.As(); + } + + /// + /// Handles setting the logix type at the specified index of the underlying member collection. + /// + /// The index at which to set the type. + /// The logix type value to set. + /// index is out of range of the array. + protected void SetIndex(string index, TLogixType value) where TLogixType : LogixType + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + var member = _elements.SingleOrDefault(m => m.Name == index); + + if (member is null) + throw new ArgumentOutOfRangeException(nameof(index), + $"The index '{index}' is outside the bound of the array."); + + member.DataType = value; + } + + /// + IEnumerator IEnumerable.GetEnumerator() => _elements.GetEnumerator(); + + /// + /// This method needs to be attached to each member of the type to enable the bubbling up of nested member data changed events. + /// + private void OnMemberDataChanged(object sender, EventArgs e) => RaiseDataChanged(sender); +} + +/// +/// A that represents an array of other logix types (either atomic or structure). +/// +/// +/// +/// +/// +public sealed class ArrayType : ArrayType, IEnumerable where TLogixType : LogixType +{ + /// + /// Creates a new initialized with the provided one dimensional array. + /// + /// The one dimensional array to initialize the array type with. + /// array is null. + /// array is empty, contains null or NullType elements, + /// or is an array of more than one logix type. + public ArrayType(Array array) : base(array) + { + } + + /// + /// Creates a new initialized with the provided one dimensional array. + /// + /// The one dimensional array to initialize the array type with. + /// array is null. + /// array is empty, contains null or NullType elements, + /// or is an array of more than one logix type. + public ArrayType(TLogixType[] array) : base(array) + { + } + + /// + /// Creates a new initialized with the provided two dimensional array. + /// + /// The two dimensional array to initialize the array type with. + /// array is null. + /// array is empty, contains null or NullType elements, + /// or is an array of more than one logix type. + public ArrayType(TLogixType[,] array) : base(array) + { + } + + /// + /// Creates a new initialized with the provided three dimensional array. + /// + /// The three dimensional array to initialize the array type with. + /// array is null. + /// array is empty, contains null or NullType elements, + /// or is an array of more than one logix type. + public ArrayType(TLogixType[,,] array) : base(array) + { + } + + /// + public ArrayType(XElement element) : base(element) + { + } + + /// + /// Gets the instance at the specified index. + /// + /// The index of the array element + /// index is out of range of the array. + public new TLogixType this[ushort x] + { + get => GetIndex($"[{x}]"); + set => SetIndex($"[{x}]", value); + } + + /// + /// Gets the instance at the specified index. + /// + /// The x index of the array element + /// The y index of the array element + /// index is out of range of the array. + public new TLogixType this[ushort x, ushort y] + { + get => GetIndex($"[{x},{y}]"); + set => SetIndex($"[{x},{y}]", value); + } + + /// + /// Gets the instance at the specified index. + /// + /// The x index of the array element + /// The y index of the array element + /// The z index of the array element + /// index is out of range of the array. + public new TLogixType this[ushort x, ushort y, ushort z] + { + get => GetIndex($"[{x},{y},{z}]"); + set => SetIndex($"[{x},{y},{z}]", value); + } + + /// + /// Implicitly converts the provided array of logix type objects to an . + /// + /// + /// A new containing the elements of the provided array. + public static implicit operator ArrayType(TLogixType[] array) => new(array); + + /// + /// Implicitly converts the provided array of logix type objects to an . + /// + /// + /// A new containing the elements of the provided array. + public static implicit operator ArrayType(TLogixType[,] array) => new(array); + + /// + /// Implicitly converts the provided array of logix type objects to an . + /// + /// + /// A new containing the elements of the provided array. + public static implicit operator ArrayType(TLogixType[,,] array) => new(array); + + /// + public IEnumerator GetEnumerator() => Members.Select(m => m.DataType.As()).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomic.cs b/src/L5Sharp/Types/Atomic.cs new file mode 100644 index 00000000..d69864ee --- /dev/null +++ b/src/L5Sharp/Types/Atomic.cs @@ -0,0 +1,101 @@ +using System; +using System.Collections.Generic; + +namespace L5Sharp.Core; + +/// +/// A static factory for objects. +/// +public static class Atomic +{ + private static readonly Dictionary> Atomics = new() + { + { nameof(BOOL), BOOL.Parse }, + { "BIT", BOOL.Parse }, + { nameof(SINT), SINT.Parse }, + { nameof(INT), INT.Parse }, + { nameof(DINT), DINT.Parse }, + { nameof(LINT), LINT.Parse }, + { nameof(REAL), REAL.Parse }, + { nameof(LREAL), LREAL.Parse }, + { nameof(USINT), USINT.Parse }, + { nameof(UINT), UINT.Parse }, + { nameof(UDINT), UDINT.Parse }, + { nameof(ULINT), ULINT.Parse } + }; + + /// + /// Parses the provided string value into an atomic type value. + /// + /// The value of the atomic type. + /// A representing the value and format of the provided value. + /// value does not have a valid format to be parsed as an atomic type. + public static AtomicType Parse(string value) + { + return value.IsEquivalent("true") ? new BOOL(true) + : value.IsEquivalent("false") ? new BOOL() + : Radix.Infer(value).Parse(value); + } + + /// + /// Parses the provided string value into an atomic type value. + /// + /// The value of the atomic type. + /// A representing the value and format of the provided value. + /// value does not have a valid format to be parsed as an atomic type. + public static TAtomic Parse(string value) where TAtomic : AtomicType + { + var atomic = value.IsEquivalent("true") ? new BOOL(true) + : value.IsEquivalent("false") ? new BOOL() + : Radix.Infer(value).Parse(value); + return (TAtomic)Convert.ChangeType(atomic, typeof(TAtomic)); + } + + /// + /// Parses the provided string value into the atomic type value specified by name. + /// + /// The name of the atomic type. + /// The value of the atomic type. + /// A representing the value and format of the provided value. + /// name does not represent a valid atomic type. + /// value does not have a valid format to be parsed as the specified atomic type. + public static AtomicType Parse(string name, string value) + { + if (!Atomics.ContainsKey(name)) + throw new ArgumentException($"The type name '{name}' is not a valid {typeof(AtomicType)}"); + + return Atomics[name].Invoke(value); + } + + /// + /// Attempts to parse the provided string input as an atomic type value with the inferred radix format. + /// + /// A string representing an atomic value to parse. + /// If the parsed successfully, then the resulting value; + /// Otherwise, null. + /// true if the string input was parsed as an atomic type; Otherwise, false. + public static bool TryParse(string value, out AtomicType? atomicType) + { + if (value.IsEquivalent("true") || value.IsEquivalent("false")) + { + atomicType = new BOOL(bool.Parse(value)); + return true; + } + + if (Radix.TryInfer(value, out var radix) && radix is not null) + { + atomicType = radix.Parse(value); + return true; + } + + atomicType = default; + return false; + } + + /// + /// Returns indication to whether the provided type name is the name of an atomic type. + /// + /// The type name to test. + /// true if name is the name of any atomic type; otherwise, false. + public static bool IsAtomic(string name) => Atomics.ContainsKey(name); +} \ No newline at end of file diff --git a/src/L5Sharp/Types/AtomicType.cs b/src/L5Sharp/Types/AtomicType.cs new file mode 100644 index 00000000..aa907d19 --- /dev/null +++ b/src/L5Sharp/Types/AtomicType.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A that represents value type object. +/// +/// +/// +/// Logix atomic types are types that have value (e.g., BOOL, SINT, INT, DINT, REAL, etc.). +/// These type are synonymous with value types in .NET and in fact wrap the .NET value types internally while adding +/// the common API. Atomic types also add to indicate the format of the current +/// type value. +/// +/// +/// All derived atomic types will implement value equality and comparison semantics to allow common operations to be performed. +/// They will also implement the interface explicitly so to allow conversion between types. +/// +/// +/// +public abstract class AtomicType : LogixType +{ + /// + public sealed override DataTypeFamily Family => DataTypeFamily.None; + + /// + public sealed override DataTypeClass Class => DataTypeClass.Atomic; + + /// + public override IEnumerable Members + { + get + { + var bits = new BitArray(GetBytes()); + for (var i = 0; i < bits.Count; i++) + { + var member = new LogixMember(i.ToString(), new BOOL(bits[i])); + member.DataChanged += OnMemberDataChanged; + yield return member; + } + } + } + + /// + /// The radix format for the . + /// + /// A representing the format of the atomic type value. + public abstract Radix Radix { get; } + + /// + /// Returns the value as an array of values. + /// + /// An array of representing the value of the type. + public abstract byte[] GetBytes(); + + /// + /// Return the atomic value formatted using the current format. + /// + /// A representing the formatted atomic value. + public override string ToString() => Radix.Format(this); + + /// + /// Returns the atomic value formatted in the specified format. + /// + /// The radix format. + /// A representing the formatted atomic value. + public string ToString(Radix radix) => radix.Format(this); + + /// + /// Serialized the atomic type as the DataValue . + /// + /// A containing the data for the atomic type. + public override XElement Serialize() + { + var element = new XElement(L5XName.DataValue); + element.Add(new XAttribute(L5XName.DataType, Name)); + element.Add(new XAttribute(L5XName.Radix, Radix)); + element.Add(new XAttribute(L5XName.Value, ToString())); + return element; + } + + /// + /// Trigger the event when a atomic member data changed event is fired to forward + /// the call up the type/member hierarchy. + /// + /// The member sending the data changed event. + /// The event args. + protected virtual void OnMemberDataChanged(object sender, EventArgs e) => RaiseDataChanged(sender); +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/BOOL.cs b/src/L5Sharp/Types/Atomics/BOOL.cs new file mode 100644 index 00000000..bb9bceff --- /dev/null +++ b/src/L5Sharp/Types/Atomics/BOOL.cs @@ -0,0 +1,305 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace L5Sharp.Core; + +/// +/// Represents a BOOL Logix atomic data type, or a type analogous to a . This object is meant +/// to wrap the DataValue or DataValueMember data for the L5X tag data structure. +/// +public sealed class BOOL : AtomicType, IComparable, IConvertible +{ + private readonly bool _value; + + /// + /// Creates a new default type. + /// + public BOOL() + { + _value = default; + Radix = Radix.Decimal; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public BOOL(bool value) + { + _value = value; + Radix = Radix.Decimal; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. All non-zero value + /// will be evaluated as true. + public BOOL(int value) + { + _value = value != 0; + Radix = Radix.Decimal; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + public BOOL(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) throw new ArgumentException("", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + public BOOL(bool value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) throw new ArgumentException("", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(BOOL); + + /// + public override Radix Radix { get; } + + /// + public override IEnumerable Members => Enumerable.Empty(); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + BOOL typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((bool)Convert.ChangeType(atomic, typeof(bool))), + ValueType value => _value.CompareTo((bool)Convert.ChangeType(value, typeof(bool))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + BOOL value => value._value == _value, + AtomicType value => _value.Equals((bool)Convert.ChangeType(value, typeof(bool))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(bool))), + _ => false + }; + } + + /// + public override byte[] GetBytes() => BitConverter.GetBytes(_value); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The format can not be inferred from value. + public static BOOL Parse(string value) + { + if (bool.TryParse(value, out var result)) + return new BOOL(result); + + switch (value) + { + case "1": + return new BOOL(true); + case "0": + return new BOOL(); + default: + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (bool)Convert.ChangeType(atomic, typeof(bool)); + return new BOOL(converted, radix); + } + } + + // Contains the implicit .NET conversions for the type. + + #region Conversions + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator BOOL(bool value) => new(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator bool(BOOL atomic) => atomic._value; + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator BOOL(int value) => new(value != 0); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator int(BOOL atomic) => atomic._value ? 1 : 0; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator BOOL(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(BOOL value) => value.ToString(); + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => _value ? (byte)1 : default; + + /// + char IConvertible.ToChar(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Char)} is not supported."); + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported."); + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value ? (double)1 : default; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => _value ? (short)1 : default; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => _value ? 1 : default; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => _value ? (long)1 : default; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => _value ? (sbyte)1 : default; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => _value ? (float)1 : default; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => _value ? (ushort)1 : default; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => _value ? (uint)1 : default; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => _value ? (ulong)1 : default; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value); + if (conversionType == typeof(SINT)) + return new SINT(_value ? (sbyte)1 : default); + if (conversionType == typeof(INT)) + return new INT(_value ? (short)1 : default); + if (conversionType == typeof(DINT)) + return new DINT(_value ? 1 : default); + if (conversionType == typeof(LINT)) + return new LINT(_value ? (long)1 : default); + if (conversionType == typeof(REAL)) + return new REAL(_value ? (float)1 : default); + if (conversionType == typeof(LREAL)) + return new LREAL(_value ? (double)1 : default); + if (conversionType == typeof(USINT)) + return new USINT(_value ? (byte)1 : default); + if (conversionType == typeof(UINT)) + return new UINT(_value ? (ushort)1 : default); + if (conversionType == typeof(UDINT)) + return new UDINT(_value ? (uint)1 : default); + if (conversionType == typeof(ULINT)) + return new ULINT(_value ? (ulong)1 : default); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/DINT.cs b/src/L5Sharp/Types/Atomics/DINT.cs new file mode 100644 index 00000000..31ac9a8e --- /dev/null +++ b/src/L5Sharp/Types/Atomics/DINT.cs @@ -0,0 +1,305 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// Represents a DINT Logix atomic data type, or a type analogous to a . +/// +public sealed class DINT : AtomicType, IComparable, IConvertible +{ + private readonly int _value; + + /// + /// Creates a new default type. + /// + public DINT() + { + _value = default; + Radix = Radix.Decimal; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public DINT(int value) + { + _value = value; + Radix = Radix.Decimal; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + /// radix is null. + /// radix is not supported by the atomic type. + public DINT(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value and radix format. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + /// radix is null. + /// radix is not supported by the atomic type. + public DINT(int value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(DINT); + + /// + public override Radix Radix { get; } + + /// + /// Gets bit member's data type value at the specified bit index. + /// + /// The zero based bit index of the value to get. + /// A representing the value of the specified bit value (0/1). + /// bit is out of range of the atomic type bit length. + public BOOL this[int bit] => + Member(bit.ToString())?.DataType.As() ?? + throw new ArgumentOutOfRangeException($"The bit index {bit} is out of range for a {Name} atomic value."); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + DINT typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((int)Convert.ChangeType(atomic, typeof(int))), + ValueType value => _value.CompareTo((int)Convert.ChangeType(value, typeof(int))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + DINT value => _value == value._value, + AtomicType atomic => _value.Equals((int)Convert.ChangeType(atomic, typeof(int))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(int))), + string value => ToString(Radix.Infer(value)).Equals(value, StringComparison.OrdinalIgnoreCase), + _ => false + }; + } + + /// + public override byte[] GetBytes() => BitConverter.GetBytes(_value); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The converted value returned null. + /// The format can not be inferred from value. + public static DINT Parse(string value) + { + if (int.TryParse(value, out var result)) + return new DINT(result); + + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (int)Convert.ChangeType(atomic, typeof(int)); + return new DINT(converted, radix); + } + + /// + /// Executes the logic to update the atomic value and forward the data changed event up the type/member hierarchy. + /// + /// The member sending the change event. + /// The event args of the event. + /// + /// Atomic members (bits) represent the value of the type. When the member data changed event is triggered, + /// we want to intercept that on the parent atomic type in order to change/update the reflected value. We can do that + /// by getting the changed member (sender) name (bit number) and value (bit value) and setting to get the updated + /// value. However, since atomic types ar immutable, we have to send the changed value up the chain to the parent + /// member (Tag, DataValue, DataValueMember) so that it can replace it's data type with the new atomic value. This is + /// captured in . + /// + protected override void OnMemberDataChanged(object sender, EventArgs e) + { + var member = (LogixMember)sender; + var bit = int.Parse(member.Name); + var value = member.DataType.As(); + var result = value ? _value | 1 << bit : _value & ~(1 << bit); + RaiseDataChanged(new DINT(result, Radix)); + } + + // Contains the implicit .NET conversions for the type. + + #region Conversions + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator DINT(int value) => new(value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator int(DINT atomic) => atomic._value; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator DINT(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(DINT value) => value.ToString(); + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value != 0; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => (byte)_value; + + /// + char IConvertible.ToChar(IFormatProvider provider) => (char)_value; + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported."); + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => (short)_value; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => _value; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => _value; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)_value; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => _value; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)_value; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)_value; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)_value; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value != 0); + if (conversionType == typeof(SINT)) + return new SINT((sbyte)_value); + if (conversionType == typeof(INT)) + return new INT((short)_value); + if (conversionType == typeof(DINT)) + return new DINT(_value); + if (conversionType == typeof(LINT)) + return new LINT(_value); + if (conversionType == typeof(REAL)) + return new REAL(_value); + if (conversionType == typeof(LREAL)) + return new LREAL(_value); + if (conversionType == typeof(USINT)) + return new USINT((byte)_value); + if (conversionType == typeof(UINT)) + return new UINT((ushort)_value); + if (conversionType == typeof(UDINT)) + return new UDINT((uint)_value); + if (conversionType == typeof(ULINT)) + return new ULINT((ulong)_value); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/INT.cs b/src/L5Sharp/Types/Atomics/INT.cs new file mode 100644 index 00000000..7ec313a7 --- /dev/null +++ b/src/L5Sharp/Types/Atomics/INT.cs @@ -0,0 +1,390 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// Represents a INT Logix atomic data type, or a type analogous to a . +/// +public sealed class INT : AtomicType, IComparable, IConvertible +{ + private readonly short _value; + + /// + /// Creates a new default type. + /// + public INT() + { + _value = default; + Radix = Radix.Decimal; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public INT(short value) + { + _value = value; + Radix = Radix.Decimal; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + public INT(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + public INT(short value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(INT); + + /// + public override Radix Radix { get; } + + /// + /// Gets bit member's data type value at the specified bit index. + /// + /// The zero based bit index of the value to get. + /// A representing the value of the specified bit value (0/1). + /// bit is out of range of the atomic type bit length. + public BOOL this[int bit] => + Member(bit.ToString())?.DataType.As() ?? + throw new ArgumentOutOfRangeException($"The bit index {bit} is out of range for a {Name} atomic value."); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + INT typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((short)Convert.ChangeType(atomic, typeof(short))), + ValueType value => _value.CompareTo((short)Convert.ChangeType(value, typeof(short))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + INT value => value._value == _value, + AtomicType atomic => _value.Equals((short)Convert.ChangeType(atomic, typeof(short))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(short))), + _ => false + }; + } + + /// + public override byte[] GetBytes() => BitConverter.GetBytes(_value); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Sets the specified bit of the atomic type to the provided value. + /// + /// The zero based bit index to set. + /// The value to set. + /// A new with the updated value. + /// value is null. + /// bit is out of range of the atomic type bit length. + public INT Set(int bit, BOOL value) + { + if (value is null) + throw new ArgumentNullException(nameof(value)); + + if (bit is < 0 or >= 16) + throw new ArgumentOutOfRangeException($"The bit {bit} is out of range for type {Name}", nameof(bit)); + + var result = (short)(value ? _value | (short)(1 << bit) : _value & (short)~(1 << bit)); + return new INT(result, Radix); + } + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The format can not be inferred from value. + public static INT Parse(string value) + { + if (short.TryParse(value, out var result)) + return new INT(result); + + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (short)Convert.ChangeType(atomic, typeof(short)); + return new INT(converted, radix); + } + + /// + /// Executes the logic to update the atomic value and forward the data changed event up the type/member hierarchy. + /// + /// The member sending the change event. + /// The event args of the event. + /// + /// Atomic members (bits) represent the value of the type. When the member data changed event is triggered, + /// we want to intercept that on the parent atomic type in order to change/update the reflected value. We can do that + /// by getting the changed member (sender) name (bit number) and value (bit value) and setting to get the updated + /// value. However, since atomic types ar immutable, we have to send the changed value up the chain to the parent + /// member (Tag, DataValue, DataValueMember) so that it can replace it's data type with the new atomic value. This is + /// captured in . + /// + protected override void OnMemberDataChanged(object sender, EventArgs e) + { + var member = (LogixMember)sender; + var bit = int.Parse(member.Name); + var value = member.DataType.As(); + var result = (short)(value ? _value | (short)(1 << bit) : _value & (short)~(1 << bit)); + RaiseDataChanged(new INT(result, Radix)); + } + + // Contains the implicit .NET conversions for the type. + + #region Convertsions + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator INT(short value) => new(value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator short(INT atomic) => atomic._value; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator INT(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(INT value) => value.ToString(); + + /* + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static explicit operator BOOL(INT atomic) => new(atomic._value != 0); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static explicit operator SINT(INT atomic) => new((sbyte)atomic._value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static explicit operator USINT(INT atomic) => new((byte)atomic._value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static explicit operator UINT(INT atomic) => new((ushort)atomic._value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator DINT(INT atomic) => new(atomic._value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static explicit operator UDINT(INT atomic) => new((uint)atomic._value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator LINT(INT atomic) => new(atomic._value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static explicit operator ULINT(INT atomic) => new((ulong)atomic._value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator REAL(INT atomic) => new(atomic._value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator LREAL(INT atomic) => new(atomic._value);*/ + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value != 0; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => (byte)_value; + + /// + char IConvertible.ToChar(IFormatProvider provider) => (char)_value; + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported."); + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => _value; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => _value; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => _value; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)_value; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => _value; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)_value; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)_value; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)_value; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value != 0); + if (conversionType == typeof(SINT)) + return new SINT((sbyte)_value); + if (conversionType == typeof(INT)) + return new INT(_value); + if (conversionType == typeof(DINT)) + return new DINT(_value); + if (conversionType == typeof(LINT)) + return new LINT(_value); + if (conversionType == typeof(REAL)) + return new REAL(_value); + if (conversionType == typeof(LREAL)) + return new LREAL(_value); + if (conversionType == typeof(USINT)) + return new USINT((byte)_value); + if (conversionType == typeof(UINT)) + return new UINT((ushort)_value); + if (conversionType == typeof(UDINT)) + return new UDINT((uint)_value); + if (conversionType == typeof(ULINT)) + return new ULINT((ulong)_value); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/LINT.cs b/src/L5Sharp/Types/Atomics/LINT.cs new file mode 100644 index 00000000..52370963 --- /dev/null +++ b/src/L5Sharp/Types/Atomics/LINT.cs @@ -0,0 +1,304 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// Represents a LINT Logix atomic data type, or a type analogous to a . +/// +public sealed class LINT : AtomicType, IComparable, IConvertible +{ + private readonly long _value; + + /// + /// Creates a new default type. + /// + public LINT() + { + _value = default; + Radix = Radix.Decimal; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public LINT(long value) + { + _value = value; + Radix = Radix.Decimal; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + public LINT(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + public LINT(long value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(LINT); + + /// + public override Radix Radix { get; } + + /// + /// Gets bit member's data type value at the specified bit index. + /// + /// The zero based bit index of the value to get. + /// A representing the value of the specified bit value (0/1). + /// bit is out of range of the atomic type bit length. + public BOOL this[int bit] => + Member(bit.ToString())?.DataType.As() ?? + throw new ArgumentOutOfRangeException($"The bit index {bit} is out of range for a {Name} atomic value."); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + LINT typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((long)Convert.ChangeType(atomic, typeof(long))), + ValueType value => _value.CompareTo((long)Convert.ChangeType(value, typeof(long))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + LINT value => _value == value._value, + AtomicType atomic => _value.Equals((long)Convert.ChangeType(atomic, typeof(long))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(long))), + _ => false + }; + } + + /// + public override byte[] GetBytes() => BitConverter.GetBytes(_value); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The format can not be inferred from value. + public static LINT Parse(string value) + { + if (long.TryParse(value, out var result)) + return new LINT(result); + + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (long)Convert.ChangeType(atomic, typeof(long)); + return new LINT(converted, radix); + } + + /// + /// Executes the logic to update the atomic value and forward the data changed event up the type/member hierarchy. + /// + /// The member sending the change event. + /// The event args of the event. + /// + /// Atomic members (bits) represent the value of the type. When the member data changed event is triggered, + /// we want to intercept that on the parent atomic type in order to change/update the reflected value. We can do that + /// by getting the changed member (sender) name (bit number) and value (bit value) and setting to get the updated + /// value. However, since atomic types ar immutable, we have to send the changed value up the chain to the parent + /// member (Tag, DataValue, DataValueMember) so that it can replace it's data type with the new atomic value. This is + /// captured in . + /// + protected override void OnMemberDataChanged(object sender, EventArgs e) + { + var member = (LogixMember)sender; + var bit = int.Parse(member.Name); + var value = member.DataType.As(); + var result = value ? _value | (long)1 << bit : _value & ~(1 << bit); + RaiseDataChanged(new LINT(result, Radix)); + } + + // Contains the implicit .NET conversions for the type. + + #region Conversions + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator LINT(long value) => new(value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator long(LINT atomic) => atomic._value; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator LINT(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(LINT value) => value.ToString(); + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value != 0; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => (byte)_value; + + /// + char IConvertible.ToChar(IFormatProvider provider) => (char)_value; + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) + { + var milliseconds = _value / 1000; + var microseconds = _value % 1000; + var ticks = microseconds * (TimeSpan.TicksPerMillisecond / 1000); + return DateTimeOffset.FromUnixTimeMilliseconds(milliseconds).AddTicks(ticks).DateTime; + } + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => (short)_value; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => (int)_value; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => _value; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)_value; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => _value; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)_value; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)_value; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)_value; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value != 0); + if (conversionType == typeof(SINT)) + return new SINT((sbyte)_value); + if (conversionType == typeof(INT)) + return new INT((short)_value); + if (conversionType == typeof(DINT)) + return new DINT((int)_value); + if (conversionType == typeof(LINT)) + return new LINT(_value); + if (conversionType == typeof(REAL)) + return new REAL(_value); + if (conversionType == typeof(LREAL)) + return new LREAL(_value); + if (conversionType == typeof(USINT)) + return new USINT((byte)_value); + if (conversionType == typeof(UINT)) + return new UINT((ushort)_value); + if (conversionType == typeof(UDINT)) + return new UDINT((uint)_value); + if (conversionType == typeof(ULINT)) + return new ULINT((ulong)_value); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/LREAL.cs b/src/L5Sharp/Types/Atomics/LREAL.cs new file mode 100644 index 00000000..c12a85c1 --- /dev/null +++ b/src/L5Sharp/Types/Atomics/LREAL.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace L5Sharp.Core; + +/// +/// Represents a LREAL Logix atomic data type, or a type analogous to a . +/// +public sealed class LREAL : AtomicType, IComparable, IConvertible +{ + private readonly double _value; + + /// + /// Creates a new default type. + /// + public LREAL() + { + _value = default; + Radix = Radix.Float; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public LREAL(double value) + { + _value = value; + Radix = Radix.Float; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + public LREAL(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + public LREAL(double value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(LREAL); + + /// + public override Radix Radix { get; } + + /// + public override IEnumerable Members => Enumerable.Empty(); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + LREAL typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((double)Convert.ChangeType(atomic, typeof(double))), + ValueType value => _value.CompareTo((double)Convert.ChangeType(value, typeof(double))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + LREAL value => Math.Abs(_value - value._value) < double.Epsilon, + AtomicType atomic => _value.Equals((double)Convert.ChangeType(atomic, typeof(double))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(double))), + _ => false + }; + } + + /// + public override byte[] GetBytes() => BitConverter.GetBytes(_value); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The format can not be inferred from value. + public static LREAL Parse(string value) + { + if (value.Contains("QNAN")) return new LREAL(float.NaN); + + if (double.TryParse(value, out var result)) + return new LREAL(result); + + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (double)Convert.ChangeType(atomic, typeof(double)); + return new LREAL(converted, radix); + } + + // Contains the implicit .NET conversions for the type. + + #region Conversions + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator LREAL(double value) => new(value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator double(LREAL atomic) => atomic._value; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator LREAL(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(LREAL value) => value.ToString(); + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value != 0; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => (byte)_value; + + /// + char IConvertible.ToChar(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Char)} is not supported."); + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported."); + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => (short)_value; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => (int)_value; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => (long)_value; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)_value; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => (float)_value; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)_value; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)_value; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)_value; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value != 0); + if (conversionType == typeof(SINT)) + return new SINT((sbyte)_value); + if (conversionType == typeof(INT)) + return new INT((short)_value); + if (conversionType == typeof(DINT)) + return new DINT((int)_value); + if (conversionType == typeof(LINT)) + return new LINT((long)_value); + if (conversionType == typeof(REAL)) + return new REAL((float)_value); + if (conversionType == typeof(LREAL)) + return new LREAL(_value); + if (conversionType == typeof(USINT)) + return new USINT((byte)_value); + if (conversionType == typeof(UINT)) + return new UINT((ushort)_value); + if (conversionType == typeof(UDINT)) + return new UDINT((uint)_value); + if (conversionType == typeof(ULINT)) + return new ULINT((ulong)_value); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/REAL.cs b/src/L5Sharp/Types/Atomics/REAL.cs new file mode 100644 index 00000000..1cc49f96 --- /dev/null +++ b/src/L5Sharp/Types/Atomics/REAL.cs @@ -0,0 +1,275 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace L5Sharp.Core; + +/// +/// Represents a REAL Logix atomic data type, or a type analogous to a . +/// +public sealed class REAL : AtomicType, IComparable, IConvertible +{ + private readonly float _value; + + /// + /// Creates a new default type. + /// + public REAL() + { + _value = default; + Radix = Radix.Float; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public REAL(float value) + { + _value = value; + Radix = Radix.Float; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + public REAL(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + public REAL(float value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(REAL); + + /// + public override Radix Radix { get; } + + /// + public override IEnumerable Members => Enumerable.Empty(); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + REAL typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((float)Convert.ChangeType(atomic, typeof(float))), + ValueType value => _value.CompareTo((float)Convert.ChangeType(value, typeof(float))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + REAL value => Math.Abs(_value - value._value) < float.Epsilon, + AtomicType atomic => _value.Equals((float)Convert.ChangeType(atomic, typeof(float))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(float))), + _ => false + }; + } + + /// + public override byte[] GetBytes() => BitConverter.GetBytes(_value); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The format can not be inferred from value. + public static REAL Parse(string value) + { + if (value.Contains("QNAN")) return new REAL(float.NaN); + + if (float.TryParse(value, out var result)) + return new REAL(result); + + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (float)Convert.ChangeType(atomic, typeof(float)); + return new REAL(converted, radix); + } + + // Contains the implicit .NET conversions for the type. + + #region Conversions + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator REAL(float value) => new(value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator float(REAL atomic) => atomic._value; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator REAL(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(REAL value) => value.ToString(); + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value != 0; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => (byte)_value; + + /// + char IConvertible.ToChar(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Char)} is not supported."); + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported."); + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => (short)_value; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => (int)_value; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => (long)_value; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)_value; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => _value; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)_value; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)_value; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)_value; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value != 0); + if (conversionType == typeof(SINT)) + return new SINT((sbyte)_value); + if (conversionType == typeof(INT)) + return new INT((short)_value); + if (conversionType == typeof(DINT)) + return new DINT((int)_value); + if (conversionType == typeof(LINT)) + return new LINT((long)_value); + if (conversionType == typeof(REAL)) + return new REAL(_value); + if (conversionType == typeof(LREAL)) + return new LREAL(_value); + if (conversionType == typeof(USINT)) + return new USINT((byte)_value); + if (conversionType == typeof(UINT)) + return new UINT((ushort)_value); + if (conversionType == typeof(UDINT)) + return new UDINT((uint)_value); + if (conversionType == typeof(ULINT)) + return new ULINT((ulong)_value); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/SINT.cs b/src/L5Sharp/Types/Atomics/SINT.cs new file mode 100644 index 00000000..6cf23da6 --- /dev/null +++ b/src/L5Sharp/Types/Atomics/SINT.cs @@ -0,0 +1,299 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// Represents a SINT Logix atomic data type, or a type analogous to . +/// +public sealed class SINT : AtomicType, IComparable, IConvertible +{ + private readonly sbyte _value; + + /// + /// Creates a new default type. + /// + public SINT() + { + _value = default; + Radix = Radix.Decimal; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public SINT(sbyte value) + { + _value = value; + Radix = Radix.Decimal; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + public SINT(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + public SINT(sbyte value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(SINT); + + /// + public override Radix Radix { get; } + + /// + /// Gets bit member's data type value at the specified bit index. + /// + /// The zero based bit index of the value to get. + /// A representing the value of the specified bit value (0/1). + /// bit is out of range of the atomic type bit length. + public BOOL this[int bit] => + Member(bit.ToString())?.DataType.As() ?? + throw new ArgumentOutOfRangeException($"The bit index {bit} is out of range for a {Name} atomic value."); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + SINT typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((sbyte)Convert.ChangeType(atomic, typeof(sbyte))), + ValueType value => _value.CompareTo((sbyte)Convert.ChangeType(value, typeof(sbyte))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + SINT value => value._value == _value, + AtomicType atomic => _value.Equals((sbyte)Convert.ChangeType(atomic, typeof(sbyte))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(sbyte))), + _ => false + }; + } + + /// + public override byte[] GetBytes() => unchecked(new[] { (byte)_value }); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The format can not be inferred from value. + public static SINT Parse(string value) + { + if (sbyte.TryParse(value, out var result)) + return new SINT(result); + + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (sbyte)Convert.ChangeType(atomic, typeof(sbyte)); + return new SINT(converted, radix); + } + + /// + /// Executes the logic to update the atomic value and forward the data changed event up the type/member hierarchy. + /// + /// The member sending the change event. + /// The event args of the event. + /// + /// Atomic members (bits) represent the value of the type. When the member data changed event is triggered, + /// we want to intercept that on the parent atomic type in order to change/update the reflected value. We can do that + /// by getting the changed member (sender) name (bit number) and value (bit value) and setting to get the updated + /// value. However, since atomic types ar immutable, we have to send the changed value up the chain to the parent + /// member (Tag, DataValue, DataValueMember) so that it can replace it's data type with the new atomic value. This is + /// captured in . + /// + protected override void OnMemberDataChanged(object sender, EventArgs e) + { + var member = (LogixMember)sender; + var bit = int.Parse(member.Name); + var value = member.DataType.As(); + var result = (sbyte)(value ? _value | (sbyte)(1 << bit) : _value & (sbyte)~(1 << bit)); + RaiseDataChanged(new SINT(result, Radix)); + } + + // Contains the implicit .NET conversions for the type. + + #region Conversions + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator SINT(sbyte value) => new(value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator sbyte(SINT atomic) => atomic._value; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator SINT(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(SINT value) => value.ToString(); + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value != 0; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => (byte)_value; + + /// + char IConvertible.ToChar(IFormatProvider provider) => (char)_value; + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported."); + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => _value; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => _value; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => _value; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => _value; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => _value; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)_value; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)_value; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => (ulong)_value; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value != 0); + if (conversionType == typeof(SINT)) + return new SINT(_value); + if (conversionType == typeof(INT)) + return new INT(_value); + if (conversionType == typeof(DINT)) + return new DINT(_value); + if (conversionType == typeof(LINT)) + return new LINT(_value); + if (conversionType == typeof(REAL)) + return new REAL(_value); + if (conversionType == typeof(LREAL)) + return new LREAL(_value); + if (conversionType == typeof(USINT)) + return new USINT((byte)_value); + if (conversionType == typeof(UINT)) + return new UINT((ushort)_value); + if (conversionType == typeof(UDINT)) + return new UDINT((uint)_value); + if (conversionType == typeof(ULINT)) + return new ULINT((ulong)_value); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/UDINT.cs b/src/L5Sharp/Types/Atomics/UDINT.cs new file mode 100644 index 00000000..a9a5cf84 --- /dev/null +++ b/src/L5Sharp/Types/Atomics/UDINT.cs @@ -0,0 +1,303 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// Represents a UDINT Logix atomic data type, or a type analogous to a . +/// +public sealed class UDINT : AtomicType, IComparable, IConvertible +{ + private readonly uint _value; + + /// + /// Creates a new default type. + /// + public UDINT() + { + _value = default; + Radix = Radix.Decimal; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public UDINT(uint value) + { + _value = value; + Radix = Radix.Decimal; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + /// radix is null. + /// radix is not supported by the atomic type. + public UDINT(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value and radix format. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + /// radix is null. + /// radix is not supported by the atomic type. + public UDINT(uint value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(UDINT); + + /// + public override Radix Radix { get; } + + /// + /// Gets bit member's data type value at the specified bit index. + /// + /// The zero based bit index of the value to get. + /// A representing the value of the specified bit value (0/1). + /// bit is out of range of the atomic type bit length. + public BOOL this[int bit] => + Member(bit.ToString())?.DataType.As() ?? + throw new ArgumentOutOfRangeException($"The bit index {bit} is out of range for a {Name} atomic value."); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + UDINT typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((uint)Convert.ChangeType(atomic, typeof(uint))), + ValueType value => _value.CompareTo((uint)Convert.ChangeType(value, typeof(uint))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + UDINT value => _value == value._value, + AtomicType atomic => _value.Equals((uint)Convert.ChangeType(atomic, typeof(uint))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(uint))), + _ => false + }; + } + + /// + public override byte[] GetBytes() => BitConverter.GetBytes(_value); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The format can not be inferred from value. + public static UDINT Parse(string value) + { + if (uint.TryParse(value, out var result)) + return new UDINT(result); + + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (uint)Convert.ChangeType(atomic, typeof(uint)); + return new UDINT(converted, radix); + } + + /// + /// Executes the logic to update the atomic value and forward the data changed event up the type/member hierarchy. + /// + /// The member sending the change event. + /// The event args of the event. + /// + /// Atomic members (bits) represent the value of the type. When the member data changed event is triggered, + /// we want to intercept that on the parent atomic type in order to change/update the reflected value. We can do that + /// by getting the changed member (sender) name (bit number) and value (bit value) and setting to get the updated + /// value. However, since atomic types ar immutable, we have to send the changed value up the chain to the parent + /// member (Tag, DataValue, DataValueMember) so that it can replace it's data type with the new atomic value. This is + /// captured in . + /// + protected override void OnMemberDataChanged(object sender, EventArgs e) + { + var member = (LogixMember)sender; + var bit = int.Parse(member.Name); + var value = member.DataType.As(); + var result = value ? _value | (uint)(1 << bit) : _value & (uint)~(1 << bit); + RaiseDataChanged(new UDINT(result, Radix)); + } + + // Contains the implicit .NET conversions for the type. + + #region Conversions + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator UDINT(uint value) => new(value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator uint(UDINT atomic) => atomic._value; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator UDINT(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(UDINT value) => value.ToString(); + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value != 0; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => (byte)_value; + + /// + char IConvertible.ToChar(IFormatProvider provider) => (char)_value; + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported."); + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => (short)_value; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => (int)_value; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => _value; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)_value; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => _value; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)_value; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => _value; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => _value; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value != 0); + if (conversionType == typeof(SINT)) + return new SINT((sbyte)_value); + if (conversionType == typeof(INT)) + return new INT((short)_value); + if (conversionType == typeof(DINT)) + return new DINT((int)_value); + if (conversionType == typeof(LINT)) + return new LINT(_value); + if (conversionType == typeof(REAL)) + return new REAL(_value); + if (conversionType == typeof(LREAL)) + return new LREAL(_value); + if (conversionType == typeof(USINT)) + return new USINT((byte)_value); + if (conversionType == typeof(UINT)) + return new UINT((ushort)_value); + if (conversionType == typeof(UDINT)) + return new UDINT(_value); + if (conversionType == typeof(ULINT)) + return new ULINT(_value); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/UINT.cs b/src/L5Sharp/Types/Atomics/UINT.cs new file mode 100644 index 00000000..f8e2bf11 --- /dev/null +++ b/src/L5Sharp/Types/Atomics/UINT.cs @@ -0,0 +1,299 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// Represents a UINT Logix atomic data type, or a type analogous to a . +/// +public sealed class UINT : AtomicType, IComparable, IConvertible +{ + private readonly ushort _value; + + /// + /// Creates a new default type. + /// + public UINT() + { + _value = default; + Radix = Radix.Decimal; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public UINT(ushort value) + { + _value = value; + Radix = Radix.Decimal; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + public UINT(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + public UINT(ushort value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(UINT); + + /// + public override Radix Radix { get; } + + /// + /// Gets bit member's data type value at the specified bit index. + /// + /// The zero based bit index of the value to get. + /// A representing the value of the specified bit value (0/1). + /// bit is out of range of the atomic type bit length. + public BOOL this[int bit] => + Member(bit.ToString())?.DataType.As() ?? + throw new ArgumentOutOfRangeException($"The bit index {bit} is out of range for a {Name} atomic value."); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + UINT typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((ushort)Convert.ChangeType(atomic, typeof(ushort))), + ValueType value => _value.CompareTo((ushort)Convert.ChangeType(value, typeof(ushort))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + UINT value => _value == value._value, + AtomicType atomic => _value.Equals((ushort)Convert.ChangeType(atomic, typeof(ushort))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(ushort))), + _ => false + }; + } + + /// + public override byte[] GetBytes() => BitConverter.GetBytes(_value); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The format can not be inferred from value. + public static UINT Parse(string value) + { + if (ushort.TryParse(value, out var result)) + return new UINT(result); + + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (ushort)Convert.ChangeType(atomic, typeof(ushort)); + return new UINT(converted, radix); + } + + /// + /// Executes the logic to update the atomic value and forward the data changed event up the type/member hierarchy. + /// + /// The member sending the change event. + /// The event args of the event. + /// + /// Atomic members (bits) represent the value of the type. When the member data changed event is triggered, + /// we want to intercept that on the parent atomic type in order to change/update the reflected value. We can do that + /// by getting the changed member (sender) name (bit number) and value (bit value) and setting to get the updated + /// value. However, since atomic types ar immutable, we have to send the changed value up the chain to the parent + /// member (Tag, DataValue, DataValueMember) so that it can replace it's data type with the new atomic value. This is + /// captured in . + /// + protected override void OnMemberDataChanged(object sender, EventArgs e) + { + var member = (LogixMember)sender; + var bit = int.Parse(member.Name); + var value = member.DataType.As(); + var result = (ushort)(value ? _value | (ushort)(1 << bit) : _value & (ushort)~(1 << bit)); + RaiseDataChanged(new UINT(result, Radix)); + } + + // Contains the implicit .NET conversions for the type. + + #region Conversions + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator UINT(ushort value) => new(value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator ushort(UINT atomic) => atomic._value; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator UINT(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(UINT value) => value.ToString(); + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value != 0; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => (byte)_value; + + /// + char IConvertible.ToChar(IFormatProvider provider) => (char)_value; + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported."); + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => (short)_value; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => _value; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => _value; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)_value; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => _value; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => _value; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => _value; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => _value; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value != 0); + if (conversionType == typeof(SINT)) + return new SINT((sbyte)_value); + if (conversionType == typeof(INT)) + return new INT((short)_value); + if (conversionType == typeof(DINT)) + return new DINT(_value); + if (conversionType == typeof(LINT)) + return new LINT(_value); + if (conversionType == typeof(REAL)) + return new REAL(_value); + if (conversionType == typeof(LREAL)) + return new LREAL(_value); + if (conversionType == typeof(USINT)) + return new USINT((byte)_value); + if (conversionType == typeof(UINT)) + return new UINT(_value); + if (conversionType == typeof(UDINT)) + return new UDINT(_value); + if (conversionType == typeof(ULINT)) + return new ULINT(_value); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/ULINT.cs b/src/L5Sharp/Types/Atomics/ULINT.cs new file mode 100644 index 00000000..b887ca66 --- /dev/null +++ b/src/L5Sharp/Types/Atomics/ULINT.cs @@ -0,0 +1,299 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// Represents a ULINT Logix atomic data type, or a type analogous to a . +/// +public sealed class ULINT : AtomicType, IComparable, IConvertible +{ + private readonly ulong _value; + + /// + /// Creates a new default type. + /// + public ULINT() + { + _value = default; + Radix = Radix.Decimal; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public ULINT(ulong value) + { + _value = value; + Radix = Radix.Decimal; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + public ULINT(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + public ULINT(ulong value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(ULINT); + + /// + public override Radix Radix { get; } + + /// + /// Gets bit member's data type value at the specified bit index. + /// + /// The zero based bit index of the value to get. + /// A representing the value of the specified bit value (0/1). + /// bit is out of range of the atomic type bit length. + public BOOL this[int bit] => + Member(bit.ToString())?.DataType.As() ?? + throw new ArgumentOutOfRangeException($"The bit index {bit} is out of range for a {Name} atomic value."); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + ULINT typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((ulong)Convert.ChangeType(atomic, typeof(ulong))), + ValueType value => _value.CompareTo((ulong)Convert.ChangeType(value, typeof(ulong))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + ULINT value => value._value == _value, + AtomicType atomic => _value.Equals((ulong)Convert.ChangeType(atomic, typeof(ulong))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(ulong))), + _ => false + }; + } + + /// + public override byte[] GetBytes() => BitConverter.GetBytes(_value); + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The format can not be inferred from value. + public static ULINT Parse(string value) + { + if (ulong.TryParse(value, out var result)) + return new ULINT(result); + + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (ulong)Convert.ChangeType(atomic, typeof(ulong)); + return new ULINT(converted, radix); + } + + /// + /// Executes the logic to update the atomic value and forward the data changed event up the type/member hierarchy. + /// + /// The member sending the change event. + /// The event args of the event. + /// + /// Atomic members (bits) represent the value of the type. When the member data changed event is triggered, + /// we want to intercept that on the parent atomic type in order to change/update the reflected value. We can do that + /// by getting the changed member (sender) name (bit number) and value (bit value) and setting to get the updated + /// value. However, since atomic types ar immutable, we have to send the changed value up the chain to the parent + /// member (Tag, DataValue, DataValueMember) so that it can replace it's data type with the new atomic value. This is + /// captured in . + /// + protected override void OnMemberDataChanged(object sender, EventArgs e) + { + var member = (LogixMember)sender; + var bit = int.Parse(member.Name); + var value = member.DataType.As(); + var result = value ? _value | (ulong)1 << bit : _value & (ulong)~(1 << bit); + RaiseDataChanged(new ULINT(result, Radix)); + } + + // Contains the implicit .NET conversions for the type. + + #region Conversions + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator ULINT(ulong value) => new(value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator ulong(ULINT atomic) => atomic._value; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator ULINT(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(ULINT value) => value.ToString(); + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value != 0; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => (byte)_value; + + /// + char IConvertible.ToChar(IFormatProvider provider) => (char)_value; + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported."); + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => (short)_value; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => (int)_value; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => (long)_value; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)_value; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => _value; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => (ushort)_value; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => (uint)_value; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => _value; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value != 0); + if (conversionType == typeof(SINT)) + return new SINT((sbyte)_value); + if (conversionType == typeof(INT)) + return new INT((short)_value); + if (conversionType == typeof(DINT)) + return new DINT((int)_value); + if (conversionType == typeof(LINT)) + return new LINT((long)_value); + if (conversionType == typeof(REAL)) + return new REAL(_value); + if (conversionType == typeof(LREAL)) + return new LREAL(_value); + if (conversionType == typeof(USINT)) + return new USINT((byte)_value); + if (conversionType == typeof(UINT)) + return new UINT((ushort)_value); + if (conversionType == typeof(UDINT)) + return new UDINT((uint)_value); + if (conversionType == typeof(ULINT)) + return new ULINT(_value); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Atomics/USINT.cs b/src/L5Sharp/Types/Atomics/USINT.cs new file mode 100644 index 00000000..05325a7b --- /dev/null +++ b/src/L5Sharp/Types/Atomics/USINT.cs @@ -0,0 +1,299 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// Represents a USINT Logix atomic data type, or a type analogous to a . +/// +public sealed class USINT : AtomicType, IComparable, IConvertible +{ + private readonly byte _value; + + /// + /// Creates a new default type. + /// + public USINT() + { + _value = default; + Radix = Radix.Decimal; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + public USINT(byte value) + { + _value = value; + Radix = Radix.Decimal; + } + + /// + /// Creates a new value with the provided radix format. + /// + /// The number format of the value. + public USINT(Radix radix) + { + _value = default; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + /// Creates a new with the provided value. + /// + /// The value to initialize the type with. + /// The optional radix format of the value. + public USINT(byte value, Radix radix) + { + _value = value; + if (radix is null) throw new ArgumentNullException(nameof(radix)); + if (!radix.SupportsType(this)) + throw new ArgumentException($"Invalid Radix {radix} for atomic type {Name}.", nameof(radix)); + Radix = radix; + } + + /// + public override string Name => nameof(USINT); + + /// + public override Radix Radix { get; } + + /// + /// Gets bit member's data type value at the specified bit index. + /// + /// The zero based bit index of the value to get. + /// A representing the value of the specified bit value (0/1). + /// bit is out of range of the atomic type bit length. + public BOOL this[int bit] => + Member(bit.ToString())?.DataType.As() ?? + throw new ArgumentOutOfRangeException($"The bit index {bit} is out of range for a {Name} atomic value."); + + /// + public int CompareTo(object? obj) + { + return obj switch + { + null => 1, + USINT typed => _value.CompareTo(typed._value), + AtomicType atomic => _value.CompareTo((byte)Convert.ChangeType(atomic, typeof(byte))), + ValueType value => _value.CompareTo((byte)Convert.ChangeType(value, typeof(byte))), + _ => throw new ArgumentException($"Cannot compare logix type {obj.GetType().Name} with {GetType().Name}.") + }; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + USINT value => value._value == _value, + AtomicType atomic => _value.Equals((byte)Convert.ChangeType(atomic, typeof(byte))), + ValueType value => _value.Equals(Convert.ChangeType(value, typeof(byte))), + _ => false + }; + } + + /// + public override byte[] GetBytes() => new[] { _value }; + + /// + public override int GetHashCode() => _value.GetHashCode(); + + /// + /// Parses the provided string value to a new . + /// + /// The string value to parse. + /// A representing the parsed value. + /// The format can not be inferred from value. + public static USINT Parse(string value) + { + if (byte.TryParse(value, out var result)) + return new USINT(result); + + var radix = Radix.Infer(value); + var atomic = radix.Parse(value); + var converted = (byte)Convert.ChangeType(atomic, typeof(byte)); + return new USINT(converted, radix); + } + + /// + /// Executes the logic to update the atomic value and forward the data changed event up the type/member hierarchy. + /// + /// The member sending the change event. + /// The event args of the event. + /// + /// Atomic members (bits) represent the value of the type. When the member data changed event is triggered, + /// we want to intercept that on the parent atomic type in order to change/update the reflected value. We can do that + /// by getting the changed member (sender) name (bit number) and value (bit value) and setting to get the updated + /// value. However, since atomic types ar immutable, we have to send the changed value up the chain to the parent + /// member (Tag, DataValue, DataValueMember) so that it can replace it's data type with the new atomic value. This is + /// captured in . + /// + protected override void OnMemberDataChanged(object sender, EventArgs e) + { + var member = (LogixMember)sender; + var bit = int.Parse(member.Name); + var value = member.DataType.As(); + var result = (byte)(value ? _value | (byte)(1 << bit) : _value & (byte)~(1 << bit)); + RaiseDataChanged(new USINT(result, Radix)); + } + + // Contains the implicit .NET conversions for the type. + + #region Conversions + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A value. + public static implicit operator USINT(byte value) => new(value); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator byte(USINT atomic) => atomic._value; + + /// + /// Implicitly converts a to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator USINT(string value) => Parse(value); + + /// + /// Implicitly converts the provided to a value. + /// + /// The value to convert. + /// A new value. + public static implicit operator string(USINT value) => value.ToString(); + + #endregion + + // Contains the IConvertible implementation for the type. I am explicitly implementing this interface for each + // atomic type to avoid polluting the API, and to have the implementation as performant as possible. + // To perform conversion, use the recommended .NET Convert.ChangeType() method and specify the target type. + + #region Convertible + + /// + TypeCode IConvertible.GetTypeCode() => TypeCode.Object; + + /// + bool IConvertible.ToBoolean(IFormatProvider provider) => _value != 0; + + /// + byte IConvertible.ToByte(IFormatProvider provider) => _value; + + /// + char IConvertible.ToChar(IFormatProvider provider) => (char)_value; + + /// + DateTime IConvertible.ToDateTime(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(DateTime)} is not supported."); + + /// + decimal IConvertible.ToDecimal(IFormatProvider provider) => + throw new InvalidCastException($"Conversion from {Name} to {nameof(Decimal)} is not supported."); + + /// + double IConvertible.ToDouble(IFormatProvider provider) => _value; + + /// + short IConvertible.ToInt16(IFormatProvider provider) => _value; + + /// + int IConvertible.ToInt32(IFormatProvider provider) => _value; + + /// + long IConvertible.ToInt64(IFormatProvider provider) => _value; + + /// + sbyte IConvertible.ToSByte(IFormatProvider provider) => (sbyte)_value; + + /// + float IConvertible.ToSingle(IFormatProvider provider) => _value; + + /// + string IConvertible.ToString(IFormatProvider provider) => ToString(); + + /// + object IConvertible.ToType(Type conversionType, IFormatProvider provider) + { + var convertible = (IConvertible)this; + + return Type.GetTypeCode(conversionType) switch + { + TypeCode.Boolean => convertible.ToBoolean(provider), + TypeCode.Byte => convertible.ToByte(provider), + TypeCode.Char => convertible.ToChar(provider), + TypeCode.DateTime => convertible.ToDateTime(provider), + TypeCode.Decimal => convertible.ToDecimal(provider), + TypeCode.Double => convertible.ToDouble(provider), + TypeCode.Empty => throw new ArgumentNullException(nameof(conversionType)), + TypeCode.Int16 => convertible.ToInt16(provider), + TypeCode.Int32 => convertible.ToInt32(provider), + TypeCode.Int64 => convertible.ToInt64(provider), + TypeCode.Object => ToAtomic(conversionType), + TypeCode.SByte => convertible.ToSByte(provider), + TypeCode.Single => convertible.ToSingle(provider), + TypeCode.String => ToString(), + TypeCode.UInt16 => convertible.ToUInt16(provider), + TypeCode.UInt32 => convertible.ToUInt32(provider), + TypeCode.UInt64 => convertible.ToUInt64(provider), + TypeCode.DBNull => throw new InvalidCastException( + "Conversion for type code 'DbNull' not supported by AtomicType."), + _ => throw new InvalidCastException($"Conversion for {conversionType.Name} not supported by AtomicType.") + }; + } + + /// + ushort IConvertible.ToUInt16(IFormatProvider provider) => _value; + + /// + uint IConvertible.ToUInt32(IFormatProvider provider) => _value; + + /// + ulong IConvertible.ToUInt64(IFormatProvider provider) => _value; + + /// + /// Converts the current atomic type to the specified atomic type. + /// + /// The atomic type to convert to. + /// A representing the converted atomic type value. + /// The specified type is not a valid atomic type. + private object ToAtomic(Type conversionType) + { + if (conversionType == typeof(BOOL)) + return new BOOL(_value != 0); + if (conversionType == typeof(SINT)) + return new SINT((sbyte)_value); + if (conversionType == typeof(INT)) + return new INT(_value); + if (conversionType == typeof(DINT)) + return new DINT(_value); + if (conversionType == typeof(LINT)) + return new LINT(_value); + if (conversionType == typeof(REAL)) + return new REAL(_value); + if (conversionType == typeof(LREAL)) + return new LREAL(_value); + if (conversionType == typeof(USINT)) + return new USINT(_value); + if (conversionType == typeof(UINT)) + return new UINT(_value); + if (conversionType == typeof(UDINT)) + return new UDINT(_value); + if (conversionType == typeof(ULINT)) + return new ULINT(_value); + + throw new InvalidCastException($"Cannot convert from {GetType().Name} to {conversionType.Name}."); + } + + #endregion +} \ No newline at end of file diff --git a/src/L5Sharp/Types/ComplexType.cs b/src/L5Sharp/Types/ComplexType.cs new file mode 100644 index 00000000..3170a6c7 --- /dev/null +++ b/src/L5Sharp/Types/ComplexType.cs @@ -0,0 +1,119 @@ +using System; +using System.Collections.Generic; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A mutable allowing the user to further extend or transform the structure of a complex +/// logix type after instantiation of the object. +/// +/// +/// This type simply exposes methods for modifying the underlying members collection of a structure type. +/// Users can inherit from this type if they wish to mutate user defined types after instantiation. All structure type +/// elements in the L5X will be deserialized as a complex type unless a specific type is found for them (e.g., TIMER). +/// Predefined types will inherit structure type as they should not be mutated. +/// +public class ComplexType : StructureType +{ + /// + public ComplexType(XElement element) : base(element) + { + } + + /// + /// Creates a new empty with the name 'ComplexType' indicating this represents some generic + /// or non-descriptive type with members configured by the user. + /// + public ComplexType() : base(nameof(ComplexType)) + { + } + + /// + /// Creates a new with the provided name. + /// + /// The name of the logix type. + /// name is null or empty. + public ComplexType(string name) : base(name) + { + } + + /// + /// Creates a new with the provided name and member collection. + /// + /// The name of the logix type. + /// + /// name is null or empty. + /// members is null or any member in members is null. + public ComplexType(string name, IEnumerable members) : base(name, members) + { + } + + /// + /// Adds the provided member to the end of the type member collection. + /// + /// The to add. + /// member is null. + public void Add(LogixMember member) => AddMember(member); + + /// + /// Adds the provided member collection to the end of the type member collection. + /// + /// The collection of to add. + /// members or any object in members is null. + public void AddRange(ICollection members) => AddMembers(members); + + /// + /// Removes all members from the current type. + /// + public void Clear() => ClearMembers(); + + /// + /// Inserts the provided member at the specified index of the type member collection. + /// + /// The zero-based index at which item should be inserted. + /// The member to insert. + /// member is null. + /// index is less than 0. -or- index is greater than the length of + /// the member collection. + public void Insert(int index, LogixMember member) => InsertMember(index, member); + + /// + /// Removes a member with the specified name from the type member collection. + /// + /// The name of the member to remove. + public void Remove(string name) => RemoveMember(name); + + /// + /// Removes a member at the specified index from the type member collection. + /// + /// The zero-based index of the member to remove. + public void Remove(int index) => RemoveMember(index); + + /// + /// Replaces a member having the specified name with the provided member instance. + /// + /// The name of the member to replace. + /// The member to replace the current member with. + /// member is null. + /// name does not exists in the structure type. + public void Replace(string name, LogixMember member) => ReplaceMember(name, member); + + /// + /// Replaces a member having the specified name with a new member instance of the same name and + /// provided . + /// + /// The name of the member to replace. + /// The data to update the specified member with. + /// type is null. + /// name does not exists in the structure type. + public void Replace(string name, LogixType type) => ReplaceMember(name, type); + + /// + /// Replaces a member at the specified index with the provided member instance. + /// + /// The zer-based index at which to replace the member. + /// The member to replace the current member with. + /// member is null. + public void Replace(int index, LogixMember member) => ReplaceMember(index, member); +} \ No newline at end of file diff --git a/src/L5Sharp/Types/NullType.cs b/src/L5Sharp/Types/NullType.cs new file mode 100644 index 00000000..b86758aa --- /dev/null +++ b/src/L5Sharp/Types/NullType.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// Represents a null implementation, or a type that is neither atomic, structure, array, +/// or string. +/// +/// This would be the default for any tag that has no data type defined. +// ReSharper disable once InconsistentNaming +public sealed class NullType : LogixType +{ + private static readonly NullType Singleton = new(); + + private NullType() + { + } + + /// + public override string Name => "NULL"; + + /// + public override DataTypeFamily Family => DataTypeFamily.None; + + /// + public override DataTypeClass Class => DataTypeClass.Unknown; + + /// + public override IEnumerable Members => Enumerable.Empty(); + + /// + public override XElement Serialize() => new(L5XName.Data, new XAttribute(L5XName.Format, DataFormat.Decorated)); + + /// + /// Gets the singleton instance of the logix type. + /// + public static readonly NullType Instance = Singleton; +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Predefined/ALARM.cs b/src/L5Sharp/Types/Predefined/ALARM.cs new file mode 100644 index 00000000..1471022b --- /dev/null +++ b/src/L5Sharp/Types/Predefined/ALARM.cs @@ -0,0 +1,266 @@ +using System.Xml.Linq; + +// ReSharper disable InconsistentNaming I want to keep the naming consistent with Logix (for now). + +namespace L5Sharp.Core; + +/// +/// A predefined or built in data type in Logix that is a part of the alarm instruction set. +/// +public sealed class ALARM : StructureType +{ + /// + /// Creates a new data type instance. + /// + public ALARM() : base(nameof(ALARM)) + { + EnableIn = new BOOL(); + In = new REAL(); + HHLimit = new REAL(); + HLimit = new REAL(); + LLimit = new REAL(); + LLLimit = new REAL(); + Deadband = new REAL(); + ROCPosLimit = new REAL(); + ROCNegLimit = new REAL(); + ROCPeriod = new REAL(); + EnableOut = new BOOL(); + HHAlarm = new BOOL(); + HAlarm = new BOOL(); + LAlarm = new BOOL(); + LLAlarm = new BOOL(); + ROCPosAlarm = new BOOL(); + ROCNegAlarm = new BOOL(); + ROC = new REAL(); + Status = new DINT(); + InstructFault = new BOOL(); + DeadbandInv = new BOOL(); + ROCPosLimitInv = new BOOL(); + ROCNegLimitInv = new BOOL(); + ROCPeriodInv = new BOOL(); + } + + /// + public ALARM(XElement element) : base(element) + { + } + + /// + public override DataTypeClass Class => DataTypeClass.Predefined; + + /// + /// Gets the member of the data type. + /// + public BOOL EnableIn + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL In + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL HHLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL HLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL LLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL LLLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL Deadband + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL ROCPosLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL ROCNegLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL ROCPeriod + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL EnableOut + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HHAlarm + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HAlarm + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LAlarm + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LLAlarm + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCPosAlarm + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCNegAlarm + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL ROC + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT Status + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL InstructFault + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL DeadbandInv + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCPosLimitInv + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCNegLimitInv + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCPeriodInv + { + get => GetMember(); + set => SetMember(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Predefined/ALARM_ANALOG.cs b/src/L5Sharp/Types/Predefined/ALARM_ANALOG.cs new file mode 100644 index 00000000..01250116 --- /dev/null +++ b/src/L5Sharp/Types/Predefined/ALARM_ANALOG.cs @@ -0,0 +1,689 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +// ReSharper disable InconsistentNaming Logix naming + +namespace L5Sharp.Core; + +/// +/// A predefined or built in data type in Logix that is a part of the alarm instruction set. +/// +public sealed class ALARM_ANALOG : StructureType +{ + /// + /// Creates a new data type instance. + /// + public ALARM_ANALOG() : base(nameof(ALARM_ANALOG)) + { + EnableIn = new BOOL(); + In = new REAL(); + InFault = new BOOL(); + HHEnabled = new BOOL(); + HEnabled = new BOOL(); + LEnabled = new BOOL(); + LLEnabled = new BOOL(); + AckRequired = new BOOL(); + ProgAckAll = new BOOL(); + OperAckAll = new BOOL(); + HHProgAck = new BOOL(); + HHOperAck = new BOOL(); + HProgAck = new BOOL(); + HOperAck = new BOOL(); + LProgAck = new BOOL(); + LOperAck = new BOOL(); + LLProgAck = new BOOL(); + LLOperAck = new BOOL(); + ROCPosProgAck = new BOOL(); + ROCPosOperAck = new BOOL(); + ROCNegProgAck = new BOOL(); + ROCNegOperAck = new BOOL(); + ProgSuppress = new BOOL(); + OperSuppress = new BOOL(); + ProgUnsuppress = new BOOL(); + OperUnsuppress = new BOOL(); + HHOperShelve = new BOOL(); + HOperShelve = new BOOL(); + LOperShelve = new BOOL(); + LLOperShelve = new BOOL(); + ROCPosOperShelve = new BOOL(); + ROCNegOperShelve = new BOOL(); + ProgUnshelveAll = new BOOL(); + HHOperUnshelve = new BOOL(); + HOperUnshelve = new BOOL(); + LOperUnshelve = new BOOL(); + LLOperUnshelve = new BOOL(); + ROCPosOperUnshelve = new BOOL(); + ROCNegOperUnshelve = new BOOL(); + ProgDisable = new BOOL(); + OperDisable = new BOOL(); + ProgEnable = new BOOL(); + OperEnable = new BOOL(); + AlarmCountReset = new BOOL(); + HHMinDurationEnable = new BOOL(); + HMinDurationEnable = new BOOL(); + LMinDurationEnable = new BOOL(); + LLMinDurationEnable = new BOOL(); + HHLimit = new REAL(); + HHSeverity = new DINT(); + HLimit = new REAL(); + HSeverity = new DINT(); + LLimit = new REAL(); + LSeverity = new DINT(); + LLLimit = new REAL(); + LLSeverity = new DINT(); + MinDurationPRE = new DINT(); + ShelveDuration = new DINT(); + MaxShelveDuration = new DINT(); + Deadband = new REAL(); + ROCPosLimit = new REAL(); + ROCPosSeverity = new DINT(); + ROCNegLimit = new REAL(); + ROCNegSeverity = new DINT(); + ROCPeriod = new REAL(); + } + + /// + public ALARM_ANALOG(XElement element) : base(nameof(ALARM_ANALOG)) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + var members = element.Attributes().Select(a => new LogixMember(a.Name.ToString(), Atomic.Parse(a.Value))); + AddMembers(members.ToList()); + } + + /// + public override DataTypeClass Class => DataTypeClass.Predefined; + + /// + public override XElement Serialize() + { + var element = new XElement(L5XName.AlarmAnalogParameters); + element.Add(Members.Select(m => new XAttribute(m.Name, m.DataType))); + return element; + } + + /// + /// Gets the member of the data type. + /// + public BOOL EnableIn + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL In + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL InFault + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HHEnabled + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HEnabled + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LEnabled + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LLEnabled + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL AckRequired + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgAckAll + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperAckAll + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HHProgAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HHOperAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HProgAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HOperAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LProgAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LOperAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LLProgAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LLOperAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCPosProgAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCPosOperAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCNegProgAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCNegOperAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgSuppress + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperSuppress + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgUnsuppress + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperUnsuppress + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HHOperShelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HOperShelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LOperShelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LLOperShelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCPosOperShelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCNegOperShelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgUnshelveAll + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HHOperUnshelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HOperUnshelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LOperUnshelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LLOperUnshelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCPosOperUnshelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ROCNegOperUnshelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgDisable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperDisable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgEnable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperEnable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL AlarmCountReset + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HHMinDurationEnable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL HMinDurationEnable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LMinDurationEnable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL LLMinDurationEnable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL HHLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT HHSeverity + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL HLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT HSeverity + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL LLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT LSeverity + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL LLLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT LLSeverity + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT MinDurationPRE + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT ShelveDuration + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT MaxShelveDuration + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL Deadband + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL ROCPosLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT ROCPosSeverity + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL ROCNegLimit + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT ROCNegSeverity + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public REAL ROCPeriod + { + get => GetMember(); + set => SetMember(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Predefined/ALARM_DIGITAL.cs b/src/L5Sharp/Types/Predefined/ALARM_DIGITAL.cs new file mode 100644 index 00000000..b64203be --- /dev/null +++ b/src/L5Sharp/Types/Predefined/ALARM_DIGITAL.cs @@ -0,0 +1,319 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +// ReSharper disable InconsistentNaming + +namespace L5Sharp.Core; + +/// +/// A predefined or built in data type in Logix that is a part of the alarm instruction set. +/// +public sealed class ALARM_DIGITAL : StructureType +{ + /// + /// Creates a new data type instance. + /// + public ALARM_DIGITAL() : base(nameof(ALARM_DIGITAL)) + { + Severity = new DINT(); + MinDurationPRE = new DINT(); + ShelveDuration = new DINT(); + MaxShelveDuration = new DINT(); + ProgTime = new LINT(Radix.DateTime); + EnableIn = new BOOL(); + In = new BOOL(); + InFault = new BOOL(); + Condition = new BOOL(); + AckRequired = new BOOL(); + Latched = new BOOL(); + ProgAck = new BOOL(); + OperAck = new BOOL(); + ProgReset = new BOOL(); + OperReset = new BOOL(); + ProgSuppress = new BOOL(); + OperSuppress = new BOOL(); + ProgUnsuppress = new BOOL(); + OperUnsuppress = new BOOL(); + OperShelve = new BOOL(); + ProgUnshelve = new BOOL(); + OperUnshelve = new BOOL(); + ProgDisable = new BOOL(); + OperDisable = new BOOL(); + ProgEnable = new BOOL(); + OperEnable = new BOOL(); + AlarmCountReset = new BOOL(); + UseProgTime = new BOOL(); + } + + /// + public ALARM_DIGITAL(XElement element) : base(nameof(ALARM_DIGITAL)) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + var members = element.Attributes().Select(a => new LogixMember(a.Name.ToString(), Atomic.Parse(a.Value))); + AddMembers(members.ToList()); + } + + /// + public override DataTypeClass Class => DataTypeClass.Predefined; + + /// + public override XElement Serialize() + { + var element = new XElement(L5XName.AlarmDigitalParameters); + element.Add(Members.Select(m => new XAttribute(m.Name, m.DataType))); + return element; + } + + /// + /// Gets the member of the data type. + /// + public BOOL EnableIn + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL In + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL InFault + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL Condition + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL AckRequired + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL Latched + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperAck + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgReset + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperReset + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgSuppress + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperSuppress + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgUnsuppress + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperUnsuppress + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperShelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgUnshelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperUnshelve + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgDisable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperDisable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ProgEnable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OperEnable + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL AlarmCountReset + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL UseProgTime + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public LINT ProgTime + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT Severity + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT MinDurationPRE + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT ShelveDuration + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT MaxShelveDuration + { + get => GetMember(); + set => SetMember(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Predefined/CONTROL.cs b/src/L5Sharp/Types/Predefined/CONTROL.cs new file mode 100644 index 00000000..13659e0c --- /dev/null +++ b/src/L5Sharp/Types/Predefined/CONTROL.cs @@ -0,0 +1,126 @@ +using System.Xml.Linq; + +// ReSharper disable InconsistentNaming RSLogix naming + +namespace L5Sharp.Core; + +/// +/// A predefined or built in data type used with ... instructions. +/// +public sealed class CONTROL : StructureType +{ + /// + /// Creates a new data type instance. + /// + public CONTROL() : base(nameof(CONTROL)) + { + LEN = new DINT(); + POS = new DINT(); + EN = new BOOL(); + EU = new BOOL(); + DN = new BOOL(); + EM = new BOOL(); + ER = new BOOL(); + UL = new BOOL(); + IN = new BOOL(); + FD = new BOOL(); + } + + /// + public CONTROL(XElement element) : base(element) + { + } + + /// + public override DataTypeClass Class => DataTypeClass.Predefined; + + /// + /// Gets the member of the data type. + /// + public DINT LEN + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT POS + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL EN + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL EU + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL DN + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL EM + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL ER + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL UL + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL IN + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL FD + { + get => GetMember(); + set => SetMember(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Predefined/COUNTER.cs b/src/L5Sharp/Types/Predefined/COUNTER.cs new file mode 100644 index 00000000..34609249 --- /dev/null +++ b/src/L5Sharp/Types/Predefined/COUNTER.cs @@ -0,0 +1,96 @@ +using System.Xml.Linq; + +// ReSharper disable InconsistentNaming RSLogix naming + +namespace L5Sharp.Core; + +/// +/// A predefined or built in data type used with counter instructions. +/// +public sealed class COUNTER : StructureType +{ + /// + /// Creates a new data type instance. + /// + public COUNTER() : base(nameof(COUNTER)) + { + PRE = new DINT(); + ACC = new DINT(); + CU = new BOOL(); + CD = new BOOL(); + DN = new BOOL(); + OV = new BOOL(); + UN = new BOOL(); + } + + /// + public COUNTER(XElement element) : base(element) + { + } + + /// + public override DataTypeClass Class => DataTypeClass.Predefined; + + /// + /// Gets the member of the data type. + /// + public DINT PRE + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public DINT ACC + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL CU + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL CD + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL DN + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL OV + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + public BOOL UN + { + get => GetMember(); + set => SetMember(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Predefined/MESSAGE.cs b/src/L5Sharp/Types/Predefined/MESSAGE.cs new file mode 100644 index 00000000..a8e04a00 --- /dev/null +++ b/src/L5Sharp/Types/Predefined/MESSAGE.cs @@ -0,0 +1,184 @@ +using System; +using System.Linq; +using System.Xml.Linq; + +// ReSharper disable InconsistentNaming RSLogix naming + +namespace L5Sharp.Core; + +/// +/// A predefined or built in data type used with message instructions. Note that the members of this type resemble those +/// observed from exported L5X, and not that of the predefined data type. +/// +public sealed class MESSAGE : StructureType +{ + /// + /// Creates a new data type instance. + /// + public MESSAGE() : base(nameof(MESSAGE)) + { + MessageType = new STRING(); + RequestedLength = new INT(); + ConnectedFlag = new INT(); + ConnectionPath = new STRING(); + CommTypeCode = new INT(); + ServiceCode = new INT(Radix.Hex); + ObjectType = new INT(Radix.Hex); + TargetObject = new INT(); + AttributeNumber = new INT(Radix.Hex); + LocalIndex = new INT(); + DestinationTag = new STRING(); + CacheConnections = new BOOL(); + LargePacketUsage = new BOOL(); + } + + /// + public MESSAGE(XElement element) : base(nameof(MESSAGE)) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + + MessageType = element.Attribute(nameof(MessageType))?.Value.Parse() ?? new STRING(); + RequestedLength = element.Attribute(nameof(RequestedLength))?.Value.Parse() ?? new INT(); + ConnectedFlag = element.Attribute(nameof(ConnectedFlag))?.Value.Parse() ?? new INT(); + ConnectionPath = element.Attribute(nameof(ConnectionPath))?.Value.Parse() ?? new STRING(); + CommTypeCode = element.Attribute(nameof(CommTypeCode))?.Value.Parse() ?? new INT(); + ServiceCode = element.Attribute(nameof(ServiceCode))?.Value.Parse() ?? new INT(); + ObjectType = element.Attribute(nameof(ObjectType))?.Value.Parse() ?? new INT(); + TargetObject = element.Attribute(nameof(TargetObject))?.Value.Parse() ?? new INT(); + AttributeNumber = element.Attribute(nameof(AttributeNumber))?.Value.Parse() ?? new INT(); + LocalIndex = element.Attribute(nameof(LocalIndex))?.Value.Parse() ?? new INT(); + DestinationTag = element.Attribute(nameof(DestinationTag))?.Value.Parse() ?? new STRING(); + CacheConnections = element.Attribute(nameof(CacheConnections))?.Value.Parse() ?? new BOOL(); + LargePacketUsage = element.Attribute(nameof(LargePacketUsage))?.Value.Parse() ?? new BOOL(); + } + + /// + public override DataTypeClass Class => DataTypeClass.Predefined; + + /// + public override XElement Serialize() + { + var element = new XElement(L5XName.MessageParameters); + element.Add(Members.Select(m => new XAttribute(m.Name, m.DataType))); + return element; + } + + /// + /// Gets the value of the parameters. + /// /// + /// Gets the value of the parameters. + /// + public STRING MessageType + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public INT RequestedLength + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public INT ConnectedFlag + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public STRING ConnectionPath + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public INT CommTypeCode + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public INT ServiceCode + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public INT ObjectType + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public INT TargetObject + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public INT AttributeNumber + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public INT LocalIndex + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public STRING DestinationTag + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public BOOL CacheConnections + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the value of the parameters. + /// + public BOOL LargePacketUsage + { + get => GetMember(); + set => SetMember(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Predefined/PHASE.cs b/src/L5Sharp/Types/Predefined/PHASE.cs new file mode 100644 index 00000000..0a95b144 --- /dev/null +++ b/src/L5Sharp/Types/Predefined/PHASE.cs @@ -0,0 +1,544 @@ +using System.Xml.Linq; + +// ReSharper disable InconsistentNaming + +namespace L5Sharp.Core; + +/// +/// A predefined data type that is built into Logix and used with PID instructions. +/// +public sealed class PHASE : StructureType +{ + /// + /// Creates a new data type instance. + /// + public PHASE() : base(nameof(PHASE)) + { + State = new DINT(); + Running = new BOOL(); + Holding = new BOOL(); + Restarting = new BOOL(); + Stopping = new BOOL(); + Aborting = new BOOL(); + Resetting = new BOOL(); + Idle = new BOOL(); + Held = new BOOL(); + Complete = new BOOL(); + Stopped = new BOOL(); + Aborted = new BOOL(); + PauseControl = new DINT(); + PauseEnabled = new BOOL(); + Paused = new BOOL(); + AutoPauseEnabled = new BOOL(); + StepIndex = new DINT(); + Failure = new DINT(); + UnitID = new DINT(); + Owner = new DINT(); + PendingRequest = new DINT(); + DownloadInputParameters = new BOOL(); + DownloadInputParametersSubset = new BOOL(); + UploadOutputParameters = new BOOL(); + UploadOutputParametersSubset = new BOOL(); + DownloadOutputParameterLimits = new BOOL(); + AcquireResources = new BOOL(); + ReleaseResources = new BOOL(); + SendMessageToLinkedPhase = new BOOL(); + SendMessageToLinkedPhaseAndWait = new BOOL(); + ReceiveMessageFromLinkedPhase = new BOOL(); + CancelMessageToLinkedPhase = new BOOL(); + SendMessageToOperator = new BOOL(); + ClearMessageToOperator = new BOOL(); + GenerateESignature = new BOOL(); + DownloadBatchData = new BOOL(); + DownloadMaterialTrackDataContainerInUse = new BOOL(); + DownloadContainerBindingPriority = new BOOL(); + DownloadSufficientMaterial = new BOOL(); + DownloadMaterialTrackDatabaseData = new BOOL(); + UploadMaterialTrackDataContainerInUse = new BOOL(); + UploadContainerBindingPriority = new BOOL(); + UploadMaterialTrackDatabaseData = new BOOL(); + AbortingRequest = new BOOL(); + NewInputParameters = new BOOL(); + Producing = new BOOL(); + Standby = new BOOL(); + } + + /// + public PHASE(XElement element) : base(element) + { + } + + /// + public override DataTypeClass Class => DataTypeClass.Predefined; + + /// + /// Gets the State member of the type. + /// + /// A atomic value. + public DINT State + { + get => GetMember(); + set => SetMember(value); + } + + + /// + /// Gets the Running member of the type. + /// + /// A atomic value. + public BOOL Running + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Holding member of the type. + /// + /// A atomic value. + public BOOL Holding + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Restarting member of the type. + /// + /// A atomic value. + public BOOL Restarting + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Stopping member of the type. + /// + /// A atomic value. + public BOOL Stopping + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Aborting member of the type. + /// + /// A atomic value. + public BOOL Aborting + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Resetting member of the type. + /// + /// A atomic value. + public BOOL Resetting + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Idle member of the type. + /// + /// A atomic value. + public BOOL Idle + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Held member of the type. + /// + /// A atomic value. + public BOOL Held + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Complete member of the type. + /// + /// A atomic value. + public BOOL Complete + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Stopped member of the type. + /// + /// A atomic value. + public BOOL Stopped + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Aborted member of the type. + /// + /// A atomic value. + public BOOL Aborted + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PauseControl member of the type. + /// + /// A atomic value. + public DINT PauseControl + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PauseEnabled member of the type. + /// + /// A atomic value. + public BOOL PauseEnabled + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Paused member of the type. + /// + /// A atomic value. + public BOOL Paused + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the AutoPauseEnabled member of the type. + /// + /// A atomic value. + public BOOL AutoPauseEnabled + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the StepIndex member of the type. + /// + /// A atomic value. + public DINT StepIndex + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Failure member of the type. + /// + /// A atomic value. + public DINT Failure + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the UnitID member of the type. + /// + /// A atomic value. + public DINT UnitID + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Owner member of the type. + /// + /// A atomic value. + public DINT Owner + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PendingRequest member of the type. + /// + /// A atomic value. + public DINT PendingRequest + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DownloadInputParameters member of the type. + /// + /// A atomic value. + public BOOL DownloadInputParameters + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DownloadInputParametersSubset member of the type. + /// + /// A atomic value. + public BOOL DownloadInputParametersSubset + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the UploadOutputParameters member of the type. + /// + /// A atomic value. + public BOOL UploadOutputParameters + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the UploadOutputParametersSubset member of the type. + /// + /// A atomic value. + public BOOL UploadOutputParametersSubset + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DownloadOutputParameterLimits member of the type. + /// + /// A atomic value. + public BOOL DownloadOutputParameterLimits + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the AcquireResources member of the type. + /// + /// A atomic value. + public BOOL AcquireResources + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the ReleaseResources member of the type. + /// + /// A atomic value. + public BOOL ReleaseResources + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the SendMessageToLinkedPhase member of the type. + /// + /// A atomic value. + public BOOL SendMessageToLinkedPhase + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the SendMessageToLinkedPhaseAndWait member of the type. + /// + /// A atomic value. + public BOOL SendMessageToLinkedPhaseAndWait + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the ReceiveMessageFromLinkedPhase member of the type. + /// + /// A atomic value. + public BOOL ReceiveMessageFromLinkedPhase + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the CancelMessageToLinkedPhase member of the type. + /// + /// A atomic value. + public BOOL CancelMessageToLinkedPhase + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the SendMessageToOperator member of the type. + /// + /// A atomic value. + public BOOL SendMessageToOperator + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the ClearMessageToOperator member of the type. + /// + /// A atomic value. + public BOOL ClearMessageToOperator + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the GenerateESignature member of the type. + /// + /// A atomic value. + public BOOL GenerateESignature + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DownloadBatchData member of the type. + /// + /// A atomic value. + public BOOL DownloadBatchData + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DownloadMaterialTrackDataContainerInUse member of the type. + /// + /// A atomic value. + public BOOL DownloadMaterialTrackDataContainerInUse + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DownloadContainerBindingPriority member of the type. + /// + /// A atomic value. + public BOOL DownloadContainerBindingPriority + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DownloadSufficientMaterial member of the type. + /// + /// A atomic value. + public BOOL DownloadSufficientMaterial + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DownloadMaterialTrackDatabaseData member of the type. + /// + /// A atomic value. + public BOOL DownloadMaterialTrackDatabaseData + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the UploadMaterialTrackDataContainerInUse member of the type. + /// + /// A atomic value. + public BOOL UploadMaterialTrackDataContainerInUse + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the UploadContainerBindingPriority member of the type. + /// + /// A atomic value. + public BOOL UploadContainerBindingPriority + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the UploadMaterialTrackDatabaseData member of the type. + /// + /// A atomic value. + public BOOL UploadMaterialTrackDatabaseData + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the AbortingRequest member of the type. + /// + /// A atomic value. + public BOOL AbortingRequest + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the NewInputParameters member of the type. + /// + /// A atomic value. + public BOOL NewInputParameters + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Producing member of the type. + /// + /// A atomic value. + public BOOL Producing + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the Standby member of the type. + /// + /// A atomic value. + public BOOL Standby + { + get => GetMember(); + set => SetMember(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Predefined/PID.cs b/src/L5Sharp/Types/Predefined/PID.cs new file mode 100644 index 00000000..86793692 --- /dev/null +++ b/src/L5Sharp/Types/Predefined/PID.cs @@ -0,0 +1,589 @@ +using System.Xml.Linq; + +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo +// ReSharper disable CommentTypo + +namespace L5Sharp.Core; + +/// +/// A predefined data type that is built into Logix and used with PID instructions. +/// +public sealed class PID : StructureType +{ + /// + /// Creates a new data type instance. + /// + public PID() : base(nameof(PID)) + { + CTL = new DINT(); + EN = new BOOL(); + CT = new BOOL(); + CL = new BOOL(); + PVT = new BOOL(); + DOE = new BOOL(); + SWM = new BOOL(); + CA = new BOOL(); + MO = new BOOL(); + PE = new BOOL(); + NDF = new BOOL(); + NOBC = new BOOL(); + NOZC = new BOOL(); + INI = new BOOL(); + SPOR = new BOOL(); + OLL = new BOOL(); + OLH = new BOOL(); + EWD = new BOOL(); + DVNA = new BOOL(); + DVPA = new BOOL(); + PVLA = new BOOL(); + PVHA = new BOOL(); + SP = new REAL(); + KP = new REAL(); + KI = new REAL(); + KD = new REAL(); + BIAS = new REAL(); + MAXS = new REAL(); + MINS = new REAL(); + DB = new REAL(); + SO = new REAL(); + MAXO = new REAL(); + MINO = new REAL(); + UPD = new REAL(); + PV = new REAL(); + ERR = new REAL(); + OUT = new REAL(); + PVH = new REAL(); + PVL = new REAL(); + DVP = new REAL(); + DVN = new REAL(); + PVDB = new REAL(); + DVDB = new REAL(); + MAXI = new REAL(); + MINI = new REAL(); + TIE = new REAL(); + MAXCV = new REAL(); + MINCV = new REAL(); + MINTIE = new REAL(); + MAXTIE = new REAL(); + DATA = ArrayType.New(17); + } + + /// + public PID(XElement element) : base(element) + { + } + + /// + public override DataTypeClass Class => DataTypeClass.Predefined; + + /// + /// Gets the CTL member of the type. + /// + /// A atomic value. + public DINT CTL + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the EN member of the type. + /// + /// A atomic value. + public BOOL EN + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the CT member of the type. + /// + /// A atomic value. + public BOOL CT + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the CL member of the type. + /// + /// A atomic value. + public BOOL CL + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PVT member of the type. + /// + /// A atomic value. + public BOOL PVT + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DOE member of the type. + /// + /// A atomic value. + public BOOL DOE + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the SWM member of the type. + /// + /// A atomic value. + public BOOL SWM + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the CA member of the type. + /// + /// A atomic value. + public BOOL CA + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MO member of the type. + /// + /// A atomic value. + public BOOL MO + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PE member of the type. + /// + /// A atomic value. + public BOOL PE + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the NDF member of the type. + /// + /// A atomic value. + public BOOL NDF + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the NOBC member of the type. + /// + /// A atomic value. + public BOOL NOBC + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the NOZC member of the type. + /// + /// A atomic value. + public BOOL NOZC + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the INI member of the type. + /// + /// A atomic value. + public BOOL INI + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the SPOR member of the type. + /// + /// A atomic value. + public BOOL SPOR + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the OLL member of the type. + /// + /// A atomic value. + public BOOL OLL + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the OLH member of the type. + /// + /// A atomic value. + public BOOL OLH + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the EWD member of the type. + /// + /// A atomic value. + public BOOL EWD + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DVNA member of the type. + /// + /// A atomic value. + public BOOL DVNA + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DVPA member of the type. + /// + /// A atomic value. + public BOOL DVPA + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PVLA member of the type. + /// + /// A atomic value. + public BOOL PVLA + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PVHA member of the type. + /// + /// A atomic value. + public BOOL PVHA + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the SP member of the type. + /// + /// A atomic value. + public REAL SP + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the KP member of the type. + /// + /// A atomic value. + public REAL KP + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the KI member of the type. + /// + /// A atomic value. + public REAL KI + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the KD member of the type. + /// + /// A atomic value. + public REAL KD + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the BIAS member of the type. + /// + /// A atomic value. + public REAL BIAS + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MAXS member of the type. + /// + /// A atomic value. + public REAL MAXS + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MINS member of the type. + /// + /// A atomic value. + public REAL MINS + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DB member of the type. + /// + /// A atomic value. + public REAL DB + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the SO member of the type. + /// + /// A atomic value. + public REAL SO + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MAXO member of the type. + /// + /// A atomic value. + public REAL MAXO + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MINO member of the type. + /// + /// A atomic value. + public REAL MINO + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the UPD member of the type. + /// + /// A atomic value. + public REAL UPD + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PV member of the type. + /// + /// A atomic value. + public REAL PV + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the ERR member of the type. + /// + /// A atomic value. + public REAL ERR + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the OUT member of the type. + /// + /// A atomic value. + public REAL OUT + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PVH member of the type. + /// + /// A atomic value. + public REAL PVH + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PVL member of the type. + /// + /// A atomic value. + public REAL PVL + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DVP member of the type. + /// + /// A atomic value. + public REAL DVP + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DVN member of the type. + /// + /// A atomic value. + public REAL DVN + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the PVDB member of the type. + /// + /// A atomic value. + public REAL PVDB + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DVDB member of the type. + /// + /// A atomic value. + public REAL DVDB + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MAXI member of the type. + /// + /// A atomic value. + public REAL MAXI + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MINI member of the type. + /// + /// A atomic value. + public REAL MINI + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the TIE member of the type. + /// + /// A atomic value. + public REAL TIE + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MAXCV member of the type. + /// + /// A atomic value. + public REAL MAXCV + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MINCV member of the type. + /// + /// A atomic value. + public REAL MINCV + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MINTIE member of the type. + /// + /// A atomic value. + public REAL MINTIE + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the MAXTIE member of the type. + /// + /// A atomic value. + public REAL MAXTIE + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the DATA member of the type. + /// + /// A atomic value. + public ArrayType DATA + { + get => GetMember>(); + set => SetMember(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Predefined/STRING.cs b/src/L5Sharp/Types/Predefined/STRING.cs new file mode 100644 index 00000000..9d2124fe --- /dev/null +++ b/src/L5Sharp/Types/Predefined/STRING.cs @@ -0,0 +1,54 @@ +using System; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// Represents a predefined String Logix data type. +/// +public sealed class STRING : StringType +{ + //This is the built in length of string types in Logix + private const int PredefinedLength = 82; + + /// + /// Creates a new empty type. + /// + public STRING() : base(nameof(STRING), string.Empty) + { + } + + /// + public STRING(XElement element) : base(element, PredefinedLength) + { + } + + /// + /// Creates a new with the provided value. + /// + /// The string value. + /// value is null. + /// + /// value length is greater than the predefined Logix string length of 82 characters. + /// + public STRING(string value) : base(nameof(STRING), value, PredefinedLength) + { + } + + /// + public override DataTypeClass Class => DataTypeClass.Predefined; + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator STRING(string input) => new(input); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator string(STRING input) => input.ToString(); +} \ No newline at end of file diff --git a/src/L5Sharp/Types/Predefined/TIMER.cs b/src/L5Sharp/Types/Predefined/TIMER.cs new file mode 100644 index 00000000..1a42de40 --- /dev/null +++ b/src/L5Sharp/Types/Predefined/TIMER.cs @@ -0,0 +1,93 @@ +using System.Xml.Linq; + +// ReSharper disable InconsistentNaming RSLogix naming + +namespace L5Sharp.Core; + +/// +/// A predefined data type that is built into Logix and used with timer instructions. +/// +public sealed class TIMER : StructureType +{ + /// + /// Creates a new data type instance. + /// + public TIMER() : base(nameof(TIMER)) + { + PRE = new DINT(); + ACC = new DINT(); + EN = new BOOL(); + TT = new BOOL(); + DN = new BOOL(); + } + + /// + public TIMER(XElement element) : base(element) + { + } + + /// + public override DataTypeClass Class => DataTypeClass.Predefined; + + /// + /// Gets the member of the data type. + /// + /// + /// The preset value specifies the value (1 msec units) which the accumulated value must reach + /// before the instruction sets the .DN bit. + /// + public DINT PRE + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + /// + /// The accumulated value specifies the number of milliseconds that have elapsed since the + /// Timer instruction was enabled. + /// + public DINT ACC + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + /// + /// The enable bit indicates that the Timer instruction is enabled. + /// + public BOOL EN + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + /// + /// The timing bit indicates that a timing operation is in process + /// + public BOOL TT + { + get => GetMember(); + set => SetMember(value); + } + + /// + /// Gets the member of the data type. + /// + /// + /// The done bit is set when .ACC ≥ .PRE. + /// + public BOOL DN + { + get => GetMember(); + set => SetMember(value); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Types/StringType.cs b/src/L5Sharp/Types/StringType.cs new file mode 100644 index 00000000..b1c364d5 --- /dev/null +++ b/src/L5Sharp/Types/StringType.cs @@ -0,0 +1,306 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A that represents a string or collection of ASCII characters. +/// +/// +/// +/// A logix string type has predefined members and , which contain the +/// current string length and set of ASCII characters representing the string value, respectively. +/// This class is inherited by , which is Rockwell's built in base string type. +/// +/// +/// StringType has special cases in terms of it's L5X structure. Rockwell treats strings sort of like a value type, +/// giving it a special . However, when serialized as a member of a complex structure, the data +/// looks more like a generic structure type. +/// +public class StringType : StructureType, IEnumerable +{ + private const string LogixAsciiPattern = @"\$[A-Fa-f0-9]{2}|\$[tlpr'$]{1}|[\x00-\x7F]"; + + /// + /// Creates a new initialized from the provided data. + /// + /// The element to parse as the new member object. + /// element is null. + /// element does not have required attributes or child elements. + public StringType(XElement element) : base(DetermineName(element), GenerateMembers(DetermineValue(element))) + { + } + + /// + /// Creates a new initialized with default name and data. + /// + /// This creates a default instance named "StringType" with an empty string. + public StringType() : base(nameof(StringType), GenerateMembers(string.Empty)) + { + } + + /// + /// Creates a new initialized with default name and provided string data. + /// + /// The string value to initialize the type with. + /// This creates a instance named "StringType" with the provided string value. + public StringType(string value) : base(nameof(StringType), GenerateMembers(value)) + { + } + + /// + /// Creates a new instance with the provided data. + /// + /// The name of the string type. + /// The string value of the type. + /// name is null or empty. + public StringType(string name, string value) : base(name, GenerateMembers(value)) + { + } + + /// + /// Creates a new object with the provided name and string value. + /// + /// The name of the string type. + /// The string value of the type. + /// The length to initialize DATA with. + /// This should be greater or equal than the length of value. + /// name is null or empty. + /// value is longer than length. + /// + /// This constructor allows you to instantiate a string type with a specified DATA length so that values + /// of different lengths may be assigned. This is meant to be used by deriving classes such as the predefined + /// Rockwell STRING type and any other user defined string type. + /// + protected StringType(string name, string value, ushort length) : base(name, GenerateMembers(value, length)) + { + } + + /// + /// Creates a new initialized from the provided and data length value. + /// + /// The element to parse as the new member object. + /// The length to initialize DATA with. + /// This should be greater or equal than the length of the value found on the provided element object. + /// element is null. + /// element does not have required attributes or child elements. + /// element contains a value longer than length. + /// + /// This constructor allows you to instantiate a string type with a specified DATA length so that values + /// of different lengths may be assigned. This is meant to be used by deriving classes such as the predefined + /// Rockwell STRING type and any other user defined string type. + /// + protected StringType(XElement element, ushort length) : base(DetermineName(element), + GenerateMembers(DetermineValue(element), length)) + { + } + + /// + public sealed override DataTypeFamily Family => DataTypeFamily.String; + + /// + /// Gets the LEN member of the string type structure. + /// + /// A logix atomic value representing the integer length of the string. + /// + /// Setting this value will do nothing. The LEN member of a string type is a computed value based on the + /// length of (non-zero characters only). Internally, the data changed event is captured to sync + /// this property value with that of the string length. + /// + public DINT LEN => ToString().Length; + + /// + /// Gets the array of bytes that represent the ASCII encoded string value. + /// + /// An array of logix atomic values representing the bytes of the string. + public ArrayType DATA + { + get => GetMember>(); + set => SetMember(value); + } + + /// + public IEnumerator GetEnumerator() => ToString().GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + /// + public override string ToString() => ToString(DATA); + + /// + public override XElement Serialize() + { + var value = ToString(); + + var element = new XElement(L5XName.Data); + element.Add(new XAttribute(L5XName.Format, DataFormat.String)); + element.Add(new XAttribute(L5XName.Length, value.Length)); + element.Add(new XCData($"'{value}'")); + return element; + } + + /// + public override bool Equals(object? obj) + { + return obj switch + { + StringType value => ToString() == value.ToString(), + string value => ToString() == value, + _ => false + }; + } + + /// + public override int GetHashCode() => ToString().GetHashCode(); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator StringType(string input) => new(input); + + /// + /// Converts the provided to a value. + /// + /// The value to convert. + /// A type value. + public static implicit operator string(StringType input) => input.ToString(); + + /// + /// A custom serialization method that returns the string type as a structure element, instead of the string formatted + /// L5X structure. + /// + /// A object representing the serialized string structure data. + public XElement SerializeStructure() + { + var value = ToString(); + + var element = new XElement(L5XName.Structure); + element.Add(new XAttribute(L5XName.DataType, Name)); + + var len = new XElement(L5XName.DataValueMember); + len.Add(new XAttribute(L5XName.Name, nameof(LEN))); + len.Add(new XAttribute(L5XName.DataType, LEN.Name)); + len.Add(new XAttribute(L5XName.Radix, Radix.Decimal)); + len.Add(new XAttribute(L5XName.Value, value.Length)); + element.Add(len); + + var data = new XElement(L5XName.DataValueMember); + data.Add(new XAttribute(L5XName.Name, nameof(DATA))); + data.Add(new XAttribute(L5XName.DataType, Name)); + data.Add(new XAttribute(L5XName.Radix, Radix.Ascii)); + data.Add(new XCData($"'{value}'")); + element.Add(data); + + return element; + } + + /// + /// Determines the string type name from a given element. This is slightly tricky because we have different places + /// to look depending on if this is a string formatted element or a nested string structure. + /// + private static string DetermineName(XElement element) + { + if (element.Attribute(L5XName.Format)?.Value == DataFormat.String) + return element.Ancestors().FirstOrDefault()?.Attribute(L5XName.DataType)?.Value ?? nameof(StringType); + + return element.Get(L5XName.DataType); + } + + /// + /// Determines the string type name from a given element. This is slightly tricky because we have different places + /// to look depending on if this is a string formatted element or a nested string structure. + /// + private static string DetermineValue(XElement element) + { + if (element.Attribute(L5XName.Format)?.Value == DataFormat.String) + return element.Value; + + return element.Elements(L5XName.DataValueMember) + .FirstOrDefault(e => e.Attribute(L5XName.Name)?.Value == nameof(DATA))?.Value + ?? string.Empty; + } + + /// + /// Generates the static string type logix members given the string data. + /// + private static IEnumerable GenerateMembers(string value) + { + var array = ToArray(value); + var len = new LogixMember(nameof(LEN), new DINT(array.Length)); + var data = new LogixMember(nameof(DATA), new ArrayType(array)); + return new List { len, data }; + } + + /// + /// Generates the static string type logix member given the string data and the predefined + /// DATA array length. This method will create the SINT array of the specified length and then + /// assign the provided value. + /// + private static IEnumerable GenerateMembers(string value, ushort length) + { + var array = ToArray(value); + + if (array.Length > length) + throw new ArgumentOutOfRangeException(nameof(value), + $"The string value '{value}' length {value.Length} is greater than the predefined length {length}."); + + var len = new LogixMember(nameof(LEN), new DINT(array.Length)); + var data = new LogixMember(nameof(DATA), GenerateData(array, length)); + + return new List { len, data }; + } + + /// + /// Generates an array type with the predefined length and initializes the array to the provided array. Will fill + /// remaining array with default SINT/Ascii values. + /// + private static ArrayType GenerateData(IReadOnlyList array, ushort length) + { + var data = new SINT[length]; + + for (var i = 0; i < length; i++) + { + data[i] = i < array.Count ? new SINT(array[i], Radix.Ascii) : new SINT(Radix.Ascii); + } + + return new ArrayType(data); + } + + /// + /// Converts the provided string value to a SINT array. Handles empty or null string. SINT array can not be empty + /// to invalid initialization of the ArrayType object. + /// + private static SINT[] ToArray(string value) + { + //If we get a null or empty string then we need to return a single element array to avoid exceptions from array type. + if (string.IsNullOrEmpty(value) || value.All(c => c == '\'')) + return new SINT[] { new(Radix.Ascii) }; + + //Logix encloses strings in single quotes so we need toe remove those if the are present. + value = value.TrimStart('\'').TrimEnd('\''); + + //Breaks apart the string into single ASCII characters to be parsed. + var matches = Regex.Matches(value, LogixAsciiPattern); + return matches.Select(m => + { + var parsed = (SINT)Radix.Ascii.Parse($"'{m.Value}'"); + return new SINT(parsed, Radix.Ascii); + }).ToArray(); + } + + /// + /// Converts the provided SINT array into a string value. Will trim bytes that are zero. + /// + private static string ToString(IEnumerable array) + { + var ascii = array.Where(s => s > 0) + .Select(s => s.ToString(Radix.Ascii).TrimStart('\'').TrimEnd('\'')).ToArray(); + return $"{string.Join(string.Empty, ascii)}"; + } +} \ No newline at end of file diff --git a/src/L5Sharp/Types/StructureType.cs b/src/L5Sharp/Types/StructureType.cs new file mode 100644 index 00000000..4e3ab2ee --- /dev/null +++ b/src/L5Sharp/Types/StructureType.cs @@ -0,0 +1,361 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// A that represents a structure containing members of different types. +/// +/// +/// +/// This type is a building block for all Predefined data types. Inherit from this class to create custom +/// user defined data types that can be used to create in memory representation of the tags for those types. +/// Inherit from if you want the ability to mutate the member structure after instantiation. +/// +/// +public abstract class StructureType : LogixType +{ + private readonly List _members; + + /// + /// Creates a new instance. + /// + /// The name of the type. + /// name is null or empty. + protected StructureType(string name) + { + if (string.IsNullOrEmpty(name)) + throw new ArgumentException("Name can not be null or empty for a structure type object."); + + Name = name; + _members = new List(); + } + + /// + /// Creates a new with the provided name and collection of objects. + /// + /// The name of the structure type. + /// The members of the structure type. + /// name is null or empty. + /// members is null. + protected StructureType(string name, IEnumerable members) : this(name) + { + AddMembers(members.ToList()); + } + + /// + /// Creates a new initialized from the provided data. + /// + /// The element to parse as the new member object. + /// element is null. + /// element does not have required attributes or child elements. + protected StructureType(XElement element) + { + if (element is null) throw new ArgumentNullException(nameof(element)); + Name = element.Get(L5XName.DataType); + _members = element.Elements().Select(e => + { + var member = new LogixMember(e); + member.DataChanged += OnMemberDataChanged; + return member; + }).ToList(); + } + + /// + public override string Name { get; } + + /// + public override DataTypeFamily Family => DataTypeFamily.None; + + /// + public override DataTypeClass Class => DataTypeClass.Unknown; + + /// + public override IEnumerable Members => _members.AsEnumerable(); + + /// + public override XElement Serialize() + { + var element = new XElement(L5XName.Structure); + element.Add(new XAttribute(L5XName.DataType, Name)); + element.Add(_members.Select(m => m.Serialize())); + return element; + } + + /// + /// Gets the logix type for the specified member. + /// + /// The member name to find. + /// The logix type to return. + /// A representing the data of the specified member. + /// A member with the specified name was not found in the member collection. + /// + /// This method is for users implementing custom user defined or predefined types. + /// Use this as the getter for all member's logix types. + /// The user of CallerMemberName allows the member name to be omitted assuming it matches the name of the calling property. + /// + protected TLogixType GetMember([CallerMemberName] string? name = null) where TLogixType : LogixType + { + var type = Members.SingleOrDefault(m => m.Name == name)?.DataType; + + return type switch + { + null => throw new InvalidOperationException($"No member with name '{name}' exists for type {Name}"), + AtomicType atomicType => (TLogixType)Convert.ChangeType(atomicType, typeof(TLogixType)), + _ => (TLogixType)type + }; + } + + /// + /// Adds or updates the specified member's logix type with the provided value. + /// + /// The logix type value to set. + /// The name of the member to set. + /// The logix type parameter of the member. + /// name or value is null. + /// + /// + /// This method is for users implementing custom user defined or predefined types. + /// Use this as the setter for all members of the type. + /// + /// + /// Internally this will update the underlying member collection. + /// If the member already exists, the data type value will be set to the provided value, + /// otherwise a new member will be added at the end of the member collection. + /// This allows the user to initialize member properties in a default constructor in the derived class. + /// Note that the order in which members exist in the underlying collection matters when importing logix type tag data. + /// + /// + protected void SetMember(TLogixType value, [CallerMemberName] string? name = null) + where TLogixType : LogixType + { + if (name is null) throw new ArgumentNullException(nameof(name)); + if (value is null) throw new ArgumentNullException(nameof(value)); + + var current = _members.SingleOrDefault(e => e.Name == name); + + if (current is null) + { + var member = new LogixMember(name, value); + member.DataChanged += OnMemberDataChanged; + _members.Add(member); + RaiseDataChanged(this); + return; + } + + current.DataType = value; + } + + /// + /// Adds the provided member to the structure type. + /// + /// The member to add. + /// member is null. + protected void AddMember(LogixMember member) + { + if (member is null) + throw new ArgumentNullException(nameof(member), "Structure type does not allow null members."); + member.DataChanged += OnMemberDataChanged; + _members.Add(member); + RaiseDataChanged(this); + } + + /// + /// Adds the provided collection of members to the structure type. + /// + /// The collection of members to add. + /// members is null or any member in members is null. + protected void AddMembers(ICollection members) + { + if (members is null) throw new ArgumentNullException(nameof(members)); + + foreach (var member in members) + { + if (member is null) + throw new ArgumentNullException(nameof(members), "Structure type does not allow null members."); + member.DataChanged += OnMemberDataChanged; + _members.Add(member); + } + + RaiseDataChanged(this); + } + + /// + /// Clears all members from the structure type. + /// + protected void ClearMembers() + { + foreach (var member in _members) member.DataChanged -= OnMemberDataChanged; + _members.Clear(); + RaiseDataChanged(this); + } + + /// + /// Inserts the provided member at the specified index of the structure type. + /// + /// The zero-based index at which item should be inserted. + /// The member to insert. + /// member is null. + /// index is less than 0. -or- index is greater than the length of + /// the member collection. + protected void InsertMember(int index, LogixMember member) + { + if (member is null) + throw new ArgumentNullException(nameof(member), "Structure type does not allow null members."); + + member.DataChanged += OnMemberDataChanged; + _members.Insert(index, member); + RaiseDataChanged(this); + } + + /// + /// Removes a member with the specified name from the structure type. + /// + /// The name of the member to remove. + protected void RemoveMember(string name) + { + var index = _members.FindIndex(m => string.Equals(m.Name, name, StringComparison.OrdinalIgnoreCase)); + if (index == -1) return; + var member = _members[index]; + member.DataChanged -= OnMemberDataChanged; + _members.RemoveAt(index); + RaiseDataChanged(this); + } + + /// + /// Removes a member at the specified index from the structure type. + /// + /// The zero-based index of the member to remove. + protected void RemoveMember(int index) + { + var member = _members[index]; + member.DataChanged -= OnMemberDataChanged; + _members.RemoveAt(index); + RaiseDataChanged(this); + } + + /// + /// Replaces a member having the specified name with the provided member instance. + /// + /// The name of the member to replace. + /// The member to replace the current member with. + /// member is null. + /// name does not exists in the structure type. + protected void ReplaceMember(string name, LogixMember member) + { + if (member is null) + throw new ArgumentNullException(nameof(member), "Structure type does not allow null members."); + + var index = _members.FindIndex(m => string.Equals(m.Name, name, StringComparison.OrdinalIgnoreCase)); + + if (index == -1) + throw new ArgumentException($"No member with name {name} was found in the structure."); + + Resubscribe(_members[index], member); + _members[index] = member; + RaiseDataChanged(this); + } + + /// + /// Replaces a member having the specified name with a new member instance of the same name and provided . + /// + /// The name of the member to replace. + /// The data to update the specified member with. + /// type is null. + /// name does not exists in the structure type. + protected void ReplaceMember(string name, LogixType type) + { + if (type is null) + throw new ArgumentNullException(nameof(type)); + + var index = _members.FindIndex(m => string.Equals(m.Name, name, StringComparison.OrdinalIgnoreCase)); + + if (index == -1) + throw new ArgumentException($"No member with name {name} was found in the structure."); + + var member = new LogixMember(name, type); + Resubscribe(_members[index], member); + _members[index] = member; + RaiseDataChanged(this); + } + + /// + /// Replaces a member at the specified index with the provided member instance. + /// + /// The zer-based index at which to replace the member. + /// The member to replace the current member with. + /// member is null. + protected void ReplaceMember(int index, LogixMember member) + { + if (member is null) + throw new ArgumentNullException(nameof(member), "Structure type does not allow null members."); + + Resubscribe(_members[index], member); + _members[index] = member; + RaiseDataChanged(this); + } + + /// + /// Raises the local logix type DataChanged event, allowing nested member data change event to bubble up the + /// to the root member. + /// + /// The object that initiated the data change event. + /// The event arguments of the data changed event. + protected virtual void OnMemberDataChanged(object sender, EventArgs e) => RaiseDataChanged(sender); + + /// + /// Remove old event handler and attach new to ensure no memory leak. + /// + private void Resubscribe(LogixMember remove, LogixMember add) + { + remove.DataChanged -= OnMemberDataChanged; + add.DataChanged += OnMemberDataChanged; + } +} + +/// +/// Extensions methods for the class. +/// +public static class StructureTypeExtensions +{ + /// + /// Traverses the type/member hierarchy of the data and builds a collection of + /// objects based on all the user defined types in the tree. + /// + /// The structure type for which to generate a list of user defined type objects. + /// A containing a for each user type in the + /// structure's type/member hierarchy + public static IEnumerable ToUDT(this StructureType type) + { + var results = new List(); + + if (type.Class == DataTypeClass.User) + { + var userType = new DataType + { + Name = type.Name, + Class = type.Class, + Family = type.Family, + Members = new LogixContainer(type.Members.Select(m => new DataTypeMember + { + Name = m.Name, + DataType = m.DataType.Name, + Dimension = m.DataType is ArrayType array ? array.Dimensions : Dimensions.Empty, + Radix = Radix.Default(m.DataType), + ExternalAccess = ExternalAccess.ReadWrite + })) + }; + + results.Add(userType); + } + + foreach (var member in type.Members) + if (member.DataType is ComplexType structureType) + results.AddRange(structureType.ToUDT()); + + return results; + } +} \ No newline at end of file diff --git a/src/L5Sharp/Utilities/Catalog/CatalogEntry.cs b/src/L5Sharp/Utilities/Catalog/CatalogEntry.cs new file mode 100644 index 00000000..c82c34fe --- /dev/null +++ b/src/L5Sharp/Utilities/Catalog/CatalogEntry.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Linq; + +namespace L5Sharp.Core +{ + /// + /// A set of properties that defines a catalog entry for a component. + /// + /// + /// This object must be retrieved from the service object. It serves as a container + /// of data that can be retrieved in order to created a valid instance using a known + /// . + /// + public class CatalogEntry + { + /// + /// Gets the of the current . + /// + public string CatalogNumber { get; set; } = string.Empty; + + /// + /// Gets the description of the . + /// + public string Description { get; set; } = string.Empty; + + /// + /// Gets the of the current . + /// + public Vendor Vendor { get; set; } = Vendor.Unknown; + + /// + /// Gets the of the current . + /// + public ProductType ProductType { get; set; } = ProductType.Unknown; + + /// + /// Gets the code number that identifies the . + /// + public ushort ProductCode { get; set; } + + /// + /// Gets a collection of valid for the . + /// + public IEnumerable Revisions { get; set; } = Enumerable.Empty(); + + /// + /// Gets a collection of that apply to the module. + /// + public IEnumerable Categories { get; set; } = Enumerable.Empty(); + + /// + /// Gets the set of that define the connections of the module. + /// + public IEnumerable Ports { get; set; } = Enumerable.Empty(); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Utilities/Catalog/ModuleCatalog.cs b/src/L5Sharp/Utilities/Catalog/ModuleCatalog.cs new file mode 100644 index 00000000..1e9c8dff --- /dev/null +++ b/src/L5Sharp/Utilities/Catalog/ModuleCatalog.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Xml.Linq; + +namespace L5Sharp.Core +{ + /// + /// A service that allows lookups for objects based on catalog numbers. + /// + /// + /// This service will attempt to load the Rockwell Catalog service file into memory in order to query for data. + /// The catalog service file's existence and content will be system dependant. If this code is run on a machine that + /// does not have Rockwell's Studio 5000 installed, instantiating this class will fail with . + /// + public class ModuleCatalog + { + private const string RaDevice = "RADevice"; + private const string CatalogNumber = "CatalogNumber"; + private const string VendorId = "VendorID"; + private const string ProductType = "ProductType"; + private const string ProductCode = "ProductCode"; + private const string MajorRev = "MajorRev"; + private const string Category = "Category"; + private const string Port = "Port"; + private const string Name = "Name"; + private const string Description = "Description"; + private const string Number = "Number"; + private const string DefaultMinorRev = "DefaultMinorRev"; + private const string Type = "Type"; + private const string DownstreamOnly = "DownstreamOnly"; + + private const string RockwellCatalogServiceLocalPath = + @"Rockwell Automation\Catalog Services\CatalogSvcsDatabaseV2.xml"; + + private readonly XDocument _catalog; + + /// + /// Creates a new instance of the service. + /// + public ModuleCatalog() + { + _catalog = GetLocalCatalog(); + } + + /// + /// Gets a instance for the provided catalogNumber + /// + /// The catalog number of the to lookup. + /// A instance for the specified catalogNumber if found in the current + /// catalog service file; otherwise, null. + /// catalogNumber is null. + /// The provided catalog number was not found. + public CatalogEntry Lookup(string catalogNumber) + { + if (catalogNumber is null) + throw new ArgumentNullException(nameof(catalogNumber)); + + var device = _catalog.Descendants(RaDevice) + .FirstOrDefault(e => e.Descendants(CatalogNumber).First().Value == catalogNumber); + + if (device is null) + throw new InvalidOperationException( + $"Device with catalog number {catalogNumber} does not exists in local catalog database."); + + return MaterializeDefinition(device); + } + + /// + /// Gets a instance for the provided catalogNumber. + /// + /// The catalog number of the to lookup. + /// A instance for the specified catalogNumber if found in the current + /// catalog service file; otherwise, null. + /// catalogNumber is null. + /// The provided catalog number was not found. + public CatalogEntry? TryLookup(string catalogNumber) + { + if (catalogNumber is null) + throw new ArgumentNullException(nameof(catalogNumber)); + + var device = _catalog.Descendants(RaDevice) + .FirstOrDefault(e => e.Descendants(CatalogNumber).First().Value == catalogNumber); + + return device is not null ? MaterializeDefinition(device) : null; + } + + private static CatalogEntry MaterializeDefinition(XContainer element) + { + var catalogNumber = GetCatalogNumber(element); + var vendor = GetVendor(element); + var productType = GetProductType(element); + var productCode = GetProductCode(element); + var revisions = GetRevisions(element); + var categories = GetCategories(element); + var ports = GetPorts(element); + var description = GetDescription(element); + + return new CatalogEntry + { + CatalogNumber = catalogNumber, + Vendor = vendor, + ProductType = productType, + ProductCode = productCode, + Revisions = revisions, + Categories = categories, + Ports = ports, + Description = description + }; + } + + private static string GetCatalogNumber(XContainer element) => + element.Descendants(CatalogNumber).FirstOrDefault()!.Value; + + private static Vendor GetVendor(XContainer element) + { + var vendor = element.Descendants(VendorId).First(); + + var id = ushort.Parse(vendor.Value); + var name = vendor.Attribute(Name)?.Value; + + return new Vendor(id, name); + } + + private static ProductType GetProductType(XContainer element) + { + var productType = element.Descendants(ProductType).First(); + + var id = ushort.Parse(productType.Value); + var name = productType.Attribute(Name)?.Value; + + return new ProductType(id, name); + } + + private static ushort GetProductCode(XContainer element) => + ushort.Parse(element.Descendants(ProductCode).First().Value); + + private static IEnumerable GetRevisions(XContainer element) + { + var revisions = element.Descendants(MajorRev); + + foreach (var revision in revisions) + { + var major = revision.Attribute(Number)?.Value; + var minor = revision.Attribute(DefaultMinorRev)?.Value; + + yield return new Revision($"{major}.{minor}"); + } + } + + private static IEnumerable GetCategories(XContainer element) => + element.Descendants(Category).Select(c => c.Attribute(Name)!.Value); + + private static IEnumerable GetPorts(XContainer element) + { + var ports = element.Descendants(Port); + + foreach (var port in ports) + { + var number = int.Parse(port.Attribute(Number)?.Value!); + var type = port.Attribute(Type)?.Value!; + var downstreamOnly = port.Elements().Any(e => e.Value == DownstreamOnly); + + yield return new PortInfo + { + Number = number, + Type = type, + DownstreamOnly = downstreamOnly + }; + } + } + + private static string GetDescription(XContainer element) => + element.Descendants(Description).First().Value; + + private static XDocument GetLocalCatalog() + { + var programData = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData); + var serviceFile = Path.Combine(programData, RockwellCatalogServiceLocalPath); + + var info = new FileInfo(serviceFile); + + if (!info.Exists) + throw new InvalidOperationException( + $"The catalog service file {serviceFile} does not exist in the current environment."); + + var document = XDocument.Load(info.FullName); + + return document; + } + } +} \ No newline at end of file diff --git a/src/L5Sharp/Utilities/Catalog/PortInfo.cs b/src/L5Sharp/Utilities/Catalog/PortInfo.cs new file mode 100644 index 00000000..787c3f2f --- /dev/null +++ b/src/L5Sharp/Utilities/Catalog/PortInfo.cs @@ -0,0 +1,29 @@ +namespace L5Sharp.Core; + +/// +/// A entity class representing the data returned from a module catalog entry. +/// +public class PortInfo +{ + /// + /// The unique identifying number of the port. + /// + /// + /// All Modules have at least one port which defines how it connects to other peripherals or devices. + /// Each port is identified by the number. Default value is 1. + /// + public int Number { get; set; } = 1; + + /// + /// Gets the value that represents the port type. + /// + /// + /// This value appears to be specific to the product. Ports with IP will have 'Network' for their type. + /// + public string Type { get; set; } = string.Empty; + + /// + /// Gets a value indicating whether the port allows upstream connection, or if it is a downstream only port. + /// + public bool DownstreamOnly { get; set; } +} \ No newline at end of file diff --git a/src/L5Sharp/Utilities/L5XExtensions.cs b/src/L5Sharp/Utilities/L5XExtensions.cs new file mode 100644 index 00000000..174c0e32 --- /dev/null +++ b/src/L5Sharp/Utilities/L5XExtensions.cs @@ -0,0 +1,236 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using System.Xml; +using System.Xml.Linq; + +namespace L5Sharp.Core; + +/// +/// Extensions methods that assist with the base functionality of the library. +/// +public static class L5XExtensions +{ + /// + /// Builds a deserialization expression delegate which returns the specified type using the current type information. + /// + /// The current type for which to build the expression. + /// The return type of the expression delegate. + /// A which accepts a and returns the specified + /// return type. + /// + /// This extension is the basis for how we build the deserialization functions using reflection and + /// expression trees. Using compiled expression trees is much more efficient that calling the invoke method for a type's + /// constructor info obtained via reflection. This method makes all the necessary checks on the current type, ensuring the + /// returned deserializer delegate will execute without exception. + /// + public static Func Deserializer(this Type type) + { + if (type is null) throw new ArgumentNullException(nameof(type)); + + if (type.IsAbstract) + throw new ArgumentException($"Can not build deserializer expression for abstract type '{type.Name}'."); + + if (!typeof(TReturn).IsAssignableFrom(type)) + throw new ArgumentException( + $"The type {type.Name} is not assignable (inherited) from '{typeof(TReturn).Name}'."); + + var constructor = type.GetConstructor(new[] { typeof(XElement) }); + + if (constructor is null || !constructor.IsPublic) + throw new ArgumentException( + $"Can not build expression for type '{type.Name}' without public constructor accepting a XElement parameter."); + + var parameter = Expression.Parameter(typeof(XElement), "element"); + var expression = Expression.New(constructor, parameter); + return Expression.Lambda>(expression, parameter).Compile(); + } + + /// + /// A concise method for getting a required attribute value from a XElement object. + /// + /// The element containing the attribute to retrieve. + /// The name of the attribute value to get. + /// The value of the element's specified attribute. + /// No attribute with name exists for the current element. + public static string Get(this XElement element, XName name) => + element.Attribute(name)?.Value ?? throw element.L5XError(name); + + /// + /// Determines if the current string is a value string. + /// + /// The string input to analyze. + /// true if the string is a valid tag name string; otherwise, false. + public static bool IsTag(this string input) => TagName.IsTag(input); + + /// + /// Determines if the current string is equal to string.Empty. + /// + /// The string input to analyze. + /// true if the string is empty. Otherwise false. + public static bool IsEmpty(this string value) => value.Equals(string.Empty); + + /// + /// Determines if this string is the same as, meaning equal to regardless of case, another string. + /// + /// The string value to compare. + /// The other string to compare. + /// true if the strings are equal using the + /// equality comparer,. Otherwise false. + /// This is a simplified way of calling the string comparer equals method since it is a little verbose. + /// This could be used a lot since Logix naming is case agnostic. + public static bool IsEquivalent(this string value, string other) => + StringComparer.OrdinalIgnoreCase.Equals(value, other); + + /// + /// Gets the L5X element name of the type's containing element. + /// + /// The type to get the L5X element name for. + /// + /// A representing the name of the parent element that corresponds to the type's container. + /// + /// + /// All this does is look for the first to use as the explicitly configured container + /// name, and if not found, returns the name with an 's' appended as the default element container + /// name, as most type's element container is just the plural type name. This is unsophisticated pluralization, + /// but it works for all cases in the L5X. + /// + public static string L5XContainer(this Type type) + { + var attribute = type.GetCustomAttributes().FirstOrDefault(); + return attribute is not null ? attribute.ContainerName : $"{type.Name}s"; + } + + /// + /// Creates and configures a to be thrown for required properties of complex + /// types that do not exist for the current element object. + /// + /// The element for which the exception is occuring. + /// The name of the attribute or child element that is missing. + /// A object configured with standard message and exception + /// data for troubleshooting purposes. + /// This is a helper so to avoid reconfiguring this exception every time it needed to be thrown. + /// Aside from setting the standard error message, this will add the target attribute name, element line number, + /// and element object as data to the exception. + public static InvalidOperationException L5XError(this XElement element, XName name) + { + var message = $"The required attribute or child element '{name}' does not exist for {element.Name}."; + var line = ((IXmlLineInfo)element).HasLineInfo() ? ((IXmlLineInfo)element).LineNumber : -1; + var exception = new InvalidOperationException(message); + exception.Data.Add("target", name); + exception.Data.Add("line", line); + exception.Data.Add("element", element); + return exception; + } + + /// + /// Gets the L5X element local name for the current element, which represents the "L5XType". + /// + /// The to get the type of. + /// A representing the type name for the element. + /// + /// The "L5XType" is simply the name of the element. Since elements map to classes, we can know which + /// type to deserialize or instantiate given the name of the element. + /// + public static string L5XType(this XElement element) => element.Name.LocalName; + + /// + /// Gets the first configured L5XType name or element name for the specified type. + /// + /// The type to get the L5XType (element name) for. + /// A representing the name of the element that corresponds to the type. + /// + /// + /// All this does look for the first configured to use as the explicitly configured + /// name, and if not found, returns the name as the default element name, + /// as most types are the name of the element anyway. + /// + /// + /// The "L5XType" is simply the name of the element. Since elements map the classes, we can know which + /// type to deserialize or instantiate given the name of the element. + /// + /// + public static string L5XType(this Type type) + { + var attribute = type.GetCustomAttributes().FirstOrDefault(); + return attribute is not null ? attribute.TypeName : type.Name; + } + + /// + /// Gets all the configured L5XType names fo the current type. + /// + /// The type to get the L5XTypes (element names) name for. + /// A collection of values representing the element names the type supports. + /// + /// This attempts to find all configured class to use as the explicitly + /// configured name(s) for the type, and if not found, returns just the name as the + /// default element name, as most types are the name of the element anyway. + /// + public static IEnumerable L5XTypes(this Type type) + { + var attributes = type.GetCustomAttributes().ToList(); + return attributes.Any() ? attributes.Select(attribute => attribute.TypeName) : new[] { type.Name }; + } + + /// + /// Gets the Name attribute value for the current . + /// + /// The instance. + /// A representing the name value if found; Otherwise, empty. + /// + /// This is a helper since we access and use the name attribute so often I just wanted to make + /// the code more concise. + /// + public static string LogixName(this XElement element) => element.Attribute(L5XName.Name)?.Value ?? string.Empty; + + /// + /// Determines the tag name for a given representing a module IO tag. + /// + /// The representing the module defined IO tag + /// (InputTag, OutputTag, or ConfigTag). + /// A representing the name of the module IO tag. + /// + /// This is a helper extension since the logic is somewhat complex and used in more than one class. + /// We look up the L5X tree for module name and parent name, as well as back down to find the potential slot of the module. + /// All this info along with the corresponding tag suffix make up the tag name for a module tag, + /// which is not inherent in the L5X element itself, but one that is important to us as it allows us to + /// find or reference these tags by name (just as you would find in Studio 5k). + /// + public static TagName ModuleTagName(this XElement element) + { + var suffix = DetermineModuleSuffix(element); + var module = element.Ancestors(L5XName.Module).FirstOrDefault(); + var moduleName = module?.Attribute(L5XName.Name)?.Value; + var parentName = module?.Attribute(L5XName.ParentModule)?.Value; + + var slot = module?.Descendants(L5XName.Port) + .Where(p => bool.TryParse(p.Attribute(L5XName.Upstream)?.Value, out var upstream) && upstream + && p.Attribute(L5XName.Type)?.Value != "Ethernet" + && int.TryParse(p.Attribute(L5XName.Address)?.Value, out _)) + .Select(p => p.Attribute(L5XName.Address)?.Value) + .FirstOrDefault(); + + return slot is not null ? $"{parentName}:{slot}:{suffix}" : $"{moduleName}:{suffix}"; + + string DetermineModuleSuffix(XElement el) + { + if (el.Name == L5XName.InputTag) + return el.Parent?.Attribute(L5XName.InputTagSuffix)?.Value ?? "I"; + + if (el.Name == L5XName.OutputTag) + return el.Parent?.Attribute(L5XName.OutputTagSuffix)?.Value ?? "O"; + + return "C"; + } + } + + /// + /// Returns the string value as a value object. + /// + /// The string value. + /// A object containing the string value. + /// This is to make converting from string to XName concise. + public static XName XName(this string value) => System.Xml.Linq.XName.Get(value); +} \ No newline at end of file diff --git a/src/L5Sharp/Utilities/L5XName.cs b/src/L5Sharp/Utilities/L5XName.cs new file mode 100644 index 00000000..75c21587 --- /dev/null +++ b/src/L5Sharp/Utilities/L5XName.cs @@ -0,0 +1,9378 @@ +// ReSharper disable InconsistentNaming +// ReSharper disable CommentTypo +// ReSharper disable IdentifierTypo + +namespace L5Sharp.Core; + +/// +/// A static class of L5X element and attribute names that provide string reference to L5X names so to avoid using +/// magic string. This class was generated by querying the L5X XSD schema file. +/// +public static class L5XName +{ + /// + /// Gets the AAttachmentType L5X name value. + /// + public const string AAttachmentType = "AAttachmentType"; + + /// + /// Gets the AbsoluteFeedbackEnable L5X name value. + /// + public const string AbsoluteFeedbackEnable = "AbsoluteFeedbackEnable"; + + /// + /// Gets the AbsoluteFeedbackOffset L5X name value. + /// + public const string AbsoluteFeedbackOffset = "AbsoluteFeedbackOffset"; + + /// + /// Gets the ABusType L5X name value. + /// + public const string ABusType = "ABusType"; + + /// + /// Gets the AccelerationDataScaling L5X name value. + /// + public const string AccelerationDataScaling = "AccelerationDataScaling"; + + /// + /// Gets the AccelerationDataScalingExp L5X name value. + /// + public const string AccelerationDataScalingExp = "AccelerationDataScalingExp"; + + /// + /// Gets the AccelerationDataScalingFactor L5X name value. + /// + public const string AccelerationDataScalingFactor = "AccelerationDataScalingFactor"; + + /// + /// Gets the AccelerationFeedforwardGain L5X name value. + /// + public const string AccelerationFeedforwardGain = "AccelerationFeedforwardGain"; + + /// + /// Gets the AccelerationLimit L5X name value. + /// + public const string AccelerationLimit = "AccelerationLimit"; + + /// + /// Gets the AccelerationLimitBipolar L5X name value. + /// + public const string AccelerationLimitBipolar = "AccelerationLimitBipolar"; + + /// + /// Gets the AccelerationLimitNegative L5X name value. + /// + public const string AccelerationLimitNegative = "AccelerationLimitNegative"; + + /// + /// Gets the AccelerationLimitPositive L5X name value. + /// + public const string AccelerationLimitPositive = "AccelerationLimitPositive"; + + /// + /// Gets the ACInjectionBrakeFrequencyThreshold L5X name value. + /// + public const string ACInjectionBrakeFrequencyThreshold = "ACInjectionBrakeFrequencyThreshold"; + + /// + /// Gets the ACInjectionBrakePowerThreshold L5X name value. + /// + public const string ACInjectionBrakePowerThreshold = "ACInjectionBrakePowerThreshold"; + + /// + /// Gets the ACInjectionBrakeRegulatorKi L5X name value. + /// + public const string ACInjectionBrakeRegulatorKi = "ACInjectionBrakeRegulatorKi"; + + /// + /// Gets the ACInjectionBrakeRegulatorKp L5X name value. + /// + public const string ACInjectionBrakeRegulatorKp = "ACInjectionBrakeRegulatorKp"; + + /// + /// Gets the Acked L5X name value. + /// + public const string Acked = "Acked"; + + /// + /// Gets the AckRequired L5X name value. + /// + public const string AckRequired = "AckRequired"; + + /// + /// Gets the AckTime L5X name value. + /// + public const string AckTime = "AckTime"; + + /// + /// Gets the ACKTimeout L5X name value. + /// + public const string ACKTimeout = "ACKTimeout"; + + /// + /// Gets the ACLineContactorInputChecking L5X name value. + /// + public const string ACLineContactorInputChecking = "ACLineContactorInputChecking"; + + /// + /// Gets the ACLineCurrentUnbalanceLimit L5X name value. + /// + public const string ACLineCurrentUnbalanceLimit = "ACLineCurrentUnbalanceLimit"; + + /// + /// Gets the ACLineFrequencyChangeAction L5X name value. + /// + public const string ACLineFrequencyChangeAction = "ACLineFrequencyChangeAction"; + + /// + /// Gets the ACLineFrequencyChangeThreshold L5X name value. + /// + public const string ACLineFrequencyChangeThreshold = "ACLineFrequencyChangeThreshold"; + + /// + /// Gets the ACLineFrequencyChangeTime L5X name value. + /// + public const string ACLineFrequencyChangeTime = "ACLineFrequencyChangeTime"; + + /// + /// Gets the ACLineHighFreqUserLimit L5X name value. + /// + public const string ACLineHighFreqUserLimit = "ACLineHighFreqUserLimit"; + + /// + /// Gets the ACLineHighFreqUserLimitAlternate L5X name value. + /// + public const string ACLineHighFreqUserLimitAlternate = "ACLineHighFreqUserLimitAlternate"; + + /// + /// Gets the ACLineLowFreqUserLimit L5X name value. + /// + public const string ACLineLowFreqUserLimit = "ACLineLowFreqUserLimit"; + + /// + /// Gets the ACLineLowFreqUserLimitAlternate L5X name value. + /// + public const string ACLineLowFreqUserLimitAlternate = "ACLineLowFreqUserLimitAlternate"; + + /// + /// Gets the ACLineOverloadUserLimit L5X name value. + /// + public const string ACLineOverloadUserLimit = "ACLineOverloadUserLimit"; + + /// + /// Gets the ACLineOvervoltageUserLimit L5X name value. + /// + public const string ACLineOvervoltageUserLimit = "ACLineOvervoltageUserLimit"; + + /// + /// Gets the ACLineOvervoltageUserLimitAlternate L5X name value. + /// + public const string ACLineOvervoltageUserLimitAlternate = "ACLineOvervoltageUserLimitAlternate"; + + /// + /// Gets the ACLineResonanceUserLimit L5X name value. + /// + public const string ACLineResonanceUserLimit = "ACLineResonanceUserLimit"; + + /// + /// Gets the ACLineSourceImpedance L5X name value. + /// + public const string ACLineSourceImpedance = "ACLineSourceImpedance"; + + /// + /// Gets the ACLineSourceImpedanceAlternate L5X name value. + /// + public const string ACLineSourceImpedanceAlternate = "ACLineSourceImpedanceAlternate"; + + /// + /// Gets the ACLineSourcePower L5X name value. + /// + public const string ACLineSourcePower = "ACLineSourcePower"; + + /// + /// Gets the ACLineSourcePowerAlternate L5X name value. + /// + public const string ACLineSourcePowerAlternate = "ACLineSourcePowerAlternate"; + + /// + /// Gets the ACLineSourceSelect L5X name value. + /// + public const string ACLineSourceSelect = "ACLineSourceSelect"; + + /// + /// Gets the ACLineSyncErrorTolerance L5X name value. + /// + public const string ACLineSyncErrorTolerance = "ACLineSyncErrorTolerance"; + + /// + /// Gets the ACLineSyncLossAction L5X name value. + /// + public const string ACLineSyncLossAction = "ACLineSyncLossAction"; + + /// + /// Gets the ACLineSyncLossTime L5X name value. + /// + public const string ACLineSyncLossTime = "ACLineSyncLossTime"; + + /// + /// Gets the ACLineUndervoltageUserLimit L5X name value. + /// + public const string ACLineUndervoltageUserLimit = "ACLineUndervoltageUserLimit"; + + /// + /// Gets the ACLineUndervoltageUserLimitAlternate L5X name value. + /// + public const string ACLineUndervoltageUserLimitAlternate = "ACLineUndervoltageUserLimitAlternate"; + + /// + /// Gets the ACLineVoltageSagAction L5X name value. + /// + public const string ACLineVoltageSagAction = "ACLineVoltageSagAction"; + + /// + /// Gets the ACLineVoltageSagThreshold L5X name value. + /// + public const string ACLineVoltageSagThreshold = "ACLineVoltageSagThreshold"; + + /// + /// Gets the ACLineVoltageSagTime L5X name value. + /// + public const string ACLineVoltageSagTime = "ACLineVoltageSagTime"; + + /// + /// Gets the ACLineVoltageTimeConstant L5X name value. + /// + public const string ACLineVoltageTimeConstant = "ACLineVoltageTimeConstant"; + + /// + /// Gets the ACLineVoltageUnbalanceLimit L5X name value. + /// + public const string ACLineVoltageUnbalanceLimit = "ACLineVoltageUnbalanceLimit"; + + /// + /// Gets the Action L5X name value. + /// + public const string Action = "Action"; + + /// + /// Gets the Action1FirstRegionID L5X name value. + /// + public const string Action1FirstRegionID = "Action1FirstRegionID"; + + /// + /// Gets the Action2FirstRegionID L5X name value. + /// + public const string Action2FirstRegionID = "Action2FirstRegionID"; + + /// + /// Gets the ActionName L5X name value. + /// + public const string ActionName = "ActionName"; + + /// + /// Gets the ActiveCurrentCommand L5X name value. + /// + public const string ActiveCurrentCommand = "ActiveCurrentCommand"; + + /// + /// Gets the ActiveCurrentLowPassFilterBandwidth L5X name value. + /// + public const string ActiveCurrentLowPassFilterBandwidth = "ActiveCurrentLowPassFilterBandwidth"; + + /// + /// Gets the ActiveCurrentNotchFilterFrequency L5X name value. + /// + public const string ActiveCurrentNotchFilterFrequency = "ActiveCurrentNotchFilterFrequency"; + + /// + /// Gets the ActiveCurrentRateLimit L5X name value. + /// + public const string ActiveCurrentRateLimit = "ActiveCurrentRateLimit"; + + /// + /// Gets the ActiveCurrentTrim L5X name value. + /// + public const string ActiveCurrentTrim = "ActiveCurrentTrim"; + + /// + /// Gets the ActiveStationFile L5X name value. + /// + public const string ActiveStationFile = "ActiveStationFile"; + + /// + /// Gets the ActualPositionTolerance L5X name value. + /// + public const string ActualPositionTolerance = "ActualPositionTolerance"; + + /// + /// Gets the ActuatorDiameter L5X name value. + /// + public const string ActuatorDiameter = "ActuatorDiameter"; + + /// + /// Gets the ActuatorDiameterUnit L5X name value. + /// + public const string ActuatorDiameterUnit = "ActuatorDiameterUnit"; + + /// + /// Gets the ActuatorLead L5X name value. + /// + public const string ActuatorLead = "ActuatorLead"; + + /// + /// Gets the ActuatorLeadUnit L5X name value. + /// + public const string ActuatorLeadUnit = "ActuatorLeadUnit"; + + /// + /// Gets the ActuatorType L5X name value. + /// + public const string ActuatorType = "ActuatorType"; + + /// + /// Gets the AdaptiveTuningConfiguration L5X name value. + /// + public const string AdaptiveTuningConfiguration = "AdaptiveTuningConfiguration"; + + /// + /// Gets the AdaptiveTuningGainScalingFactorMin L5X name value. + /// + public const string AdaptiveTuningGainScalingFactorMin = "AdaptiveTuningGainScalingFactorMin"; + + /// + /// Gets the AdaptiveTuningTrackingNotchFilters L5X name value. + /// + public const string AdaptiveTuningTrackingNotchFilters = "AdaptiveTuningTrackingNotchFilters"; + + /// + /// Gets the AddedToProject L5X name value. + /// + public const string AddedToProject = "AddedToProject"; + + /// + /// Gets the AdditionalBusCapacitance L5X name value. + /// + public const string AdditionalBusCapacitance = "AdditionalBusCapacitance"; + + /// + /// Gets the AdditionalHelpText L5X name value. + /// + public const string AdditionalHelpText = "AdditionalHelpText"; + + /// + /// Gets the AdditionalHelpTextType L5X name value. + /// + public const string AdditionalHelpTextType = "AdditionalHelpTextType"; + + /// + /// Gets the AddOnInstruction L5X name value. + /// + public const string AddOnInstruction = "AddOnInstruction"; + + /// + /// Gets the AddOnInstructionDefinition L5X name value. + /// + public const string AddOnInstructionDefinition = "AddOnInstructionDefinition"; + + /// + /// Gets the AddOnInstructionDefinitions L5X name value. + /// + public const string AddOnInstructionDefinitions = "AddOnInstructionDefinitions"; + + /// + /// Gets the AddOnInstructionDefinitionsUId L5X name value. + /// + public const string AddOnInstructionDefinitionsUId = "AddOnInstructionDefinitionsUId"; + + /// + /// Gets the Address L5X name value. + /// + public const string Address = "Address"; + + /// + /// Gets the AddressB L5X name value. + /// + public const string AddressB = "AddressB"; + + /// + /// Gets the AddressMask L5X name value. + /// + public const string AddressMask = "AddressMask"; + + /// + /// Gets the AFBD_BlockType L5X name value. + /// + public const string AFBD_BlockType = "AFBD_BlockType"; + + /// + /// Gets the AFBD_FunctionType L5X name value. + /// + public const string AFBD_FunctionType = "AFBD_FunctionType"; + + /// + /// Gets the AlarmAnalogParameters L5X name value. + /// + public const string AlarmAnalogParameters = "AlarmAnalogParameters"; + + /// + /// Gets the AlarmAnalogType L5X name value. + /// + public const string AlarmAnalogType = "AlarmAnalogType"; + + /// + /// Gets the AlarmClass L5X name value. + /// + public const string AlarmClass = "AlarmClass"; + + /// + /// Gets the AlarmCondition L5X name value. + /// + public const string AlarmCondition = "AlarmCondition"; + + /// + /// Gets the AlarmConditionDefinition L5X name value. + /// + public const string AlarmConditionDefinition = "AlarmConditionDefinition"; + + /// + /// Gets the AlarmConditions L5X name value. + /// + public const string AlarmConditions = "AlarmConditions"; + + /// + /// Gets the AlarmConfig L5X name value. + /// + public const string AlarmConfig = "AlarmConfig"; + + /// + /// Gets the AlarmConfigType L5X name value. + /// + public const string AlarmConfigType = "AlarmConfigType"; + + /// + /// Gets the AlarmCount L5X name value. + /// + public const string AlarmCount = "AlarmCount"; + + /// + /// Gets the AlarmCountReset L5X name value. + /// + public const string AlarmCountReset = "AlarmCountReset"; + + /// + /// Gets the AlarmCountResetTime L5X name value. + /// + public const string AlarmCountResetTime = "AlarmCountResetTime"; + + /// + /// Gets the AlarmDataType L5X name value. + /// + public const string AlarmDataType = "AlarmDataType"; + + /// + /// Gets the AlarmDefinitionCollectionAdaptorType L5X name value. + /// + public const string AlarmDefinitionCollectionAdaptorType = "AlarmDefinitionCollectionAdaptorType"; + + /// + /// Gets the AlarmDefinitions L5X name value. + /// + public const string AlarmDefinitions = "AlarmDefinitions"; + + /// + /// Gets the AlarmDigitalParameters L5X name value. + /// + public const string AlarmDigitalParameters = "AlarmDigitalParameters"; + + /// + /// Gets the AlarmDigitalType L5X name value. + /// + public const string AlarmDigitalType = "AlarmDigitalType"; + + /// + /// Gets the AlarmLimitsInv L5X name value. + /// + public const string AlarmLimitsInv = "AlarmLimitsInv"; + + /// + /// Gets the AlarmMessageCollectionType L5X name value. + /// + public const string AlarmMessageCollectionType = "AlarmMessageCollectionType"; + + /// + /// Gets the AlarmMessageTextType L5X name value. + /// + public const string AlarmMessageTextType = "AlarmMessageTextType"; + + /// + /// Gets the AlarmMessageType L5X name value. + /// + public const string AlarmMessageType = "AlarmMessageType"; + + /// + /// Gets the AlarmSetOperIncluded L5X name value. + /// + public const string AlarmSetOperIncluded = "AlarmSetOperIncluded"; + + /// + /// Gets the AlarmSetRollupIncluded L5X name value. + /// + public const string AlarmSetRollupIncluded = "AlarmSetRollupIncluded"; + + /// + /// Gets the AliasBase L5X name value. + /// + public const string AliasBase = "AliasBase"; + + /// + /// Gets the AliasFor L5X name value. + /// + public const string AliasFor = "AliasFor"; + + /// + /// Gets the Alternate1UpdateMultiplier L5X name value. + /// + public const string Alternate1UpdateMultiplier = "Alternate1UpdateMultiplier"; + + /// + /// Gets the Alternate2UpdateMultiplier L5X name value. + /// + public const string Alternate2UpdateMultiplier = "Alternate2UpdateMultiplier"; + + /// + /// Gets the AmplifierCatalogNumber L5X name value. + /// + public const string AmplifierCatalogNumber = "AmplifierCatalogNumber"; + + /// + /// Gets the AnyInAlarmUnack L5X name value. + /// + public const string AnyInAlarmUnack = "AnyInAlarmUnack"; + + /// + /// Gets the AppendChars L5X name value. + /// + public const string AppendChars = "AppendChars"; + + /// + /// Gets the ApplicationCatalogNumber L5X name value. + /// + public const string ApplicationCatalogNumber = "ApplicationCatalogNumber"; + + /// + /// Gets the ApplicationCatalogNumberInstance L5X name value. + /// + public const string ApplicationCatalogNumberInstance = "ApplicationCatalogNumberInstance"; + + /// + /// Gets the ApplicationCatalogNumberVersion L5X name value. + /// + public const string ApplicationCatalogNumberVersion = "ApplicationCatalogNumberVersion"; + + /// + /// Gets the ApplicationType L5X name value. + /// + public const string ApplicationType = "ApplicationType"; + + /// + /// Gets the AProgramType L5X name value. + /// + public const string AProgramType = "AProgramType"; + + /// + /// Gets the ArchitectureID L5X name value. + /// + public const string ArchitectureID = "ArchitectureID"; + + /// + /// Gets the Area L5X name value. + /// + public const string Area = "Area"; + + /// + /// Gets the AreaAdaptorCollectionType L5X name value. + /// + public const string AreaAdaptorCollectionType = "AreaAdaptorCollectionType"; + + /// + /// Gets the AreaAdaptorType L5X name value. + /// + public const string AreaAdaptorType = "AreaAdaptorType"; + + /// + /// Gets the Areas L5X name value. + /// + public const string Areas = "Areas"; + + /// + /// Gets the Argument L5X name value. + /// + public const string Argument = "Argument"; + + /// + /// Gets the ArgumentIOI L5X name value. + /// + public const string ArgumentIOI = "ArgumentIOI"; + + /// + /// Gets the ArgumentIOINTT L5X name value. + /// + public const string ArgumentIOINTT = "ArgumentIOINTT"; + + /// + /// Gets the Array L5X name value. + /// + public const string Array = "Array"; + + /// + /// Gets the ArrayMember L5X name value. + /// + public const string ArrayMember = "ArrayMember"; + + /// + /// Gets the ASCII L5X name value. + /// + public const string ASCII = "ASCII"; + + /// + /// Gets the ASCIIDriverType L5X name value. + /// + public const string ASCIIDriverType = "ASCIIDriverType"; + + /// + /// Gets the ASFC_BranchElementType L5X name value. + /// + public const string ASFC_BranchElementType = "ASFC_BranchElementType"; + + /// + /// Gets the ASFC_LangElemWireType L5X name value. + /// + public const string ASFC_LangElemWireType = "ASFC_LangElemWireType"; + + /// + /// Gets the ASFC_LegElementType L5X name value. + /// + public const string ASFC_LegElementType = "ASFC_LegElementType"; + + /// + /// Gets the ASFC_StepElementType L5X name value. + /// + public const string ASFC_StepElementType = "ASFC_StepElementType"; + + /// + /// Gets the ASFC_StopElementType L5X name value. + /// + public const string ASFC_StopElementType = "ASFC_StopElementType"; + + /// + /// Gets the ASFC_TransitionElementType L5X name value. + /// + public const string ASFC_TransitionElementType = "ASFC_TransitionElementType"; + + /// + /// Gets the AssocTag1 L5X name value. + /// + public const string AssocTag1 = "AssocTag1"; + + /// + /// Gets the AssocTag2 L5X name value. + /// + public const string AssocTag2 = "AssocTag2"; + + /// + /// Gets the AssocTag3 L5X name value. + /// + public const string AssocTag3 = "AssocTag3"; + + /// + /// Gets the AssocTag4 L5X name value. + /// + public const string AssocTag4 = "AssocTag4"; + + /// + /// Gets the Attachment L5X name value. + /// + public const string Attachment = "Attachment"; + + /// + /// Gets the AttributeNumber L5X name value. + /// + public const string AttributeNumber = "AttributeNumber"; + + /// + /// Gets the AutoDiagsEnabled L5X name value. + /// + public const string AutoDiagsEnabled = "AutoDiagsEnabled"; + + /// + /// Gets the AutoNegotiateEnabled L5X name value. + /// + public const string AutoNegotiateEnabled = "AutoNegotiateEnabled"; + + /// + /// Gets the AutoSagConfiguration L5X name value. + /// + public const string AutoSagConfiguration = "AutoSagConfiguration"; + + /// + /// Gets the AutoSagSlipIncrement L5X name value. + /// + public const string AutoSagSlipIncrement = "AutoSagSlipIncrement"; + + /// + /// Gets the AutoSagSlipTimeLimit L5X name value. + /// + public const string AutoSagSlipTimeLimit = "AutoSagSlipTimeLimit"; + + /// + /// Gets the AutoSagStart L5X name value. + /// + public const string AutoSagStart = "AutoSagStart"; + + /// + /// Gets the AutoTagUpdate L5X name value. + /// + public const string AutoTagUpdate = "AutoTagUpdate"; + + /// + /// Gets the AutotuneTag L5X name value. + /// + public const string AutotuneTag = "AutotuneTag"; + + /// + /// Gets the AutotuneTagIOI L5X name value. + /// + public const string AutotuneTagIOI = "AutotuneTagIOI"; + + /// + /// Gets the AutotuneTagIOINTT L5X name value. + /// + public const string AutotuneTagIOINTT = "AutotuneTagIOINTT"; + + /// + /// Gets the AutoValueAssignPhaseToStepOnAborted L5X name value. + /// + public const string AutoValueAssignPhaseToStepOnAborted = "AutoValueAssignPhaseToStepOnAborted"; + + /// + /// Gets the AutoValueAssignPhaseToStepOnComplete L5X name value. + /// + public const string AutoValueAssignPhaseToStepOnComplete = "AutoValueAssignPhaseToStepOnComplete"; + + /// + /// Gets the AutoValueAssignPhaseToStepOnStopped L5X name value. + /// + public const string AutoValueAssignPhaseToStepOnStopped = "AutoValueAssignPhaseToStepOnStopped"; + + /// + /// Gets the AutoValueAssignStepToPhase L5X name value. + /// + public const string AutoValueAssignStepToPhase = "AutoValueAssignStepToPhase"; + + /// + /// Gets the AuxFeedbackRatio L5X name value. + /// + public const string AuxFeedbackRatio = "AuxFeedbackRatio"; + + /// + /// Gets the AuxFeedbackResolution L5X name value. + /// + public const string AuxFeedbackResolution = "AuxFeedbackResolution"; + + /// + /// Gets the AuxFeedbackType L5X name value. + /// + public const string AuxFeedbackType = "AuxFeedbackType"; + + /// + /// Gets the AuxFeedbackUnit L5X name value. + /// + public const string AuxFeedbackUnit = "AuxFeedbackUnit"; + + /// + /// Gets the AverageVelocityTimebase L5X name value. + /// + public const string AverageVelocityTimebase = "AverageVelocityTimebase"; + + /// + /// Gets the Axes L5X name value. + /// + public const string Axes = "Axes"; + + /// + /// Gets the AxesUId L5X name value. + /// + public const string AxesUId = "AxesUId"; + + /// + /// Gets the AxisConfiguration L5X name value. + /// + public const string AxisConfiguration = "AxisConfiguration"; + + /// + /// Gets the AxisDataType L5X name value. + /// + public const string AxisDataType = "AxisDataType"; + + /// + /// Gets the AxisID L5X name value. + /// + public const string AxisID = "AxisID"; + + /// + /// Gets the AxisInfoSelect1 L5X name value. + /// + public const string AxisInfoSelect1 = "AxisInfoSelect1"; + + /// + /// Gets the AxisInfoSelect2 L5X name value. + /// + public const string AxisInfoSelect2 = "AxisInfoSelect2"; + + /// + /// Gets the AxisParameters L5X name value. + /// + public const string AxisParameters = "AxisParameters"; + + /// + /// Gets the AxisType L5X name value. + /// + public const string AxisType = "AxisType"; + + /// + /// Gets the AxisUpdateSchedule L5X name value. + /// + public const string AxisUpdateSchedule = "AxisUpdateSchedule"; + + /// + /// Gets the BacklashCompensationWindow L5X name value. + /// + public const string BacklashCompensationWindow = "BacklashCompensationWindow"; + + /// + /// Gets the BacklashReversalOffset L5X name value. + /// + public const string BacklashReversalOffset = "BacklashReversalOffset"; + + /// + /// Gets the BacklashStabilizationWindow L5X name value. + /// + public const string BacklashStabilizationWindow = "BacklashStabilizationWindow"; + + /// + /// Gets the BallScrewLead L5X name value. + /// + public const string BallScrewLead = "BallScrewLead"; + + /// + /// Gets the BaseDescription L5X name value. + /// + public const string BaseDescription = "BaseDescription"; + + /// + /// Gets the BaseEnergyObjectCapabilities L5X name value. + /// + public const string BaseEnergyObjectCapabilities = "BaseEnergyObjectCapabilities"; + + /// + /// Gets the BaseOffset1 L5X name value. + /// + public const string BaseOffset1 = "BaseOffset1"; + + /// + /// Gets the BaseOffset2 L5X name value. + /// + public const string BaseOffset2 = "BaseOffset2"; + + /// + /// Gets the BaseOffset3 L5X name value. + /// + public const string BaseOffset3 = "BaseOffset3"; + + /// + /// Gets the Baud L5X name value. + /// + public const string Baud = "Baud"; + + /// + /// Gets the BaudRate L5X name value. + /// + public const string BaudRate = "BaudRate"; + + /// + /// Gets the BeaconInterval L5X name value. + /// + public const string BeaconInterval = "BeaconInterval"; + + /// + /// Gets the BeaconTimeout L5X name value. + /// + public const string BeaconTimeout = "BeaconTimeout"; + + /// + /// Gets the BEODataType L5X name value. + /// + public const string BEODataType = "BEODataType"; + + /// + /// Gets the BEOParameters L5X name value. + /// + public const string BEOParameters = "BEOParameters"; + + /// + /// Gets the BEOType L5X name value. + /// + public const string BEOType = "BEOType"; + + /// + /// Gets the BitIndex L5X name value. + /// + public const string BitIndex = "BitIndex"; + + /// + /// Gets the BitNumber L5X name value. + /// + public const string BitNumber = "BitNumber"; + + /// + /// Gets the Block L5X name value. + /// + public const string Block = "Block"; + + /// + /// Gets the Body L5X name value. + /// + public const string Body = "Body"; + + /// + /// Gets the BrakeEngageDelayTime L5X name value. + /// + public const string BrakeEngageDelayTime = "BrakeEngageDelayTime"; + + /// + /// Gets the BrakeProveRampTime L5X name value. + /// + public const string BrakeProveRampTime = "BrakeProveRampTime"; + + /// + /// Gets the BrakeReleaseDelayTime L5X name value. + /// + public const string BrakeReleaseDelayTime = "BrakeReleaseDelayTime"; + + /// + /// Gets the BrakeSlipTolerance L5X name value. + /// + public const string BrakeSlipTolerance = "BrakeSlipTolerance"; + + /// + /// Gets the BrakeTestTorque L5X name value. + /// + public const string BrakeTestTorque = "BrakeTestTorque"; + + /// + /// Gets the Branch L5X name value. + /// + public const string Branch = "Branch"; + + /// + /// Gets the BranchFlow L5X name value. + /// + public const string BranchFlow = "BranchFlow"; + + /// + /// Gets the BranchType L5X name value. + /// + public const string BranchType = "BranchType"; + + /// + /// Gets the BreakFrequency L5X name value. + /// + public const string BreakFrequency = "BreakFrequency"; + + /// + /// Gets the BreakVoltage L5X name value. + /// + public const string BreakVoltage = "BreakVoltage"; + + /// + /// Gets the BufferSize L5X name value. + /// + public const string BufferSize = "BufferSize"; + + /// + /// Gets the BumplessReconfigSupported L5X name value. + /// + public const string BumplessReconfigSupported = "BumplessReconfigSupported"; + + /// + /// Gets the Bus L5X name value. + /// + public const string Bus = "Bus"; + + /// + /// Gets the BusLimitActiveCurrentRegulatorKi L5X name value. + /// + public const string BusLimitActiveCurrentRegulatorKi = "BusLimitActiveCurrentRegulatorKi"; + + /// + /// Gets the BusLimitActiveCurrentRegulatorKp L5X name value. + /// + public const string BusLimitActiveCurrentRegulatorKp = "BusLimitActiveCurrentRegulatorKp"; + + /// + /// Gets the BusLimitRegulatorKd L5X name value. + /// + public const string BusLimitRegulatorKd = "BusLimitRegulatorKd"; + + /// + /// Gets the BusLimitRegulatorKp L5X name value. + /// + public const string BusLimitRegulatorKp = "BusLimitRegulatorKp"; + + /// + /// Gets the BusObserverBandwidth L5X name value. + /// + public const string BusObserverBandwidth = "BusObserverBandwidth"; + + /// + /// Gets the BusObserverConfiguration L5X name value. + /// + public const string BusObserverConfiguration = "BusObserverConfiguration"; + + /// + /// Gets the BusObserverIntegratorBandwidth L5X name value. + /// + public const string BusObserverIntegratorBandwidth = "BusObserverIntegratorBandwidth"; + + /// + /// Gets the BusOvervoltageOperationalLimit L5X name value. + /// + public const string BusOvervoltageOperationalLimit = "BusOvervoltageOperationalLimit"; + + /// + /// Gets the BusRegulatorID L5X name value. + /// + public const string BusRegulatorID = "BusRegulatorID"; + + /// + /// Gets the BusRegulatorKi L5X name value. + /// + public const string BusRegulatorKi = "BusRegulatorKi"; + + /// + /// Gets the BusRegulatorKp L5X name value. + /// + public const string BusRegulatorKp = "BusRegulatorKp"; + + /// + /// Gets the BusRegulatorSetPointSource L5X name value. + /// + public const string BusRegulatorSetPointSource = "BusRegulatorSetPointSource"; + + /// + /// Gets the BusRegulatorVoltageLevel L5X name value. + /// + public const string BusRegulatorVoltageLevel = "BusRegulatorVoltageLevel"; + + /// + /// Gets the BusVoltageErrorTolerance L5X name value. + /// + public const string BusVoltageErrorTolerance = "BusVoltageErrorTolerance"; + + /// + /// Gets the BusVoltageErrorToleranceTime L5X name value. + /// + public const string BusVoltageErrorToleranceTime = "BusVoltageErrorToleranceTime"; + + /// + /// Gets the BusVoltageIntegratorBandwidth L5X name value. + /// + public const string BusVoltageIntegratorBandwidth = "BusVoltageIntegratorBandwidth"; + + /// + /// Gets the BusVoltageLoopBandwidth L5X name value. + /// + public const string BusVoltageLoopBandwidth = "BusVoltageLoopBandwidth"; + + /// + /// Gets the BusVoltageRateLimit L5X name value. + /// + public const string BusVoltageRateLimit = "BusVoltageRateLimit"; + + /// + /// Gets the BusVoltageReferenceSource L5X name value. + /// + public const string BusVoltageReferenceSource = "BusVoltageReferenceSource"; + + /// + /// Gets the BusVoltageSetPoint L5X name value. + /// + public const string BusVoltageSetPoint = "BusVoltageSetPoint"; + + /// + /// Gets the ButtonState L5X name value. + /// + public const string ButtonState = "ButtonState"; + + /// + /// Gets the CacheConnections L5X name value. + /// + public const string CacheConnections = "CacheConnections"; + + /// + /// Gets the CanBeNull L5X name value. + /// + public const string CanBeNull = "CanBeNull"; + + /// + /// Gets the CanForce L5X name value. + /// + public const string CanForce = "CanForce"; + + /// + /// Gets the CanUseRPIFromProducer L5X name value. + /// + public const string CanUseRPIFromProducer = "CanUseRPIFromProducer"; + + /// + /// Gets the CaptureSize L5X name value. + /// + public const string CaptureSize = "CaptureSize"; + + /// + /// Gets the CaptureSizeExceeded L5X name value. + /// + public const string CaptureSizeExceeded = "CaptureSizeExceeded"; + + /// + /// Gets the CaptureSizeType L5X name value. + /// + public const string CaptureSizeType = "CaptureSizeType"; + + /// + /// Gets the CaptureSizeUnit L5X name value. + /// + public const string CaptureSizeUnit = "CaptureSizeUnit"; + + /// + /// Gets the CatalogNumber L5X name value. + /// + public const string CatalogNumber = "CatalogNumber"; + + /// + /// Gets the ChangesToDetect L5X name value. + /// + public const string ChangesToDetect = "ChangesToDetect"; + + /// + /// Gets the Channel L5X name value. + /// + public const string Channel = "Channel"; + + /// + /// Gets the ChildProgram L5X name value. + /// + public const string ChildProgram = "ChildProgram"; + + /// + /// Gets the ChildProgramCollectionType L5X name value. + /// + public const string ChildProgramCollectionType = "ChildProgramCollectionType"; + + /// + /// Gets the ChildPrograms L5X name value. + /// + public const string ChildPrograms = "ChildPrograms"; + + /// + /// Gets the ChildProgramType L5X name value. + /// + public const string ChildProgramType = "ChildProgramType"; + + /// + /// Gets the CIPAxisAlarmtLog L5X name value. + /// + public const string CIPAxisAlarmtLog = "CIPAxisAlarmtLog"; + + /// + /// Gets the CIPAxisExceptionAction L5X name value. + /// + public const string CIPAxisExceptionAction = "CIPAxisExceptionAction"; + + /// + /// Gets the CIPAxisExceptionAction2 L5X name value. + /// + public const string CIPAxisExceptionAction2 = "CIPAxisExceptionAction2"; + + /// + /// Gets the CIPAxisExceptionAction2RA L5X name value. + /// + public const string CIPAxisExceptionAction2RA = "CIPAxisExceptionAction2RA"; + + /// + /// Gets the CIPAxisExceptionActionRA L5X name value. + /// + public const string CIPAxisExceptionActionRA = "CIPAxisExceptionActionRA"; + + /// + /// Gets the CIPAxisFaultLog L5X name value. + /// + public const string CIPAxisFaultLog = "CIPAxisFaultLog"; + + /// + /// Gets the CIPControllerGetAttrUpdateBits L5X name value. + /// + public const string CIPControllerGetAttrUpdateBits = "CIPControllerGetAttrUpdateBits"; + + /// + /// Gets the CIPControllerSetAttrUpdateBits L5X name value. + /// + public const string CIPControllerSetAttrUpdateBits = "CIPControllerSetAttrUpdateBits"; + + /// + /// Gets the CIPDriveGetAttrUpdateBits L5X name value. + /// + public const string CIPDriveGetAttrUpdateBits = "CIPDriveGetAttrUpdateBits"; + + /// + /// Gets the CIPDriveSetAttrUpdateBits L5X name value. + /// + public const string CIPDriveSetAttrUpdateBits = "CIPDriveSetAttrUpdateBits"; + + /// + /// Gets the Class L5X name value. + /// + public const string Class = "Class"; + + /// + /// Gets the CoarseUpdatePeriod L5X name value. + /// + public const string CoarseUpdatePeriod = "CoarseUpdatePeriod"; + + /// + /// Gets the CoastingTimeLimit L5X name value. + /// + public const string CoastingTimeLimit = "CoastingTimeLimit"; + + /// + /// Gets the Code L5X name value. + /// + public const string Code = "Code"; + + /// + /// Gets the CollectDataOnlyInRunMode L5X name value. + /// + public const string CollectDataOnlyInRunMode = "CollectDataOnlyInRunMode"; + + /// + /// Gets the Color L5X name value. + /// + public const string Color = "Color"; + + /// + /// Gets the ComDriverId L5X name value. + /// + public const string ComDriverId = "ComDriverId"; + + /// + /// Gets the CommandNotchFilter2Depth L5X name value. + /// + public const string CommandNotchFilter2Depth = "CommandNotchFilter2Depth"; + + /// + /// Gets the CommandNotchFilter2Frequency L5X name value. + /// + public const string CommandNotchFilter2Frequency = "CommandNotchFilter2Frequency"; + + /// + /// Gets the CommandNotchFilter2Gain L5X name value. + /// + public const string CommandNotchFilter2Gain = "CommandNotchFilter2Gain"; + + /// + /// Gets the CommandNotchFilter2Width L5X name value. + /// + public const string CommandNotchFilter2Width = "CommandNotchFilter2Width"; + + /// + /// Gets the CommandNotchFilterDepth L5X name value. + /// + public const string CommandNotchFilterDepth = "CommandNotchFilterDepth"; + + /// + /// Gets the CommandNotchFilterFrequency L5X name value. + /// + public const string CommandNotchFilterFrequency = "CommandNotchFilterFrequency"; + + /// + /// Gets the CommandNotchFilterGain L5X name value. + /// + public const string CommandNotchFilterGain = "CommandNotchFilterGain"; + + /// + /// Gets the CommandNotchFilterWidth L5X name value. + /// + public const string CommandNotchFilterWidth = "CommandNotchFilterWidth"; + + /// + /// Gets the CommandPositionTolerance L5X name value. + /// + public const string CommandPositionTolerance = "CommandPositionTolerance"; + + /// + /// Gets the CommandTorque L5X name value. + /// + public const string CommandTorque = "CommandTorque"; + + /// + /// Gets the CommDriver L5X name value. + /// + public const string CommDriver = "CommDriver"; + + /// + /// Gets the Comment L5X name value. + /// + public const string Comment = "Comment"; + + /// + /// Gets the CommentAdaptorTextType L5X name value. + /// + public const string CommentAdaptorTextType = "CommentAdaptorTextType"; + + /// + /// Gets the CommentAdaptorType L5X name value. + /// + public const string CommentAdaptorType = "CommentAdaptorType"; + + /// + /// Gets the CommentCollectionType L5X name value. + /// + public const string CommentCollectionType = "CommentCollectionType"; + + /// + /// Gets the Comments L5X name value. + /// + public const string Comments = "Comments"; + + /// + /// Gets the CommentWideType L5X name value. + /// + public const string CommentWideType = "CommentWideType"; + + /// + /// Gets the CommMethod L5X name value. + /// + public const string CommMethod = "CommMethod"; + + /// + /// Gets the CommPath L5X name value. + /// + public const string CommPath = "CommPath"; + + /// + /// Gets the CommPortCollectionType L5X name value. + /// + public const string CommPortCollectionType = "CommPortCollectionType"; + + /// + /// Gets the CommPorts L5X name value. + /// + public const string CommPorts = "CommPorts"; + + /// + /// Gets the CommTypeCode L5X name value. + /// + public const string CommTypeCode = "CommTypeCode"; + + /// + /// Gets the Communications L5X name value. + /// + public const string Communications = "Communications"; + + /// + /// Gets the CommunicationsType L5X name value. + /// + public const string CommunicationsType = "CommunicationsType"; + + /// + /// Gets the CommutationOffset L5X name value. + /// + public const string CommutationOffset = "CommutationOffset"; + + /// + /// Gets the CommutationOffsetCompensation L5X name value. + /// + public const string CommutationOffsetCompensation = "CommutationOffsetCompensation"; + + /// + /// Gets the CommutationPolarity L5X name value. + /// + public const string CommutationPolarity = "CommutationPolarity"; + + /// + /// Gets the CommutationSelfSensingCurrent L5X name value. + /// + public const string CommutationSelfSensingCurrent = "CommutationSelfSensingCurrent"; + + /// + /// Gets the CompactSTLines L5X name value. + /// + public const string CompactSTLines = "CompactSTLines"; + + /// + /// Gets the CompleteStateIfNotImpl L5X name value. + /// + public const string CompleteStateIfNotImpl = "CompleteStateIfNotImpl"; + + /// + /// Gets the ComponentIOI L5X name value. + /// + public const string ComponentIOI = "ComponentIOI"; + + /// + /// Gets the Condition L5X name value. + /// + public const string Condition = "Condition"; + + /// + /// Gets the ConditionType L5X name value. + /// + public const string ConditionType = "ConditionType"; + + /// + /// Gets the ConfigData L5X name value. + /// + public const string ConfigData = "ConfigData"; + + /// + /// Gets the ConfigDataType L5X name value. + /// + public const string ConfigDataType = "ConfigDataType"; + + /// + /// Gets the ConfigRevSupported L5X name value. + /// + public const string ConfigRevSupported = "ConfigRevSupported"; + + /// + /// Gets the ConfigScript L5X name value. + /// + public const string ConfigScript = "ConfigScript"; + + /// + /// Gets the ConfigScriptType L5X name value. + /// + public const string ConfigScriptType = "ConfigScriptType"; + + /// + /// Gets the ConfigSize L5X name value. + /// + public const string ConfigSize = "ConfigSize"; + + /// + /// Gets the ConfigTag L5X name value. + /// + public const string ConfigTag = "ConfigTag"; + + /// + /// Gets the ConfigTagType L5X name value. + /// + public const string ConfigTagType = "ConfigTagType"; + + /// + /// Gets the ConfigTagUId L5X name value. + /// + public const string ConfigTagUId = "ConfigTagUId"; + + /// + /// Gets the ConfigType L5X name value. + /// + public const string ConfigType = "ConfigType"; + + /// + /// Gets the ConfigurationProfile L5X name value. + /// + public const string ConfigurationProfile = "ConfigurationProfile"; + + /// + /// Gets the ConfiguredAlarmAdaptorType L5X name value. + /// + public const string ConfiguredAlarmAdaptorType = "ConfiguredAlarmAdaptorType"; + + /// + /// Gets the ConfiguredAlarmCollectionAdaptorType L5X name value. + /// + public const string ConfiguredAlarmCollectionAdaptorType = "ConfiguredAlarmCollectionAdaptorType"; + + /// + /// Gets the ConfigureSafetyIOAlways L5X name value. + /// + public const string ConfigureSafetyIOAlways = "ConfigureSafetyIOAlways"; + + /// + /// Gets the ConnectedFlag L5X name value. + /// + public const string ConnectedFlag = "ConnectedFlag"; + + /// + /// Gets the ConnectedState L5X name value. + /// + public const string ConnectedState = "ConnectedState"; + + /// + /// Gets the Connection L5X name value. + /// + public const string Connection = "Connection"; + + /// + /// Gets the ConnectionLossStoppingAction L5X name value. + /// + public const string ConnectionLossStoppingAction = "ConnectionLossStoppingAction"; + + /// + /// Gets the ConnectionPath L5X name value. + /// + public const string ConnectionPath = "ConnectionPath"; + + /// + /// Gets the Connections L5X name value. + /// + public const string Connections = "Connections"; + + /// + /// Gets the ConnectorOffset L5X name value. + /// + public const string ConnectorOffset = "ConnectorOffset"; + + /// + /// Gets the ConnMultiplier L5X name value. + /// + public const string ConnMultiplier = "ConnMultiplier"; + + /// + /// Gets the Constant L5X name value. + /// + public const string Constant = "Constant"; + + /// + /// Gets the ConsumedEnergyOdometer L5X name value. + /// + public const string ConsumedEnergyOdometer = "ConsumedEnergyOdometer"; + + /// + /// Gets the ConsumeInfo L5X name value. + /// + public const string ConsumeInfo = "ConsumeInfo"; + + /// + /// Gets the ConsumeTagInfoType L5X name value. + /// + public const string ConsumeTagInfoType = "ConsumeTagInfoType"; + + /// + /// Gets the ContainsContext L5X name value. + /// + public const string ContainsContext = "ContainsContext"; + + /// + /// Gets the ContinuousTorqueLimit L5X name value. + /// + public const string ContinuousTorqueLimit = "ContinuousTorqueLimit"; + + /// + /// Gets the Controller L5X name value. + /// + public const string Controller = "Controller"; + + /// + /// Gets the ControllerEditsExist L5X name value. + /// + public const string ControllerEditsExist = "ControllerEditsExist"; + + /// + /// Gets the ControllerLanguage L5X name value. + /// + public const string ControllerLanguage = "ControllerLanguage"; + + /// + /// Gets the ControllerType L5X name value. + /// + public const string ControllerType = "ControllerType"; + + /// + /// Gets the ControlLine L5X name value. + /// + public const string ControlLine = "ControlLine"; + + /// + /// Gets the ControlNetScheduled L5X name value. + /// + public const string ControlNetScheduled = "ControlNetScheduled"; + + /// + /// Gets the ControlNetSignature L5X name value. + /// + public const string ControlNetSignature = "ControlNetSignature"; + + /// + /// Gets the ConversionConstant L5X name value. + /// + public const string ConversionConstant = "ConversionConstant"; + + /// + /// Gets the ConversionRatioDenominator L5X name value. + /// + public const string ConversionRatioDenominator = "ConversionRatioDenominator"; + + /// + /// Gets the ConversionRatioNumerator L5X name value. + /// + public const string ConversionRatioNumerator = "ConversionRatioNumerator"; + + /// + /// Gets the ConverterACInputFrequency L5X name value. + /// + public const string ConverterACInputFrequency = "ConverterACInputFrequency"; + + /// + /// Gets the ConverterACInputPhasing L5X name value. + /// + public const string ConverterACInputPhasing = "ConverterACInputPhasing"; + + /// + /// Gets the ConverterACInputVoltage L5X name value. + /// + public const string ConverterACInputVoltage = "ConverterACInputVoltage"; + + /// + /// Gets the ConverterCapacity L5X name value. + /// + public const string ConverterCapacity = "ConverterCapacity"; + + /// + /// Gets the ConverterConfiguration L5X name value. + /// + public const string ConverterConfiguration = "ConverterConfiguration"; + + /// + /// Gets the ConverterControlMode L5X name value. + /// + public const string ConverterControlMode = "ConverterControlMode"; + + /// + /// Gets the ConverterCurrentIntegratorBandwidth L5X name value. + /// + public const string ConverterCurrentIntegratorBandwidth = "ConverterCurrentIntegratorBandwidth"; + + /// + /// Gets the ConverterCurrentLimitSource L5X name value. + /// + public const string ConverterCurrentLimitSource = "ConverterCurrentLimitSource"; + + /// + /// Gets the ConverterCurrentLoopBandwidth L5X name value. + /// + public const string ConverterCurrentLoopBandwidth = "ConverterCurrentLoopBandwidth"; + + /// + /// Gets the ConverterCurrentLoopBandwidthBase L5X name value. + /// + public const string ConverterCurrentLoopBandwidthBase = "ConverterCurrentLoopBandwidthBase"; + + /// + /// Gets the ConverterCurrentLoopDamping L5X name value. + /// + public const string ConverterCurrentLoopDamping = "ConverterCurrentLoopDamping"; + + /// + /// Gets the ConverterCurrentLoopTuningMethod L5X name value. + /// + public const string ConverterCurrentLoopTuningMethod = "ConverterCurrentLoopTuningMethod"; + + /// + /// Gets the ConverterCurrentVectorLimit L5X name value. + /// + public const string ConverterCurrentVectorLimit = "ConverterCurrentVectorLimit"; + + /// + /// Gets the ConverterDCBusCapacitance L5X name value. + /// + public const string ConverterDCBusCapacitance = "ConverterDCBusCapacitance"; + + /// + /// Gets the ConverterGroundCurrentUserLimit L5X name value. + /// + public const string ConverterGroundCurrentUserLimit = "ConverterGroundCurrentUserLimit"; + + /// + /// Gets the ConverterHeatsinkOvertempUserLimit L5X name value. + /// + public const string ConverterHeatsinkOvertempUserLimit = "ConverterHeatsinkOvertempUserLimit"; + + /// + /// Gets the ConverterInputPhaseLossAction L5X name value. + /// + public const string ConverterInputPhaseLossAction = "ConverterInputPhaseLossAction"; + + /// + /// Gets the ConverterInputPhaseLossTime L5X name value. + /// + public const string ConverterInputPhaseLossTime = "ConverterInputPhaseLossTime"; + + /// + /// Gets the ConverterModelTimeConstant L5X name value. + /// + public const string ConverterModelTimeConstant = "ConverterModelTimeConstant"; + + /// + /// Gets the ConverterModelTimeConstantBase L5X name value. + /// + public const string ConverterModelTimeConstantBase = "ConverterModelTimeConstantBase"; + + /// + /// Gets the ConverterMotoringPowerLimit L5X name value. + /// + public const string ConverterMotoringPowerLimit = "ConverterMotoringPowerLimit"; + + /// + /// Gets the ConverterOverloadAction L5X name value. + /// + public const string ConverterOverloadAction = "ConverterOverloadAction"; + + /// + /// Gets the ConverterOvertemperatureUserLimit L5X name value. + /// + public const string ConverterOvertemperatureUserLimit = "ConverterOvertemperatureUserLimit"; + + /// + /// Gets the ConverterPreChargeOverloadUserLimit L5X name value. + /// + public const string ConverterPreChargeOverloadUserLimit = "ConverterPreChargeOverloadUserLimit"; + + /// + /// Gets the ConverterRatedCurrent L5X name value. + /// + public const string ConverterRatedCurrent = "ConverterRatedCurrent"; + + /// + /// Gets the ConverterRatedPeakCurrent L5X name value. + /// + public const string ConverterRatedPeakCurrent = "ConverterRatedPeakCurrent"; + + /// + /// Gets the ConverterRatedPower L5X name value. + /// + public const string ConverterRatedPower = "ConverterRatedPower"; + + /// + /// Gets the ConverterRatedVoltage L5X name value. + /// + public const string ConverterRatedVoltage = "ConverterRatedVoltage"; + + /// + /// Gets the ConverterRegenerativePowerLimit L5X name value. + /// + public const string ConverterRegenerativePowerLimit = "ConverterRegenerativePowerLimit"; + + /// + /// Gets the ConverterStartupMethod L5X name value. + /// + public const string ConverterStartupMethod = "ConverterStartupMethod"; + + /// + /// Gets the ConverterThermalOverloadUserLimit L5X name value. + /// + public const string ConverterThermalOverloadUserLimit = "ConverterThermalOverloadUserLimit"; + + /// + /// Gets the CoordinateDefinition L5X name value. + /// + public const string CoordinateDefinition = "CoordinateDefinition"; + + /// + /// Gets the CoordinateSystemAutoTagUpdate L5X name value. + /// + public const string CoordinateSystemAutoTagUpdate = "CoordinateSystemAutoTagUpdate"; + + /// + /// Gets the CoordinateSystemDataType L5X name value. + /// + public const string CoordinateSystemDataType = "CoordinateSystemDataType"; + + /// + /// Gets the CoordinateSystemParameters L5X name value. + /// + public const string CoordinateSystemParameters = "CoordinateSystemParameters"; + + /// + /// Gets the CoordinateSystemsUId L5X name value. + /// + public const string CoordinateSystemsUId = "CoordinateSystemsUId"; + + /// + /// Gets the CoordinateSystemType L5X name value. + /// + public const string CoordinateSystemType = "CoordinateSystemType"; + + /// + /// Gets the CoordinationMode L5X name value. + /// + public const string CoordinationMode = "CoordinationMode"; + + /// + /// Gets the CoordinationUnits L5X name value. + /// + public const string CoordinationUnits = "CoordinationUnits"; + + /// + /// Gets the Count L5X name value. + /// + public const string Count = "Count"; + + /// + /// Gets the CreatedBy L5X name value. + /// + public const string CreatedBy = "CreatedBy"; + + /// + /// Gets the CreatedDate L5X name value. + /// + public const string CreatedDate = "CreatedDate"; + + /// + /// Gets the CST L5X name value. + /// + public const string CST = "CST"; + + /// + /// Gets the CSTType L5X name value. + /// + public const string CSTType = "CSTType"; + + /// + /// Gets the CSTUId L5X name value. + /// + public const string CSTUId = "CSTUId"; + + /// + /// Gets the CurrentDisturbance L5X name value. + /// + public const string CurrentDisturbance = "CurrentDisturbance"; + + /// + /// Gets the CurrentError L5X name value. + /// + public const string CurrentError = "CurrentError"; + + /// + /// Gets the CurrentLanguage L5X name value. + /// + public const string CurrentLanguage = "CurrentLanguage"; + + /// + /// Gets the CurrentLimitRegulatorKd L5X name value. + /// + public const string CurrentLimitRegulatorKd = "CurrentLimitRegulatorKd"; + + /// + /// Gets the CurrentLimitRegulatorKi L5X name value. + /// + public const string CurrentLimitRegulatorKi = "CurrentLimitRegulatorKi"; + + /// + /// Gets the CurrentLimitRegulatorKp L5X name value. + /// + public const string CurrentLimitRegulatorKp = "CurrentLimitRegulatorKp"; + + /// + /// Gets the CurrentLoopBandwidth L5X name value. + /// + public const string CurrentLoopBandwidth = "CurrentLoopBandwidth"; + + /// + /// Gets the CurrentLoopBandwidthScalingFactor L5X name value. + /// + public const string CurrentLoopBandwidthScalingFactor = "CurrentLoopBandwidthScalingFactor"; + + /// + /// Gets the CurrentProjectLanguage L5X name value. + /// + public const string CurrentProjectLanguage = "CurrentProjectLanguage"; + + /// + /// Gets the CurrentVectorLimit L5X name value. + /// + public const string CurrentVectorLimit = "CurrentVectorLimit"; + + /// + /// Gets the CustomProperties L5X name value. + /// + public const string CustomProperties = "CustomProperties"; + + /// + /// Gets the CustomPropertiesAdaptorType L5X name value. + /// + public const string CustomPropertiesAdaptorType = "CustomPropertiesAdaptorType"; + + /// + /// Gets the CustomPropertiesCollectionType L5X name value. + /// + public const string CustomPropertiesCollectionType = "CustomPropertiesCollectionType"; + + /// + /// Gets the CustomPropertiesTextType L5X name value. + /// + public const string CustomPropertiesTextType = "CustomPropertiesTextType"; + + /// + /// Gets the CyclicReadUpdateList L5X name value. + /// + public const string CyclicReadUpdateList = "CyclicReadUpdateList"; + + /// + /// Gets the CyclicWriteUpdateList L5X name value. + /// + public const string CyclicWriteUpdateList = "CyclicWriteUpdateList"; + + /// + /// Gets the DampingFactor L5X name value. + /// + public const string DampingFactor = "DampingFactor"; + + /// + /// Gets the Data L5X name value. + /// + public const string Data = "Data"; + + /// + /// Gets the DataArray L5X name value. + /// + public const string DataArray = "DataArray"; + + /// + /// Gets the DataArrayElement L5X name value. + /// + public const string DataArrayElement = "DataArrayElement"; + + /// + /// Gets the DataBits L5X name value. + /// + public const string DataBits = "DataBits"; + + /// + /// Gets the DataCapturesToKeep L5X name value. + /// + public const string DataCapturesToKeep = "DataCapturesToKeep"; + + /// + /// Gets the DataCollectionType L5X name value. + /// + public const string DataCollectionType = "DataCollectionType"; + + /// + /// Gets the DataLog L5X name value. + /// + public const string DataLog = "DataLog"; + + /// + /// Gets the DataLogCapturesExceeded L5X name value. + /// + public const string DataLogCapturesExceeded = "DataLogCapturesExceeded"; + + /// + /// Gets the DataLogCollectionType L5X name value. + /// + public const string DataLogCollectionType = "DataLogCollectionType"; + + /// + /// Gets the DataLogCollectionUId L5X name value. + /// + public const string DataLogCollectionUId = "DataLogCollectionUId"; + + /// + /// Gets the DataLogs L5X name value. + /// + public const string DataLogs = "DataLogs"; + + /// + /// Gets the DataLogSize L5X name value. + /// + public const string DataLogSize = "DataLogSize"; + + /// + /// Gets the DataLogSizeExceeded L5X name value. + /// + public const string DataLogSizeExceeded = "DataLogSizeExceeded"; + + /// + /// Gets the DataLogSizeUnit L5X name value. + /// + public const string DataLogSizeUnit = "DataLogSizeUnit"; + + /// + /// Gets the DataLogTag L5X name value. + /// + public const string DataLogTag = "DataLogTag"; + + /// + /// Gets the DataLogTagCollectionType L5X name value. + /// + public const string DataLogTagCollectionType = "DataLogTagCollectionType"; + + /// + /// Gets the DataLogTags L5X name value. + /// + public const string DataLogTags = "DataLogTags"; + + /// + /// Gets the DataLogTagType L5X name value. + /// + public const string DataLogTagType = "DataLogTagType"; + + /// + /// Gets the DataLogType L5X name value. + /// + public const string DataLogType = "DataLogType"; + + /// + /// Gets the DataStatus L5X name value. + /// + public const string DataStatus = "DataStatus"; + + /// + /// Gets the DataStructure L5X name value. + /// + public const string DataStructure = "DataStructure"; + + /// + /// Gets the DataTablePadPercentage L5X name value. + /// + public const string DataTablePadPercentage = "DataTablePadPercentage"; + + /// + /// Gets the DataType L5X name value. + /// + public const string DataType = "DataType"; + + /// + /// Gets the DatatypeAlarmDefinition L5X name value. + /// + public const string DatatypeAlarmDefinition = "DatatypeAlarmDefinition"; + + /// + /// Gets the DatatypeAlarmDefinitionType L5X name value. + /// + public const string DatatypeAlarmDefinitionType = "DatatypeAlarmDefinitionType"; + + /// + /// Gets the DataTypeCollectionType L5X name value. + /// + public const string DataTypeCollectionType = "DataTypeCollectionType"; + + /// + /// Gets the DataTypeDependencyCollectionType L5X name value. + /// + public const string DataTypeDependencyCollectionType = "DataTypeDependencyCollectionType"; + + /// + /// Gets the DataTypeDependencyType L5X name value. + /// + public const string DataTypeDependencyType = "DataTypeDependencyType"; + + /// + /// Gets the DataTypeMemberType L5X name value. + /// + public const string DataTypeMemberType = "DataTypeMemberType"; + + /// + /// Gets the DataTypes L5X name value. + /// + public const string DataTypes = "DataTypes"; + + /// + /// Gets the DataTypesUId L5X name value. + /// + public const string DataTypesUId = "DataTypesUId"; + + /// + /// Gets the DataTypeType L5X name value. + /// + public const string DataTypeType = "DataTypeType"; + + /// + /// Gets the DataTypeUId L5X name value. + /// + public const string DataTypeUId = "DataTypeUId"; + + /// + /// Gets the DataValue L5X name value. + /// + public const string DataValue = "DataValue"; + + /// + /// Gets the DataValueMember L5X name value. + /// + public const string DataValueMember = "DataValueMember"; + + /// + /// Gets the DataWideType L5X name value. + /// + public const string DataWideType = "DataWideType"; + + /// + /// Gets the DCDWaitDelay L5X name value. + /// + public const string DCDWaitDelay = "DCDWaitDelay"; + + /// + /// Gets the DCInjectionBrakeCurrent L5X name value. + /// + public const string DCInjectionBrakeCurrent = "DCInjectionBrakeCurrent"; + + /// + /// Gets the DCInjectionBrakeTime L5X name value. + /// + public const string DCInjectionBrakeTime = "DCInjectionBrakeTime"; + + /// + /// Gets the DCInjectionCurrentRegulatorKi L5X name value. + /// + public const string DCInjectionCurrentRegulatorKi = "DCInjectionCurrentRegulatorKi"; + + /// + /// Gets the DCInjectionCurrentRegulatorKp L5X name value. + /// + public const string DCInjectionCurrentRegulatorKp = "DCInjectionCurrentRegulatorKp"; + + /// + /// Gets the Deadband L5X name value. + /// + public const string Deadband = "Deadband"; + + /// + /// Gets the DeadbandInv L5X name value. + /// + public const string DeadbandInv = "DeadbandInv"; + + /// + /// Gets the DecelerationLimit L5X name value. + /// + public const string DecelerationLimit = "DecelerationLimit"; + + /// + /// Gets the DecoratedDataElements L5X name value. + /// + public const string DecoratedDataElements = "DecoratedDataElements"; + + /// + /// Gets the DecoratedDataType L5X name value. + /// + public const string DecoratedDataType = "DecoratedDataType"; + + /// + /// Gets the DecoratedDefaultDataType L5X name value. + /// + public const string DecoratedDefaultDataType = "DecoratedDefaultDataType"; + + /// + /// Gets the DefaultData L5X name value. + /// + public const string DefaultData = "DefaultData"; + + /// + /// Gets the DefaultDataWideType L5X name value. + /// + public const string DefaultDataWideType = "DefaultDataWideType"; + + /// + /// Gets the DefaultProjectLanguage L5X name value. + /// + public const string DefaultProjectLanguage = "DefaultProjectLanguage"; + + /// + /// Gets the DefaultRPI L5X name value. + /// + public const string DefaultRPI = "DefaultRPI"; + + /// + /// Gets the DefaultValue L5X name value. + /// + public const string DefaultValue = "DefaultValue"; + + /// + /// Gets the DeleteMode L5X name value. + /// + public const string DeleteMode = "DeleteMode"; + + /// + /// Gets the DeletionProhibited L5X name value. + /// + public const string DeletionProhibited = "DeletionProhibited"; + + /// + /// Gets the Dependencies L5X name value. + /// + public const string Dependencies = "Dependencies"; + + /// + /// Gets the DependenciesWideType L5X name value. + /// + public const string DependenciesWideType = "DependenciesWideType"; + + /// + /// Gets the Dependency L5X name value. + /// + public const string Dependency = "Dependency"; + + /// + /// Gets the DependencyWideType L5X name value. + /// + public const string DependencyWideType = "DependencyWideType"; + + /// + /// Gets the Description L5X name value. + /// + public const string Description = "Description"; + + /// + /// Gets the DescriptionHash L5X name value. + /// + public const string DescriptionHash = "DescriptionHash"; + + /// + /// Gets the DescriptionTextType L5X name value. + /// + public const string DescriptionTextType = "DescriptionTextType"; + + /// + /// Gets the DescriptionType L5X name value. + /// + public const string DescriptionType = "DescriptionType"; + + /// + /// Gets the DescWidth L5X name value. + /// + public const string DescWidth = "DescWidth"; + + /// + /// Gets the DescX L5X name value. + /// + public const string DescX = "DescX"; + + /// + /// Gets the DescY L5X name value. + /// + public const string DescY = "DescY"; + + /// + /// Gets the DestinationTag L5X name value. + /// + public const string DestinationTag = "DestinationTag"; + + /// + /// Gets the DeviceDiagnosticProfile L5X name value. + /// + public const string DeviceDiagnosticProfile = "DeviceDiagnosticProfile"; + + /// + /// Gets the DeviceDiagnosticProfileCollectionType L5X name value. + /// + public const string DeviceDiagnosticProfileCollectionType = "DeviceDiagnosticProfileCollectionType"; + + /// + /// Gets the DeviceDiagnosticProfiles L5X name value. + /// + public const string DeviceDiagnosticProfiles = "DeviceDiagnosticProfiles"; + + /// + /// Gets the DeviceDiagnosticProfileType L5X name value. + /// + public const string DeviceDiagnosticProfileType = "DeviceDiagnosticProfileType"; + + /// + /// Gets the DeviceLevelRingType L5X name value. + /// + public const string DeviceLevelRingType = "DeviceLevelRingType"; + + /// + /// Gets the DeviceObjType L5X name value. + /// + public const string DeviceObjType = "DeviceObjType"; + + /// + /// Gets the DF1 L5X name value. + /// + public const string DF1 = "DF1"; + + /// + /// Gets the DF1DriverType L5X name value. + /// + public const string DF1DriverType = "DF1DriverType"; + + /// + /// Gets the DF1Mode L5X name value. + /// + public const string DF1Mode = "DF1Mode"; + + /// + /// Gets the DHPlusDestinationLink L5X name value. + /// + public const string DHPlusDestinationLink = "DHPlusDestinationLink"; + + /// + /// Gets the DHPlusDestinationNode L5X name value. + /// + public const string DHPlusDestinationNode = "DHPlusDestinationNode"; + + /// + /// Gets the DHPlusSourceLink L5X name value. + /// + public const string DHPlusSourceLink = "DHPlusSourceLink"; + + /// + /// Gets the DiagnosticMessage L5X name value. + /// + public const string DiagnosticMessage = "DiagnosticMessage"; + + /// + /// Gets the DiagnosticMessages L5X name value. + /// + public const string DiagnosticMessages = "DiagnosticMessages"; + + /// + /// Gets the DiagnosticProfileID L5X name value. + /// + public const string DiagnosticProfileID = "DiagnosticProfileID"; + + /// + /// Gets the DiagnosticsMessageAdaptorType L5X name value. + /// + public const string DiagnosticsMessageAdaptorType = "DiagnosticsMessageAdaptorType"; + + /// + /// Gets the DiagnosticsMessageCollectionType L5X name value. + /// + public const string DiagnosticsMessageCollectionType = "DiagnosticsMessageCollectionType"; + + /// + /// Gets the Dimension L5X name value. + /// + public const string Dimension = "Dimension"; + + /// + /// Gets the Dimensions L5X name value. + /// + public const string Dimensions = "Dimensions"; + + /// + /// Gets the DirectDriveRampRate L5X name value. + /// + public const string DirectDriveRampRate = "DirectDriveRampRate"; + + /// + /// Gets the DirectedLink L5X name value. + /// + public const string DirectedLink = "DirectedLink"; + + /// + /// Gets the Direction L5X name value. + /// + public const string Direction = "Direction"; + + /// + /// Gets the DirectionalScalingRatio L5X name value. + /// + public const string DirectionalScalingRatio = "DirectionalScalingRatio"; + + /// + /// Gets the Disabled L5X name value. + /// + public const string Disabled = "Disabled"; + + /// + /// Gets the DisableUpdateOutputs L5X name value. + /// + public const string DisableUpdateOutputs = "DisableUpdateOutputs"; + + /// + /// Gets the DisplayCode L5X name value. + /// + public const string DisplayCode = "DisplayCode"; + + /// + /// Gets the DisplayName L5X name value. + /// + public const string DisplayName = "DisplayName"; + + /// + /// Gets the DisplayText L5X name value. + /// + public const string DisplayText = "DisplayText"; + + /// + /// Gets the DisplayTextType L5X name value. + /// + public const string DisplayTextType = "DisplayTextType"; + + /// + /// Gets the DLL L5X name value. + /// + public const string DLL = "DLL"; + + /// + /// Gets the DomainName L5X name value. + /// + public const string DomainName = "DomainName"; + + /// + /// Gets the DownloadProjectCustomProperties L5X name value. + /// + public const string DownloadProjectCustomProperties = "DownloadProjectCustomProperties"; + + /// + /// Gets the DownloadProjectDocumentationAndExtendedProperties L5X name value. + /// + public const string DownloadProjectDocumentationAndExtendedProperties = + "DownloadProjectDocumentationAndExtendedProperties"; + + /// + /// Gets the DriveEnableInputFaultAction L5X name value. + /// + public const string DriveEnableInputFaultAction = "DriveEnableInputFaultAction"; + + /// + /// Gets the DriveFaultAction L5X name value. + /// + public const string DriveFaultAction = "DriveFaultAction"; + + /// + /// Gets the DriveModelTimeConstant L5X name value. + /// + public const string DriveModelTimeConstant = "DriveModelTimeConstant"; + + /// + /// Gets the DriveModelTimeConstantBase L5X name value. + /// + public const string DriveModelTimeConstantBase = "DriveModelTimeConstantBase"; + + /// + /// Gets the DrivePolarity L5X name value. + /// + public const string DrivePolarity = "DrivePolarity"; + + /// + /// Gets the DriveRatedPeakCurrent L5X name value. + /// + public const string DriveRatedPeakCurrent = "DriveRatedPeakCurrent"; + + /// + /// Gets the DriveRatedVoltage L5X name value. + /// + public const string DriveRatedVoltage = "DriveRatedVoltage"; + + /// + /// Gets the DriverType L5X name value. + /// + public const string DriverType = "DriverType"; + + /// + /// Gets the DrivesADCEnabled L5X name value. + /// + public const string DrivesADCEnabled = "DrivesADCEnabled"; + + /// + /// Gets the DrivesADCMode L5X name value. + /// + public const string DrivesADCMode = "DrivesADCMode"; + + /// + /// Gets the DriveThermalFaultAction L5X name value. + /// + public const string DriveThermalFaultAction = "DriveThermalFaultAction"; + + /// + /// Gets the DriveUnit L5X name value. + /// + public const string DriveUnit = "DriveUnit"; + + /// + /// Gets the DuplexEnabled L5X name value. + /// + public const string DuplexEnabled = "DuplexEnabled"; + + /// + /// Gets the DuplexMode L5X name value. + /// + public const string DuplexMode = "DuplexMode"; + + /// + /// Gets the DuplicateDetection L5X name value. + /// + public const string DuplicateDetection = "DuplicateDetection"; + + /// + /// Gets the DynamicsConfigurationBits L5X name value. + /// + public const string DynamicsConfigurationBits = "DynamicsConfigurationBits"; + + /// + /// Gets the EchoMode L5X name value. + /// + public const string EchoMode = "EchoMode"; + + /// + /// Gets the EditedBy L5X name value. + /// + public const string EditedBy = "EditedBy"; + + /// + /// Gets the EditedDate L5X name value. + /// + public const string EditedDate = "EditedDate"; + + /// + /// Gets the EditID L5X name value. + /// + public const string EditID = "EditID"; + + /// + /// Gets the EditResource L5X name value. + /// + public const string EditResource = "EditResource"; + + /// + /// Gets the EditsExist L5X name value. + /// + public const string EditsExist = "EditsExist"; + + /// + /// Gets the EEODataType L5X name value. + /// + public const string EEODataType = "EEODataType"; + + /// + /// Gets the EEOParameters L5X name value. + /// + public const string EEOParameters = "EEOParameters"; + + /// + /// Gets the EEOType L5X name value. + /// + public const string EEOType = "EEOType"; + + /// + /// Gets the EKey L5X name value. + /// + public const string EKey = "EKey"; + + /// + /// Gets the Element L5X name value. + /// + public const string Element = "Element"; + + /// + /// Gets the EmbeddedLanguageBlockType L5X name value. + /// + public const string EmbeddedLanguageBlockType = "EmbeddedLanguageBlockType"; + + /// + /// Gets the EmbeddedResponseEnable L5X name value. + /// + public const string EmbeddedResponseEnable = "EmbeddedResponseEnable"; + + /// + /// Gets the Enabled L5X name value. + /// + public const string Enabled = "Enabled"; + + /// + /// Gets the EnableDataLoggingOnDownload L5X name value. + /// + public const string EnableDataLoggingOnDownload = "EnableDataLoggingOnDownload"; + + /// + /// Gets the EnableIn L5X name value. + /// + public const string EnableIn = "EnableIn"; + + /// + /// Gets the EnableOut L5X name value. + /// + public const string EnableOut = "EnableOut"; + + /// + /// Gets the EnableStoreFwd L5X name value. + /// + public const string EnableStoreFwd = "EnableStoreFwd"; + + /// + /// Gets the EnableTimeout L5X name value. + /// + public const string EnableTimeout = "EnableTimeout"; + + /// + /// Gets the EncodedData L5X name value. + /// + public const string EncodedData = "EncodedData"; + + /// + /// Gets the EncodedRoutineType L5X name value. + /// + public const string EncodedRoutineType = "EncodedRoutineType"; + + /// + /// Gets the EncodedSourceKey L5X name value. + /// + public const string EncodedSourceKey = "EncodedSourceKey"; + + /// + /// Gets the EncodedType L5X name value. + /// + public const string EncodedType = "EncodedType"; + + /// + /// Gets the EncodedUDIDefinitionType L5X name value. + /// + public const string EncodedUDIDefinitionType = "EncodedUDIDefinitionType"; + + /// + /// Gets the EncodedWideType L5X name value. + /// + public const string EncodedWideType = "EncodedWideType"; + + /// + /// Gets the EncryptedAOIContent L5X name value. + /// + public const string EncryptedAOIContent = "EncryptedAOIContent"; + + /// + /// Gets the EncryptedContent L5X name value. + /// + public const string EncryptedContent = "EncryptedContent"; + + /// + /// Gets the EncryptedContentType L5X name value. + /// + public const string EncryptedContentType = "EncryptedContentType"; + + /// + /// Gets the EncryptedSegments L5X name value. + /// + public const string EncryptedSegments = "EncryptedSegments"; + + /// + /// Gets the EncryptedType L5X name value. + /// + public const string EncryptedType = "EncryptedType"; + + /// + /// Gets the EncryptionConfig L5X name value. + /// + public const string EncryptionConfig = "EncryptionConfig"; + + /// + /// Gets the EncryptionInfo L5X name value. + /// + public const string EncryptionInfo = "EncryptionInfo"; + + /// + /// Gets the EncryptionInfoType L5X name value. + /// + public const string EncryptionInfoType = "EncryptionInfoType"; + + /// + /// Gets the EncryptionKey L5X name value. + /// + public const string EncryptionKey = "EncryptionKey"; + + /// + /// Gets the EncryptionKeyType L5X name value. + /// + public const string EncryptionKeyType = "EncryptionKeyType"; + + /// + /// Gets the EndEffectorOffset1 L5X name value. + /// + public const string EndEffectorOffset1 = "EndEffectorOffset1"; + + /// + /// Gets the EndEffectorOffset2 L5X name value. + /// + public const string EndEffectorOffset2 = "EndEffectorOffset2"; + + /// + /// Gets the EndEffectorOffset3 L5X name value. + /// + public const string EndEffectorOffset3 = "EndEffectorOffset3"; + + /// + /// Gets the EndPoint1 L5X name value. + /// + public const string EndPoint1 = "EndPoint1"; + + /// + /// Gets the EndPoint2 L5X name value. + /// + public const string EndPoint2 = "EndPoint2"; + + /// + /// Gets the EnergyAccuracy L5X name value. + /// + public const string EnergyAccuracy = "EnergyAccuracy"; + + /// + /// Gets the EnergyResourceType L5X name value. + /// + public const string EnergyResourceType = "EnergyResourceType"; + + /// + /// Gets the EnergyTransferRate L5X name value. + /// + public const string EnergyTransferRate = "EnergyTransferRate"; + + /// + /// Gets the EngineeringUnit L5X name value. + /// + public const string EngineeringUnit = "EngineeringUnit"; + + /// + /// Gets the EngineeringUnitCollectionType L5X name value. + /// + public const string EngineeringUnitCollectionType = "EngineeringUnitCollectionType"; + + /// + /// Gets the EngineeringUnits L5X name value. + /// + public const string EngineeringUnits = "EngineeringUnits"; + + /// + /// Gets the EngineeringUnitTextType L5X name value. + /// + public const string EngineeringUnitTextType = "EngineeringUnitTextType"; + + /// + /// Gets the EngineeringUnitType L5X name value. + /// + public const string EngineeringUnitType = "EngineeringUnitType"; + + /// + /// Gets the EngUnits L5X name value. + /// + public const string EngUnits = "EngUnits"; + + /// + /// Gets the ENQTransmitLimit L5X name value. + /// + public const string ENQTransmitLimit = "ENQTransmitLimit"; + + /// + /// Gets the EntryPoint L5X name value. + /// + public const string EntryPoint = "EntryPoint"; + + /// + /// Gets the EOTSuppression L5X name value. + /// + public const string EOTSuppression = "EOTSuppression"; + + /// + /// Gets the EquipmentId L5X name value. + /// + public const string EquipmentId = "EquipmentId"; + + /// + /// Gets the ErrorDetection L5X name value. + /// + public const string ErrorDetection = "ErrorDetection"; + + /// + /// Gets the EtherNetIPMode L5X name value. + /// + public const string EtherNetIPMode = "EtherNetIPMode"; + + /// + /// Gets the EthernetLinkCollectionType L5X name value. + /// + public const string EthernetLinkCollectionType = "EthernetLinkCollectionType"; + + /// + /// Gets the EthernetLinkType L5X name value. + /// + public const string EthernetLinkType = "EthernetLinkType"; + + /// + /// Gets the EthernetNetwork L5X name value. + /// + public const string EthernetNetwork = "EthernetNetwork"; + + /// + /// Gets the EthernetPort L5X name value. + /// + public const string EthernetPort = "EthernetPort"; + + /// + /// Gets the EthernetPorts L5X name value. + /// + public const string EthernetPorts = "EthernetPorts"; + + /// + /// Gets the EvaluationPeriod L5X name value. + /// + public const string EvaluationPeriod = "EvaluationPeriod"; + + /// + /// Gets the EventID L5X name value. + /// + public const string EventID = "EventID"; + + /// + /// Gets the EventInfo L5X name value. + /// + public const string EventInfo = "EventInfo"; + + /// + /// Gets the EventTag L5X name value. + /// + public const string EventTag = "EventTag"; + + /// + /// Gets the EventTrigger L5X name value. + /// + public const string EventTrigger = "EventTrigger"; + + /// + /// Gets the ExcludeFromAggregation L5X name value. + /// + public const string ExcludeFromAggregation = "ExcludeFromAggregation"; + + /// + /// Gets the ExecuteEnableInFalse L5X name value. + /// + public const string ExecuteEnableInFalse = "ExecuteEnableInFalse"; + + /// + /// Gets the ExecutePostscan L5X name value. + /// + public const string ExecutePostscan = "ExecutePostscan"; + + /// + /// Gets the ExecutePrescan L5X name value. + /// + public const string ExecutePrescan = "ExecutePrescan"; + + /// + /// Gets the ExecutingTaskName L5X name value. + /// + public const string ExecutingTaskName = "ExecutingTaskName"; + + /// + /// Gets the ExportDate L5X name value. + /// + public const string ExportDate = "ExportDate"; + + /// + /// Gets the ExportOptions L5X name value. + /// + public const string ExportOptions = "ExportOptions"; + + /// + /// Gets the Expression L5X name value. + /// + public const string Expression = "Expression"; + + /// + /// Gets the ExtDeviceObjType L5X name value. + /// + public const string ExtDeviceObjType = "ExtDeviceObjType"; + + /// + /// Gets the ExtendedDataStatus L5X name value. + /// + public const string ExtendedDataStatus = "ExtendedDataStatus"; + + /// + /// Gets the ExtendedProperties L5X name value. + /// + public const string ExtendedProperties = "ExtendedProperties"; + + /// + /// Gets the ExternalAccess L5X name value. + /// + public const string ExternalAccess = "ExternalAccess"; + + /// + /// Gets the ExternalContent L5X name value. + /// + public const string ExternalContent = "ExternalContent"; + + /// + /// Gets the ExternalContentType L5X name value. + /// + public const string ExternalContentType = "ExternalContentType"; + + /// + /// Gets the ExternalDCBusCapacitance L5X name value. + /// + public const string ExternalDCBusCapacitance = "ExternalDCBusCapacitance"; + + /// + /// Gets the ExternalDriveType L5X name value. + /// + public const string ExternalDriveType = "ExternalDriveType"; + + /// + /// Gets the ExternalRequestAction L5X name value. + /// + public const string ExternalRequestAction = "ExternalRequestAction"; + + /// + /// Gets the ExternalRoutineXml L5X name value. + /// + public const string ExternalRoutineXml = "ExternalRoutineXml"; + + /// + /// Gets the Family L5X name value. + /// + public const string Family = "Family"; + + /// + /// Gets the FaultCode L5X name value. + /// + public const string FaultCode = "FaultCode"; + + /// + /// Gets the FaultConfigurationBits L5X name value. + /// + public const string FaultConfigurationBits = "FaultConfigurationBits"; + + /// + /// Gets the Faulted L5X name value. + /// + public const string Faulted = "Faulted"; + + /// + /// Gets the FaultInfo L5X name value. + /// + public const string FaultInfo = "FaultInfo"; + + /// + /// Gets the FaultLogType L5X name value. + /// + public const string FaultLogType = "FaultLogType"; + + /// + /// Gets the FaultRoutineName L5X name value. + /// + public const string FaultRoutineName = "FaultRoutineName"; + + /// + /// Gets the FaultString L5X name value. + /// + public const string FaultString = "FaultString"; + + /// + /// Gets the FBD_FeedbackWireType L5X name value. + /// + public const string FBD_FeedbackWireType = "FBD_FeedbackWireType"; + + /// + /// Gets the FBD_GsvBlockType L5X name value. + /// + public const string FBD_GsvBlockType = "FBD_GsvBlockType"; + + /// + /// Gets the FBD_InputRefType L5X name value. + /// + public const string FBD_InputRefType = "FBD_InputRefType"; + + /// + /// Gets the FBD_InputWireConnectorType L5X name value. + /// + public const string FBD_InputWireConnectorType = "FBD_InputWireConnectorType"; + + /// + /// Gets the FBD_JSRType L5X name value. + /// + public const string FBD_JSRType = "FBD_JSRType"; + + /// + /// Gets the FBD_OutputRefType L5X name value. + /// + public const string FBD_OutputRefType = "FBD_OutputRefType"; + + /// + /// Gets the FBD_OutputWireConnectorType L5X name value. + /// + public const string FBD_OutputWireConnectorType = "FBD_OutputWireConnectorType"; + + /// + /// Gets the FBD_RETType L5X name value. + /// + public const string FBD_RETType = "FBD_RETType"; + + /// + /// Gets the FBD_SBRType L5X name value. + /// + public const string FBD_SBRType = "FBD_SBRType"; + + /// + /// Gets the FBD_SpecialArrayType L5X name value. + /// + public const string FBD_SpecialArrayType = "FBD_SpecialArrayType"; + + /// + /// Gets the FBD_SsvBlockType L5X name value. + /// + public const string FBD_SsvBlockType = "FBD_SsvBlockType"; + + /// + /// Gets the FBD_UDIArgumentType L5X name value. + /// + public const string FBD_UDIArgumentType = "FBD_UDIArgumentType"; + + /// + /// Gets the FBD_UDIBlockType L5X name value. + /// + public const string FBD_UDIBlockType = "FBD_UDIBlockType"; + + /// + /// Gets the FBD_WireType L5X name value. + /// + public const string FBD_WireType = "FBD_WireType"; + + /// + /// Gets the FBDContent L5X name value. + /// + public const string FBDContent = "FBDContent"; + + /// + /// Gets the FBDContentType L5X name value. + /// + public const string FBDContentType = "FBDContentType"; + + /// + /// Gets the Feedback1AccelFilterBandwidth L5X name value. + /// + public const string Feedback1AccelFilterBandwidth = "Feedback1AccelFilterBandwidth"; + + /// + /// Gets the Feedback1AccelFilterTaps L5X name value. + /// + public const string Feedback1AccelFilterTaps = "Feedback1AccelFilterTaps"; + + /// + /// Gets the Feedback1BatteryAbsolute L5X name value. + /// + public const string Feedback1BatteryAbsolute = "Feedback1BatteryAbsolute"; + + /// + /// Gets the Feedback1CalibrationOffset L5X name value. + /// + public const string Feedback1CalibrationOffset = "Feedback1CalibrationOffset"; + + /// + /// Gets the Feedback1CycleInterpolation L5X name value. + /// + public const string Feedback1CycleInterpolation = "Feedback1CycleInterpolation"; + + /// + /// Gets the Feedback1CycleResolution L5X name value. + /// + public const string Feedback1CycleResolution = "Feedback1CycleResolution"; + + /// + /// Gets the Feedback1DataCode L5X name value. + /// + public const string Feedback1DataCode = "Feedback1DataCode"; + + /// + /// Gets the Feedback1DataLength L5X name value. + /// + public const string Feedback1DataLength = "Feedback1DataLength"; + + /// + /// Gets the Feedback1Length L5X name value. + /// + public const string Feedback1Length = "Feedback1Length"; + + /// + /// Gets the Feedback1LossAction L5X name value. + /// + public const string Feedback1LossAction = "Feedback1LossAction"; + + /// + /// Gets the Feedback1Polarity L5X name value. + /// + public const string Feedback1Polarity = "Feedback1Polarity"; + + /// + /// Gets the Feedback1ResolverCableBalance L5X name value. + /// + public const string Feedback1ResolverCableBalance = "Feedback1ResolverCableBalance"; + + /// + /// Gets the Feedback1ResolverExcitationFrequency L5X name value. + /// + public const string Feedback1ResolverExcitationFrequency = "Feedback1ResolverExcitationFrequency"; + + /// + /// Gets the Feedback1ResolverExcitationVoltage L5X name value. + /// + public const string Feedback1ResolverExcitationVoltage = "Feedback1ResolverExcitationVoltage"; + + /// + /// Gets the Feedback1ResolverTransformerRatio L5X name value. + /// + public const string Feedback1ResolverTransformerRatio = "Feedback1ResolverTransformerRatio"; + + /// + /// Gets the Feedback1StartupMethod L5X name value. + /// + public const string Feedback1StartupMethod = "Feedback1StartupMethod"; + + /// + /// Gets the Feedback1Turns L5X name value. + /// + public const string Feedback1Turns = "Feedback1Turns"; + + /// + /// Gets the Feedback1Type L5X name value. + /// + public const string Feedback1Type = "Feedback1Type"; + + /// + /// Gets the Feedback1Unit L5X name value. + /// + public const string Feedback1Unit = "Feedback1Unit"; + + /// + /// Gets the Feedback1VelocityFilterBandwidth L5X name value. + /// + public const string Feedback1VelocityFilterBandwidth = "Feedback1VelocityFilterBandwidth"; + + /// + /// Gets the Feedback1VelocityFilterTaps L5X name value. + /// + public const string Feedback1VelocityFilterTaps = "Feedback1VelocityFilterTaps"; + + /// + /// Gets the Feedback2AccelFilterBandwidth L5X name value. + /// + public const string Feedback2AccelFilterBandwidth = "Feedback2AccelFilterBandwidth"; + + /// + /// Gets the Feedback2AccelFilterTaps L5X name value. + /// + public const string Feedback2AccelFilterTaps = "Feedback2AccelFilterTaps"; + + /// + /// Gets the Feedback2BatteryAbsolute L5X name value. + /// + public const string Feedback2BatteryAbsolute = "Feedback2BatteryAbsolute"; + + /// + /// Gets the Feedback2CalibrationOffset L5X name value. + /// + public const string Feedback2CalibrationOffset = "Feedback2CalibrationOffset"; + + /// + /// Gets the Feedback2CycleInterpolation L5X name value. + /// + public const string Feedback2CycleInterpolation = "Feedback2CycleInterpolation"; + + /// + /// Gets the Feedback2CycleResolution L5X name value. + /// + public const string Feedback2CycleResolution = "Feedback2CycleResolution"; + + /// + /// Gets the Feedback2DataCode L5X name value. + /// + public const string Feedback2DataCode = "Feedback2DataCode"; + + /// + /// Gets the Feedback2DataLength L5X name value. + /// + public const string Feedback2DataLength = "Feedback2DataLength"; + + /// + /// Gets the Feedback2Length L5X name value. + /// + public const string Feedback2Length = "Feedback2Length"; + + /// + /// Gets the Feedback2LossAction L5X name value. + /// + public const string Feedback2LossAction = "Feedback2LossAction"; + + /// + /// Gets the Feedback2Polarity L5X name value. + /// + public const string Feedback2Polarity = "Feedback2Polarity"; + + /// + /// Gets the Feedback2ResolverCableBalance L5X name value. + /// + public const string Feedback2ResolverCableBalance = "Feedback2ResolverCableBalance"; + + /// + /// Gets the Feedback2ResolverExcitationFrequency L5X name value. + /// + public const string Feedback2ResolverExcitationFrequency = "Feedback2ResolverExcitationFrequency"; + + /// + /// Gets the Feedback2ResolverExcitationVoltage L5X name value. + /// + public const string Feedback2ResolverExcitationVoltage = "Feedback2ResolverExcitationVoltage"; + + /// + /// Gets the Feedback2ResolverTransformerRatio L5X name value. + /// + public const string Feedback2ResolverTransformerRatio = "Feedback2ResolverTransformerRatio"; + + /// + /// Gets the Feedback2StartupMethod L5X name value. + /// + public const string Feedback2StartupMethod = "Feedback2StartupMethod"; + + /// + /// Gets the Feedback2Turns L5X name value. + /// + public const string Feedback2Turns = "Feedback2Turns"; + + /// + /// Gets the Feedback2Type L5X name value. + /// + public const string Feedback2Type = "Feedback2Type"; + + /// + /// Gets the Feedback2Unit L5X name value. + /// + public const string Feedback2Unit = "Feedback2Unit"; + + /// + /// Gets the Feedback2VelocityFilterBandwidth L5X name value. + /// + public const string Feedback2VelocityFilterBandwidth = "Feedback2VelocityFilterBandwidth"; + + /// + /// Gets the Feedback2VelocityFilterTaps L5X name value. + /// + public const string Feedback2VelocityFilterTaps = "Feedback2VelocityFilterTaps"; + + /// + /// Gets the FeedbackCommutationAligned L5X name value. + /// + public const string FeedbackCommutationAligned = "FeedbackCommutationAligned"; + + /// + /// Gets the FeedbackConfiguration L5X name value. + /// + public const string FeedbackConfiguration = "FeedbackConfiguration"; + + /// + /// Gets the FeedbackDataLossUserLimit L5X name value. + /// + public const string FeedbackDataLossUserLimit = "FeedbackDataLossUserLimit"; + + /// + /// Gets the FeedbackFaultAction L5X name value. + /// + public const string FeedbackFaultAction = "FeedbackFaultAction"; + + /// + /// Gets the FeedbackMasterSelect L5X name value. + /// + public const string FeedbackMasterSelect = "FeedbackMasterSelect"; + + /// + /// Gets the FeedbackNoiseFaultAction L5X name value. + /// + public const string FeedbackNoiseFaultAction = "FeedbackNoiseFaultAction"; + + /// + /// Gets the FeedbackNoiseUserLimit L5X name value. + /// + public const string FeedbackNoiseUserLimit = "FeedbackNoiseUserLimit"; + + /// + /// Gets the FeedbackSignalLossUserLimit L5X name value. + /// + public const string FeedbackSignalLossUserLimit = "FeedbackSignalLossUserLimit"; + + /// + /// Gets the FeedbackUnitRatio L5X name value. + /// + public const string FeedbackUnitRatio = "FeedbackUnitRatio"; + + /// + /// Gets the FeedbackWire L5X name value. + /// + public const string FeedbackWire = "FeedbackWire"; + + /// + /// Gets the FilePath L5X name value. + /// + public const string FilePath = "FilePath"; + + /// + /// Gets the FluxAdaptionEnable L5X name value. + /// + public const string FluxAdaptionEnable = "FluxAdaptionEnable"; + + /// + /// Gets the FluxAdaptionRegulatorKi L5X name value. + /// + public const string FluxAdaptionRegulatorKi = "FluxAdaptionRegulatorKi"; + + /// + /// Gets the FluxAdaptionRegulatorKp L5X name value. + /// + public const string FluxAdaptionRegulatorKp = "FluxAdaptionRegulatorKp"; + + /// + /// Gets the FluxBrakingEnable L5X name value. + /// + public const string FluxBrakingEnable = "FluxBrakingEnable"; + + /// + /// Gets the FluxBrakingRegulatorKi L5X name value. + /// + public const string FluxBrakingRegulatorKi = "FluxBrakingRegulatorKi"; + + /// + /// Gets the FluxBrakingRegulatorKp L5X name value. + /// + public const string FluxBrakingRegulatorKp = "FluxBrakingRegulatorKp"; + + /// + /// Gets the FluxBrakingVoltageLimit L5X name value. + /// + public const string FluxBrakingVoltageLimit = "FluxBrakingVoltageLimit"; + + /// + /// Gets the FluxCurrentReference L5X name value. + /// + public const string FluxCurrentReference = "FluxCurrentReference"; + + /// + /// Gets the FluxDownRegulatorKi L5X name value. + /// + public const string FluxDownRegulatorKi = "FluxDownRegulatorKi"; + + /// + /// Gets the FluxDownRegulatorKp L5X name value. + /// + public const string FluxDownRegulatorKp = "FluxDownRegulatorKp"; + + /// + /// Gets the FluxIntegralTimeConstant L5X name value. + /// + public const string FluxIntegralTimeConstant = "FluxIntegralTimeConstant"; + + /// + /// Gets the FluxLoopBandwidth L5X name value. + /// + public const string FluxLoopBandwidth = "FluxLoopBandwidth"; + + /// + /// Gets the FluxUpControl L5X name value. + /// + public const string FluxUpControl = "FluxUpControl"; + + /// + /// Gets the FluxUpTime L5X name value. + /// + public const string FluxUpTime = "FluxUpTime"; + + /// + /// Gets the FluxVectorFrequencyRegulatorKi L5X name value. + /// + public const string FluxVectorFrequencyRegulatorKi = "FluxVectorFrequencyRegulatorKi"; + + /// + /// Gets the FluxVectorFrequencyRegulatorKp L5X name value. + /// + public const string FluxVectorFrequencyRegulatorKp = "FluxVectorFrequencyRegulatorKp"; + + /// + /// Gets the FlyingStartCEMFBrakeLevel L5X name value. + /// + public const string FlyingStartCEMFBrakeLevel = "FlyingStartCEMFBrakeLevel"; + + /// + /// Gets the FlyingStartCEMFBrakeTime L5X name value. + /// + public const string FlyingStartCEMFBrakeTime = "FlyingStartCEMFBrakeTime"; + + /// + /// Gets the FlyingStartCEMFCurrentRegKi L5X name value. + /// + public const string FlyingStartCEMFCurrentRegKi = "FlyingStartCEMFCurrentRegKi"; + + /// + /// Gets the FlyingStartCEMFCurrentRegKp L5X name value. + /// + public const string FlyingStartCEMFCurrentRegKp = "FlyingStartCEMFCurrentRegKp"; + + /// + /// Gets the FlyingStartCEMFExcitationRegKi L5X name value. + /// + public const string FlyingStartCEMFExcitationRegKi = "FlyingStartCEMFExcitationRegKi"; + + /// + /// Gets the FlyingStartCEMFExcitationRegKp L5X name value. + /// + public const string FlyingStartCEMFExcitationRegKp = "FlyingStartCEMFExcitationRegKp"; + + /// + /// Gets the FlyingStartCEMFReconnectDelay L5X name value. + /// + public const string FlyingStartCEMFReconnectDelay = "FlyingStartCEMFReconnectDelay"; + + /// + /// Gets the FlyingStartCEMFVelocityRegKi L5X name value. + /// + public const string FlyingStartCEMFVelocityRegKi = "FlyingStartCEMFVelocityRegKi"; + + /// + /// Gets the FlyingStartCEMFVelocityRegKp L5X name value. + /// + public const string FlyingStartCEMFVelocityRegKp = "FlyingStartCEMFVelocityRegKp"; + + /// + /// Gets the FlyingStartCEMFZeroSpeedThreshold L5X name value. + /// + public const string FlyingStartCEMFZeroSpeedThreshold = "FlyingStartCEMFZeroSpeedThreshold"; + + /// + /// Gets the FlyingStartEnable L5X name value. + /// + public const string FlyingStartEnable = "FlyingStartEnable"; + + /// + /// Gets the FlyingStartMethod L5X name value. + /// + public const string FlyingStartMethod = "FlyingStartMethod"; + + /// + /// Gets the FlyingStartSweepBrakeLevel L5X name value. + /// + public const string FlyingStartSweepBrakeLevel = "FlyingStartSweepBrakeLevel"; + + /// + /// Gets the FlyingStartSweepBrakeTime L5X name value. + /// + public const string FlyingStartSweepBrakeTime = "FlyingStartSweepBrakeTime"; + + /// + /// Gets the FlyingStartSweepInitialVoltageRegKi L5X name value. + /// + public const string FlyingStartSweepInitialVoltageRegKi = "FlyingStartSweepInitialVoltageRegKi"; + + /// + /// Gets the FlyingStartSweepInitialVoltageRegKp L5X name value. + /// + public const string FlyingStartSweepInitialVoltageRegKp = "FlyingStartSweepInitialVoltageRegKp"; + + /// + /// Gets the FlyingStartSweepReconnectDelay L5X name value. + /// + public const string FlyingStartSweepReconnectDelay = "FlyingStartSweepReconnectDelay"; + + /// + /// Gets the FlyingStartSweepRecoveryCurrentRegKi L5X name value. + /// + public const string FlyingStartSweepRecoveryCurrentRegKi = "FlyingStartSweepRecoveryCurrentRegKi"; + + /// + /// Gets the FlyingStartSweepSpeedDetectLevel L5X name value. + /// + public const string FlyingStartSweepSpeedDetectLevel = "FlyingStartSweepSpeedDetectLevel"; + + /// + /// Gets the FlyingStartSweepSpeedDetectTime L5X name value. + /// + public const string FlyingStartSweepSpeedDetectTime = "FlyingStartSweepSpeedDetectTime"; + + /// + /// Gets the FlyingStartSweepTime L5X name value. + /// + public const string FlyingStartSweepTime = "FlyingStartSweepTime"; + + /// + /// Gets the FlyingStartSweepVelocityRegKi L5X name value. + /// + public const string FlyingStartSweepVelocityRegKi = "FlyingStartSweepVelocityRegKi"; + + /// + /// Gets the FlyingStartSweepVelocityRegKp L5X name value. + /// + public const string FlyingStartSweepVelocityRegKp = "FlyingStartSweepVelocityRegKp"; + + /// + /// Gets the FlyingStartSweepVHzDCBoostAdjust L5X name value. + /// + public const string FlyingStartSweepVHzDCBoostAdjust = "FlyingStartSweepVHzDCBoostAdjust"; + + /// + /// Gets the FlyingStartSweepVHzRatio L5X name value. + /// + public const string FlyingStartSweepVHzRatio = "FlyingStartSweepVHzRatio"; + + /// + /// Gets the FlyingStartSweepZeroSpeedThreshold L5X name value. + /// + public const string FlyingStartSweepZeroSpeedThreshold = "FlyingStartSweepZeroSpeedThreshold"; + + /// + /// Gets the Force L5X name value. + /// + public const string Force = "Force"; + + /// + /// Gets the ForceData L5X name value. + /// + public const string ForceData = "ForceData"; + + /// + /// Gets the ForceDataWideType L5X name value. + /// + public const string ForceDataWideType = "ForceDataWideType"; + + /// + /// Gets the ForceMemorySaving L5X name value. + /// + public const string ForceMemorySaving = "ForceMemorySaving"; + + /// + /// Gets the ForcesExist L5X name value. + /// + public const string ForcesExist = "ForcesExist"; + + /// + /// Gets the ForceValue L5X name value. + /// + public const string ForceValue = "ForceValue"; + + /// + /// Gets the Format L5X name value. + /// + public const string Format = "Format"; + + /// + /// Gets the FrequencyControlMethod L5X name value. + /// + public const string FrequencyControlMethod = "FrequencyControlMethod"; + + /// + /// Gets the FrictionCompensation L5X name value. + /// + public const string FrictionCompensation = "FrictionCompensation"; + + /// + /// Gets the FrictionCompensationBreakawayTime L5X name value. + /// + public const string FrictionCompensationBreakawayTime = "FrictionCompensationBreakawayTime"; + + /// + /// Gets the FrictionCompensationHysteresis L5X name value. + /// + public const string FrictionCompensationHysteresis = "FrictionCompensationHysteresis"; + + /// + /// Gets the FrictionCompensationMethod L5X name value. + /// + public const string FrictionCompensationMethod = "FrictionCompensationMethod"; + + /// + /// Gets the FrictionCompensationSliding L5X name value. + /// + public const string FrictionCompensationSliding = "FrictionCompensationSliding"; + + /// + /// Gets the FrictionCompensationStartSpeed L5X name value. + /// + public const string FrictionCompensationStartSpeed = "FrictionCompensationStartSpeed"; + + /// + /// Gets the FrictionCompensationStatic L5X name value. + /// + public const string FrictionCompensationStatic = "FrictionCompensationStatic"; + + /// + /// Gets the FrictionCompensationViscous L5X name value. + /// + public const string FrictionCompensationViscous = "FrictionCompensationViscous"; + + /// + /// Gets the FrictionCompensationWindow L5X name value. + /// + public const string FrictionCompensationWindow = "FrictionCompensationWindow"; + + /// + /// Gets the FromID L5X name value. + /// + public const string FromID = "FromID"; + + /// + /// Gets the FromParam L5X name value. + /// + public const string FromParam = "FromParam"; + + /// + /// Gets the FullName L5X name value. + /// + public const string FullName = "FullName"; + + /// + /// Gets the Function L5X name value. + /// + public const string Function = "Function"; + + /// + /// Gets the GainTuningConfigurationBits L5X name value. + /// + public const string GainTuningConfigurationBits = "GainTuningConfigurationBits"; + + /// + /// Gets the Gateway L5X name value. + /// + public const string Gateway = "Gateway"; + + /// + /// Gets the GeneralFaultType L5X name value. + /// + public const string GeneralFaultType = "GeneralFaultType"; + + /// + /// Gets the GeneralStatus L5X name value. + /// + public const string GeneralStatus = "GeneralStatus"; + + /// + /// Gets the GeneratedEnergyOdometer L5X name value. + /// + public const string GeneratedEnergyOdometer = "GeneratedEnergyOdometer"; + + /// + /// Gets the GenerateFaultMember L5X name value. + /// + public const string GenerateFaultMember = "GenerateFaultMember"; + + /// + /// Gets the GenerateSequenceEvents L5X name value. + /// + public const string GenerateSequenceEvents = "GenerateSequenceEvents"; + + /// + /// Gets the Group L5X name value. + /// + public const string Group = "Group"; + + /// + /// Gets the GSV L5X name value. + /// + public const string GSV = "GSV"; + + /// + /// Gets the HAcked L5X name value. + /// + public const string HAcked = "HAcked"; + + /// + /// Gets the HAlarmCount L5X name value. + /// + public const string HAlarmCount = "HAlarmCount"; + + /// + /// Gets the HardOvertravelFaultAction L5X name value. + /// + public const string HardOvertravelFaultAction = "HardOvertravelFaultAction"; + + /// + /// Gets the HardwareStatusType L5X name value. + /// + public const string HardwareStatusType = "HardwareStatusType"; + + /// + /// Gets the HEnabled L5X name value. + /// + public const string HEnabled = "HEnabled"; + + /// + /// Gets the HHAcked L5X name value. + /// + public const string HHAcked = "HHAcked"; + + /// + /// Gets the HHAlarmCount L5X name value. + /// + public const string HHAlarmCount = "HHAlarmCount"; + + /// + /// Gets the HHEnabled L5X name value. + /// + public const string HHEnabled = "HHEnabled"; + + /// + /// Gets the HHInAlarm L5X name value. + /// + public const string HHInAlarm = "HHInAlarm"; + + /// + /// Gets the HHInAlarmTime L5X name value. + /// + public const string HHInAlarmTime = "HHInAlarmTime"; + + /// + /// Gets the HHInAlarmUnack L5X name value. + /// + public const string HHInAlarmUnack = "HHInAlarmUnack"; + + /// + /// Gets the HHLimit L5X name value. + /// + public const string HHLimit = "HHLimit"; + + /// + /// Gets the HHMinDurationEnable L5X name value. + /// + public const string HHMinDurationEnable = "HHMinDurationEnable"; + + /// + /// Gets the HHOperAck L5X name value. + /// + public const string HHOperAck = "HHOperAck"; + + /// + /// Gets the HHOperShelve L5X name value. + /// + public const string HHOperShelve = "HHOperShelve"; + + /// + /// Gets the HHOperUnshelve L5X name value. + /// + public const string HHOperUnshelve = "HHOperUnshelve"; + + /// + /// Gets the HHProgAck L5X name value. + /// + public const string HHProgAck = "HHProgAck"; + + /// + /// Gets the HHSeverity L5X name value. + /// + public const string HHSeverity = "HHSeverity"; + + /// + /// Gets the HHShelved L5X name value. + /// + public const string HHShelved = "HHShelved"; + + /// + /// Gets the Hidden L5X name value. + /// + public const string Hidden = "Hidden"; + + /// + /// Gets the HideDesc L5X name value. + /// + public const string HideDesc = "HideDesc"; + + /// + /// Gets the HInAlarm L5X name value. + /// + public const string HInAlarm = "HInAlarm"; + + /// + /// Gets the HInAlarmTime L5X name value. + /// + public const string HInAlarmTime = "HInAlarmTime"; + + /// + /// Gets the HInAlarmUnack L5X name value. + /// + public const string HInAlarmUnack = "HInAlarmUnack"; + + /// + /// Gets the HistoryEntry L5X name value. + /// + public const string HistoryEntry = "HistoryEntry"; + + /// + /// Gets the HistoryEntryType L5X name value. + /// + public const string HistoryEntryType = "HistoryEntryType"; + + /// + /// Gets the HLimit L5X name value. + /// + public const string HLimit = "HLimit"; + + /// + /// Gets the HMIBCDataType L5X name value. + /// + public const string HMIBCDataType = "HMIBCDataType"; + + /// + /// Gets the HMIBCParameters L5X name value. + /// + public const string HMIBCParameters = "HMIBCParameters"; + + /// + /// Gets the HMIBCType L5X name value. + /// + public const string HMIBCType = "HMIBCType"; + + /// + /// Gets the HMICmd L5X name value. + /// + public const string HMICmd = "HMICmd"; + + /// + /// Gets the HMIGroup L5X name value. + /// + public const string HMIGroup = "HMIGroup"; + + /// + /// Gets the HMinDurationEnable L5X name value. + /// + public const string HMinDurationEnable = "HMinDurationEnable"; + + /// + /// Gets the HomeConfigurationBits L5X name value. + /// + public const string HomeConfigurationBits = "HomeConfigurationBits"; + + /// + /// Gets the HomeDirection L5X name value. + /// + public const string HomeDirection = "HomeDirection"; + + /// + /// Gets the HomeMode L5X name value. + /// + public const string HomeMode = "HomeMode"; + + /// + /// Gets the HomeOffset L5X name value. + /// + public const string HomeOffset = "HomeOffset"; + + /// + /// Gets the HomePosition L5X name value. + /// + public const string HomePosition = "HomePosition"; + + /// + /// Gets the HomeReturnSpeed L5X name value. + /// + public const string HomeReturnSpeed = "HomeReturnSpeed"; + + /// + /// Gets the HomeSequence L5X name value. + /// + public const string HomeSequence = "HomeSequence"; + + /// + /// Gets the HomeSpeed L5X name value. + /// + public const string HomeSpeed = "HomeSpeed"; + + /// + /// Gets the HomeTorqueLevel L5X name value. + /// + public const string HomeTorqueLevel = "HomeTorqueLevel"; + + /// + /// Gets the HookupTestDistance L5X name value. + /// + public const string HookupTestDistance = "HookupTestDistance"; + + /// + /// Gets the HookupTestFeedbackChannel L5X name value. + /// + public const string HookupTestFeedbackChannel = "HookupTestFeedbackChannel"; + + /// + /// Gets the HookupTestSpeed L5X name value. + /// + public const string HookupTestSpeed = "HookupTestSpeed"; + + /// + /// Gets the HookupTestTime L5X name value. + /// + public const string HookupTestTime = "HookupTestTime"; + + /// + /// Gets the HOperAck L5X name value. + /// + public const string HOperAck = "HOperAck"; + + /// + /// Gets the HOperShelve L5X name value. + /// + public const string HOperShelve = "HOperShelve"; + + /// + /// Gets the HOperUnshelve L5X name value. + /// + public const string HOperUnshelve = "HOperUnshelve"; + + /// + /// Gets the HostName L5X name value. + /// + public const string HostName = "HostName"; + + /// + /// Gets the HProgAck L5X name value. + /// + public const string HProgAck = "HProgAck"; + + /// + /// Gets the HSeverity L5X name value. + /// + public const string HSeverity = "HSeverity"; + + /// + /// Gets the HShelved L5X name value. + /// + public const string HShelved = "HShelved"; + + /// + /// Gets the ICon L5X name value. + /// + public const string ICon = "ICon"; + + /// + /// Gets the Id L5X name value. + /// + public const string Id = "Id"; + + /// + /// Gets the ID L5X name value. + /// + public const string ID = "ID"; + + /// + /// Gets the ImportCreateMode L5X name value. + /// + public const string ImportCreateMode = "ImportCreateMode"; + + /// + /// Gets the In L5X name value. + /// + public const string In = "In"; + + /// + /// Gets the InAlarm L5X name value. + /// + public const string InAlarm = "InAlarm"; + + /// + /// Gets the InAlarmTime L5X name value. + /// + public const string InAlarmTime = "InAlarmTime"; + + /// + /// Gets the InAlarmUnack L5X name value. + /// + public const string InAlarmUnack = "InAlarmUnack"; + + /// + /// Gets the InAliasTag L5X name value. + /// + public const string InAliasTag = "InAliasTag"; + + /// + /// Gets the InAliasTagType L5X name value. + /// + public const string InAliasTagType = "InAliasTagType"; + + /// + /// Gets the IncludeDataSegment L5X name value. + /// + public const string IncludeDataSegment = "IncludeDataSegment"; + + /// + /// Gets the IncludeKeySegment L5X name value. + /// + public const string IncludeKeySegment = "IncludeKeySegment"; + + /// + /// Gets the IncludePortSegment L5X name value. + /// + public const string IncludePortSegment = "IncludePortSegment"; + + /// + /// Gets the Index L5X name value. + /// + public const string Index = "Index"; + + /// + /// Gets the IndicatorTag L5X name value. + /// + public const string IndicatorTag = "IndicatorTag"; + + /// + /// Gets the IndicatorTagIOI L5X name value. + /// + public const string IndicatorTagIOI = "IndicatorTagIOI"; + + /// + /// Gets the InductionMotorFluxCurrent L5X name value. + /// + public const string InductionMotorFluxCurrent = "InductionMotorFluxCurrent"; + + /// + /// Gets the InductionMotorMagnetizationReactance L5X name value. + /// + public const string InductionMotorMagnetizationReactance = "InductionMotorMagnetizationReactance"; + + /// + /// Gets the InductionMotorRatedFrequency L5X name value. + /// + public const string InductionMotorRatedFrequency = "InductionMotorRatedFrequency"; + + /// + /// Gets the InductionMotorRatedSlipSpeed L5X name value. + /// + public const string InductionMotorRatedSlipSpeed = "InductionMotorRatedSlipSpeed"; + + /// + /// Gets the InductionMotorRotorLeakageReactance L5X name value. + /// + public const string InductionMotorRotorLeakageReactance = "InductionMotorRotorLeakageReactance"; + + /// + /// Gets the InductionMotorRotorResistance L5X name value. + /// + public const string InductionMotorRotorResistance = "InductionMotorRotorResistance"; + + /// + /// Gets the InductionMotorStatorLeakageReactance L5X name value. + /// + public const string InductionMotorStatorLeakageReactance = "InductionMotorStatorLeakageReactance"; + + /// + /// Gets the InductionMotorStatorResistance L5X name value. + /// + public const string InductionMotorStatorResistance = "InductionMotorStatorResistance"; + + /// + /// Gets the InFault L5X name value. + /// + public const string InFault = "InFault"; + + /// + /// Gets the InFaulted L5X name value. + /// + public const string InFaulted = "InFaulted"; + + /// + /// Gets the InhibitAutomaticFirmwareUpdate L5X name value. + /// + public const string InhibitAutomaticFirmwareUpdate = "InhibitAutomaticFirmwareUpdate"; + + /// + /// Gets the Inhibited L5X name value. + /// + public const string Inhibited = "Inhibited"; + + /// + /// Gets the InhibitTask L5X name value. + /// + public const string InhibitTask = "InhibitTask"; + + /// + /// Gets the InIOI L5X name value. + /// + public const string InIOI = "InIOI"; + + /// + /// Gets the InIOINTT L5X name value. + /// + public const string InIOINTT = "InIOINTT"; + + /// + /// Gets the InitialState L5X name value. + /// + public const string InitialState = "InitialState"; + + /// + /// Gets the InitialStep L5X name value. + /// + public const string InitialStep = "InitialStep"; + + /// + /// Gets the InitialStepIndex L5X name value. + /// + public const string InitialStepIndex = "InitialStepIndex"; + + /// + /// Gets the InOutParameter L5X name value. + /// + public const string InOutParameter = "InOutParameter"; + + /// + /// Gets the Input L5X name value. + /// + public const string Input = "Input"; + + /// + /// Gets the InputConnectionType L5X name value. + /// + public const string InputConnectionType = "InputConnectionType"; + + /// + /// Gets the InputCxnPoint L5X name value. + /// + public const string InputCxnPoint = "InputCxnPoint"; + + /// + /// Gets the InputData L5X name value. + /// + public const string InputData = "InputData"; + + /// + /// Gets the InputDataType L5X name value. + /// + public const string InputDataType = "InputDataType"; + + /// + /// Gets the InputImageInit L5X name value. + /// + public const string InputImageInit = "InputImageInit"; + + /// + /// Gets the InputImageInitType L5X name value. + /// + public const string InputImageInitType = "InputImageInitType"; + + /// + /// Gets the InputPowerPhase L5X name value. + /// + public const string InputPowerPhase = "InputPowerPhase"; + + /// + /// Gets the InputProductionTrigger L5X name value. + /// + public const string InputProductionTrigger = "InputProductionTrigger"; + + /// + /// Gets the InputRealTimeFormat L5X name value. + /// + public const string InputRealTimeFormat = "InputRealTimeFormat"; + + /// + /// Gets the InputSize L5X name value. + /// + public const string InputSize = "InputSize"; + + /// + /// Gets the InputSizeVariable L5X name value. + /// + public const string InputSizeVariable = "InputSizeVariable"; + + /// + /// Gets the InputTag L5X name value. + /// + public const string InputTag = "InputTag"; + + /// + /// Gets the InputTagSuffix L5X name value. + /// + public const string InputTagSuffix = "InputTagSuffix"; + + /// + /// Gets the InputTagType L5X name value. + /// + public const string InputTagType = "InputTagType"; + + /// + /// Gets the InputTagUId L5X name value. + /// + public const string InputTagUId = "InputTagUId"; + + /// + /// Gets the InRegionId L5X name value. + /// + public const string InRegionId = "InRegionId"; + + /// + /// Gets the InstructFault L5X name value. + /// + public const string InstructFault = "InstructFault"; + + /// + /// Gets the Instruction L5X name value. + /// + public const string Instruction = "Instruction"; + + /// + /// Gets the InstructionAdaptorCollectionType L5X name value. + /// + public const string InstructionAdaptorCollectionType = "InstructionAdaptorCollectionType"; + + /// + /// Gets the InstructionAdaptorType L5X name value. + /// + public const string InstructionAdaptorType = "InstructionAdaptorType"; + + /// + /// Gets the Instructions L5X name value. + /// + public const string Instructions = "Instructions"; + + /// + /// Gets the InstructionUsageConfiguredAOIs L5X name value. + /// + public const string InstructionUsageConfiguredAOIs = "InstructionUsageConfiguredAOIs"; + + /// + /// Gets the InstructionUsageConfiguredInstructions L5X name value. + /// + public const string InstructionUsageConfiguredInstructions = "InstructionUsageConfiguredInstructions"; + + /// + /// Gets the IntegratorHoldEnable L5X name value. + /// + public const string IntegratorHoldEnable = "IntegratorHoldEnable"; + + /// + /// Gets the InterfaceSpeed L5X name value. + /// + public const string InterfaceSpeed = "InterfaceSpeed"; + + /// + /// Gets the InternetProtocol L5X name value. + /// + public const string InternetProtocol = "InternetProtocol"; + + /// + /// Gets the InterpolatedPositionConfiguration L5X name value. + /// + public const string InterpolatedPositionConfiguration = "InterpolatedPositionConfiguration"; + + /// + /// Gets the InverterCapacity L5X name value. + /// + public const string InverterCapacity = "InverterCapacity"; + + /// + /// Gets the InverterGroundCurrentUserLimit L5X name value. + /// + public const string InverterGroundCurrentUserLimit = "InverterGroundCurrentUserLimit"; + + /// + /// Gets the InverterOverloadAction L5X name value. + /// + public const string InverterOverloadAction = "InverterOverloadAction"; + + /// + /// Gets the InverterOvertemperatureUserLimit L5X name value. + /// + public const string InverterOvertemperatureUserLimit = "InverterOvertemperatureUserLimit"; + + /// + /// Gets the InverterThermalOverloadUserLimit L5X name value. + /// + public const string InverterThermalOverloadUserLimit = "InverterThermalOverloadUserLimit"; + + /// + /// Gets the IO L5X name value. + /// + public const string IO = "IO"; + + /// + /// Gets the IOForcesEnabled L5X name value. + /// + public const string IOForcesEnabled = "IOForcesEnabled"; + + /// + /// Gets the IOMapLEDStatus L5X name value. + /// + public const string IOMapLEDStatus = "IOMapLEDStatus"; + + /// + /// Gets the IOMemoryPadPercentage L5X name value. + /// + public const string IOMemoryPadPercentage = "IOMemoryPadPercentage"; + + /// + /// Gets the IPAddress L5X name value. + /// + public const string IPAddress = "IPAddress"; + + /// + /// Gets the IRef L5X name value. + /// + public const string IRef = "IRef"; + + /// + /// Gets the IsBoolean L5X name value. + /// + public const string IsBoolean = "IsBoolean"; + + /// + /// Gets the IsConcurrent L5X name value. + /// + public const string IsConcurrent = "IsConcurrent"; + + /// + /// Gets the IsEncrypted L5X name value. + /// + public const string IsEncrypted = "IsEncrypted"; + + /// + /// Gets the IsPermissionSet L5X name value. + /// + public const string IsPermissionSet = "IsPermissionSet"; + + /// + /// Gets the IsPlantPAxTaskingModelEnabled L5X name value. + /// + public const string IsPlantPAxTaskingModelEnabled = "IsPlantPAxTaskingModelEnabled"; + + /// + /// Gets the IsProjectDirty L5X name value. + /// + public const string IsProjectDirty = "IsProjectDirty"; + + /// + /// Gets the IsProjectDirtyFromAnotherWorkstation L5X name value. + /// + public const string IsProjectDirtyFromAnotherWorkstation = "IsProjectDirtyFromAnotherWorkstation"; + + /// + /// Gets the IsTemplate L5X name value. + /// + public const string IsTemplate = "IsTemplate "; + + /// + /// Gets the JointRatioDenominator L5X name value. + /// + public const string JointRatioDenominator = "JointRatioDenominator"; + + /// + /// Gets the JointRatioNumerator L5X name value. + /// + public const string JointRatioNumerator = "JointRatioNumerator"; + + /// + /// Gets the JSR L5X name value. + /// + public const string JSR = "JSR"; + + /// + /// Gets the KeepTestEditsOnSwitchOver L5X name value. + /// + public const string KeepTestEditsOnSwitchOver = "KeepTestEditsOnSwitchOver"; + + /// + /// Gets the KeySwitchPosition L5X name value. + /// + public const string KeySwitchPosition = "KeySwitchPosition"; + + /// + /// Gets the L5KDataType L5X name value. + /// + public const string L5KDataType = "L5KDataType"; + + /// + /// Gets the L5KDefaultDataType L5X name value. + /// + public const string L5KDefaultDataType = "L5KDefaultDataType"; + + /// + /// Gets the L5KForceDataType L5X name value. + /// + public const string L5KForceDataType = "L5KForceDataType"; + + /// + /// Gets the Label L5X name value. + /// + public const string Label = "Label"; + + /// + /// Gets the LabelAdaptorCollectionType L5X name value. + /// + public const string LabelAdaptorCollectionType = "LabelAdaptorCollectionType"; + + /// + /// Gets the LabelAdaptorType L5X name value. + /// + public const string LabelAdaptorType = "LabelAdaptorType"; + + /// + /// Gets the LabelCollectionType L5X name value. + /// + public const string LabelCollectionType = "LabelCollectionType"; + + /// + /// Gets the Labels L5X name value. + /// + public const string Labels = "Labels"; + + /// + /// Gets the LabelsWideType L5X name value. + /// + public const string LabelsWideType = "LabelsWideType"; + + /// + /// Gets the LabelTextType L5X name value. + /// + public const string LabelTextType = "LabelTextType"; + + /// + /// Gets the LabelType L5X name value. + /// + public const string LabelType = "LabelType"; + + /// + /// Gets the LabelWideType L5X name value. + /// + public const string LabelWideType = "LabelWideType"; + + /// + /// Gets the LAcked L5X name value. + /// + public const string LAcked = "LAcked"; + + /// + /// Gets the LAlarmCount L5X name value. + /// + public const string LAlarmCount = "LAlarmCount"; + + /// + /// Gets the Lang L5X name value. + /// + public const string Lang = "Lang"; + + /// + /// Gets the LanguageElements L5X name value. + /// + public const string LanguageElements = "LanguageElements"; + + /// + /// Gets the LargePacketUsage L5X name value. + /// + public const string LargePacketUsage = "LargePacketUsage"; + + /// + /// Gets the LastModifiedDate L5X name value. + /// + public const string LastModifiedDate = "LastModifiedDate"; + + /// + /// Gets the LastScanTime L5X name value. + /// + public const string LastScanTime = "LastScanTime"; + + /// + /// Gets the Latched L5X name value. + /// + public const string Latched = "Latched"; + + /// + /// Gets the LatchedTagIOI L5X name value. + /// + public const string LatchedTagIOI = "LatchedTagIOI"; + + /// + /// Gets the LatchedTagIOINTT L5X name value. + /// + public const string LatchedTagIOINTT = "LatchedTagIOINTT"; + + /// + /// Gets the LDTCalibrationConstant L5X name value. + /// + public const string LDTCalibrationConstant = "LDTCalibrationConstant"; + + /// + /// Gets the LDTCalibrationConstantUnits L5X name value. + /// + public const string LDTCalibrationConstantUnits = "LDTCalibrationConstantUnits"; + + /// + /// Gets the LDTLength L5X name value. + /// + public const string LDTLength = "LDTLength"; + + /// + /// Gets the LDTLengthUnits L5X name value. + /// + public const string LDTLengthUnits = "LDTLengthUnits"; + + /// + /// Gets the LDTRecirculations L5X name value. + /// + public const string LDTRecirculations = "LDTRecirculations"; + + /// + /// Gets the LDTScaling L5X name value. + /// + public const string LDTScaling = "LDTScaling"; + + /// + /// Gets the LDTScalingUnits L5X name value. + /// + public const string LDTScalingUnits = "LDTScalingUnits"; + + /// + /// Gets the LDTType L5X name value. + /// + public const string LDTType = "LDTType"; + + /// + /// Gets the Leg L5X name value. + /// + public const string Leg = "Leg"; + + /// + /// Gets the LEnabled L5X name value. + /// + public const string LEnabled = "LEnabled"; + + /// + /// Gets the Length L5X name value. + /// + public const string Length = "Length"; + + /// + /// Gets the Library L5X name value. + /// + public const string Library = "Library"; + + /// + /// Gets the LibraryAdaptorCollectionType L5X name value. + /// + public const string LibraryAdaptorCollectionType = "LibraryAdaptorCollectionType"; + + /// + /// Gets the LibraryAdaptorType L5X name value. + /// + public const string LibraryAdaptorType = "LibraryAdaptorType"; + + /// + /// Gets the Librarys L5X name value. + /// + public const string Librarys = "Librarys"; + + /// + /// Gets the Limit L5X name value. + /// + public const string Limit = "Limit"; + + /// + /// Gets the LimitHigh L5X name value. + /// + public const string LimitHigh = "LimitHigh"; + + /// + /// Gets the LimitHighUsesExpr L5X name value. + /// + public const string LimitHighUsesExpr = "LimitHighUsesExpr"; + + /// + /// Gets the LimitLow L5X name value. + /// + public const string LimitLow = "LimitLow"; + + /// + /// Gets the LimitLowUsesExpr L5X name value. + /// + public const string LimitLowUsesExpr = "LimitLowUsesExpr"; + + /// + /// Gets the LInAlarm L5X name value. + /// + public const string LInAlarm = "LInAlarm"; + + /// + /// Gets the LInAlarmTime L5X name value. + /// + public const string LInAlarmTime = "LInAlarmTime"; + + /// + /// Gets the LInAlarmUnack L5X name value. + /// + public const string LInAlarmUnack = "LInAlarmUnack"; + + /// + /// Gets the Line L5X name value. + /// + public const string Line = "Line"; + + /// + /// Gets the LinearMotorDampingCoefficient L5X name value. + /// + public const string LinearMotorDampingCoefficient = "LinearMotorDampingCoefficient"; + + /// + /// Gets the LinearMotorIntegralLimitSwitch L5X name value. + /// + public const string LinearMotorIntegralLimitSwitch = "LinearMotorIntegralLimitSwitch"; + + /// + /// Gets the LinearMotorMass L5X name value. + /// + public const string LinearMotorMass = "LinearMotorMass"; + + /// + /// Gets the LinearMotorMaxSpeed L5X name value. + /// + public const string LinearMotorMaxSpeed = "LinearMotorMaxSpeed"; + + /// + /// Gets the LinearMotorPolePitch L5X name value. + /// + public const string LinearMotorPolePitch = "LinearMotorPolePitch"; + + /// + /// Gets the LinearMotorRatedSpeed L5X name value. + /// + public const string LinearMotorRatedSpeed = "LinearMotorRatedSpeed"; + + /// + /// Gets the LinkLength1 L5X name value. + /// + public const string LinkLength1 = "LinkLength1"; + + /// + /// Gets the LinkLength2 L5X name value. + /// + public const string LinkLength2 = "LinkLength2"; + + /// + /// Gets the LinkLength3 L5X name value. + /// + public const string LinkLength3 = "LinkLength3"; + + /// + /// Gets the LLAcked L5X name value. + /// + public const string LLAcked = "LLAcked"; + + /// + /// Gets the LLAlarmCount L5X name value. + /// + public const string LLAlarmCount = "LLAlarmCount"; + + /// + /// Gets the LLEnabled L5X name value. + /// + public const string LLEnabled = "LLEnabled"; + + /// + /// Gets the LLimit L5X name value. + /// + public const string LLimit = "LLimit"; + + /// + /// Gets the LLInAlarm L5X name value. + /// + public const string LLInAlarm = "LLInAlarm"; + + /// + /// Gets the LLInAlarmTime L5X name value. + /// + public const string LLInAlarmTime = "LLInAlarmTime"; + + /// + /// Gets the LLInAlarmUnack L5X name value. + /// + public const string LLInAlarmUnack = "LLInAlarmUnack"; + + /// + /// Gets the LLLimit L5X name value. + /// + public const string LLLimit = "LLLimit"; + + /// + /// Gets the LLMinDurationEnable L5X name value. + /// + public const string LLMinDurationEnable = "LLMinDurationEnable"; + + /// + /// Gets the LLOperAck L5X name value. + /// + public const string LLOperAck = "LLOperAck"; + + /// + /// Gets the LLOperShelve L5X name value. + /// + public const string LLOperShelve = "LLOperShelve"; + + /// + /// Gets the LLOperUnshelve L5X name value. + /// + public const string LLOperUnshelve = "LLOperUnshelve"; + + /// + /// Gets the LLProgAck L5X name value. + /// + public const string LLProgAck = "LLProgAck"; + + /// + /// Gets the LLSeverity L5X name value. + /// + public const string LLSeverity = "LLSeverity"; + + /// + /// Gets the LLShelved L5X name value. + /// + public const string LLShelved = "LLShelved"; + + /// + /// Gets the LMinDurationEnable L5X name value. + /// + public const string LMinDurationEnable = "LMinDurationEnable"; + + /// + /// Gets the LoadCoupling L5X name value. + /// + public const string LoadCoupling = "LoadCoupling"; + + /// + /// Gets the LoadInertiaRatio L5X name value. + /// + public const string LoadInertiaRatio = "LoadInertiaRatio"; + + /// + /// Gets the LoadObserverBandwidth L5X name value. + /// + public const string LoadObserverBandwidth = "LoadObserverBandwidth"; + + /// + /// Gets the LoadObserverConfiguration L5X name value. + /// + public const string LoadObserverConfiguration = "LoadObserverConfiguration"; + + /// + /// Gets the LoadObserverFeedbackGain L5X name value. + /// + public const string LoadObserverFeedbackGain = "LoadObserverFeedbackGain"; + + /// + /// Gets the LoadObserverIntegratorBandwidth L5X name value. + /// + public const string LoadObserverIntegratorBandwidth = "LoadObserverIntegratorBandwidth"; + + /// + /// Gets the LoadRatio L5X name value. + /// + public const string LoadRatio = "LoadRatio"; + + /// + /// Gets the LoadType L5X name value. + /// + public const string LoadType = "LoadType"; + + /// + /// Gets the LocalControl L5X name value. + /// + public const string LocalControl = "LocalControl"; + + /// + /// Gets the LocalElement L5X name value. + /// + public const string LocalElement = "LocalElement"; + + /// + /// Gets the LocalIndex L5X name value. + /// + public const string LocalIndex = "LocalIndex"; + + /// + /// Gets the LocalizedAdditionalHelpText L5X name value. + /// + public const string LocalizedAdditionalHelpText = "LocalizedAdditionalHelpText"; + + /// + /// Gets the LocalizedAdditionalHelpTextType L5X name value. + /// + public const string LocalizedAdditionalHelpTextType = "LocalizedAdditionalHelpTextType"; + + /// + /// Gets the LocalizedComment L5X name value. + /// + public const string LocalizedComment = "LocalizedComment"; + + /// + /// Gets the LocalizedCommentWideType L5X name value. + /// + public const string LocalizedCommentWideType = "LocalizedCommentWideType"; + + /// + /// Gets the LocalizedDescription L5X name value. + /// + public const string LocalizedDescription = "LocalizedDescription"; + + /// + /// Gets the LocalizedEngineeringUnit L5X name value. + /// + public const string LocalizedEngineeringUnit = "LocalizedEngineeringUnit"; + + /// + /// Gets the LocalizedLabel L5X name value. + /// + public const string LocalizedLabel = "LocalizedLabel"; + + /// + /// Gets the LocalizedPrimaryActionSet L5X name value. + /// + public const string LocalizedPrimaryActionSet = "LocalizedPrimaryActionSet"; + + /// + /// Gets the LocalizedRevisionNote L5X name value. + /// + public const string LocalizedRevisionNote = "LocalizedRevisionNote"; + + /// + /// Gets the LocalizedState0 L5X name value. + /// + public const string LocalizedState0 = "LocalizedState0"; + + /// + /// Gets the LocalizedState1 L5X name value. + /// + public const string LocalizedState1 = "LocalizedState1"; + + /// + /// Gets the LocalizedText L5X name value. + /// + public const string LocalizedText = "LocalizedText"; + + /// + /// Gets the LocalTag L5X name value. + /// + public const string LocalTag = "LocalTag"; + + /// + /// Gets the LocalTags L5X name value. + /// + public const string LocalTags = "LocalTags"; + + /// + /// Gets the LocalTimeAdjustment L5X name value. + /// + public const string LocalTimeAdjustment = "LocalTimeAdjustment"; + + /// + /// Gets the LogicHash L5X name value. + /// + public const string LogicHash = "LogicHash"; + + /// + /// Gets the LoopResponse L5X name value. + /// + public const string LoopResponse = "LoopResponse"; + + /// + /// Gets the LOperAck L5X name value. + /// + public const string LOperAck = "LOperAck"; + + /// + /// Gets the LOperShelve L5X name value. + /// + public const string LOperShelve = "LOperShelve"; + + /// + /// Gets the LOperUnshelve L5X name value. + /// + public const string LOperUnshelve = "LOperUnshelve"; + + /// + /// Gets the LossOfCommCmd L5X name value. + /// + public const string LossOfCommCmd = "LossOfCommCmd"; + + /// + /// Gets the LowFrequencyIdCurrentLimitRegulatorKp L5X name value. + /// + public const string LowFrequencyIdCurrentLimitRegulatorKp = "LowFrequencyIdCurrentLimitRegulatorKp"; + + /// + /// Gets the LowFrequencyIqCurrentLimitRegulatorKp L5X name value. + /// + public const string LowFrequencyIqCurrentLimitRegulatorKp = "LowFrequencyIqCurrentLimitRegulatorKp"; + + /// + /// Gets the LProgAck L5X name value. + /// + public const string LProgAck = "LProgAck"; + + /// + /// Gets the LqIqFeedbackFilterBandwidth L5X name value. + /// + public const string LqIqFeedbackFilterBandwidth = "LqIqFeedbackFilterBandwidth"; + + /// + /// Gets the LSeverity L5X name value. + /// + public const string LSeverity = "LSeverity"; + + /// + /// Gets the LShelved L5X name value. + /// + public const string LShelved = "LShelved"; + + /// + /// Gets the MainRoutineName L5X name value. + /// + public const string MainRoutineName = "MainRoutineName"; + + /// + /// Gets the Major L5X name value. + /// + public const string Major = "Major"; + + /// + /// Gets the MajorFault L5X name value. + /// + public const string MajorFault = "MajorFault"; + + /// + /// Gets the MajorFaultProgram L5X name value. + /// + public const string MajorFaultProgram = "MajorFaultProgram"; + + /// + /// Gets the MajorFaultProgramUId L5X name value. + /// + public const string MajorFaultProgramUId = "MajorFaultProgramUId"; + + /// + /// Gets the MajorRev L5X name value. + /// + public const string MajorRev = "MajorRev"; + + /// + /// Gets the MapConnectionCollectionType L5X name value. + /// + public const string MapConnectionCollectionType = "MapConnectionCollectionType"; + + /// + /// Gets the MapConnectionType L5X name value. + /// + public const string MapConnectionType = "MapConnectionType"; + + /// + /// Gets the MapDeviceCollectionType L5X name value. + /// + public const string MapDeviceCollectionType = "MapDeviceCollectionType"; + + /// + /// Gets the MapDeviceType L5X name value. + /// + public const string MapDeviceType = "MapDeviceType"; + + /// + /// Gets the Marker L5X name value. + /// + public const string Marker = "Marker"; + + /// + /// Gets the MaskValueSize L5X name value. + /// + public const string MaskValueSize = "MaskValueSize"; + + /// + /// Gets the Master L5X name value. + /// + public const string Master = "Master"; + + /// + /// Gets the MasterID L5X name value. + /// + public const string MasterID = "MasterID"; + + /// + /// Gets the MasterInputConfigurationBits L5X name value. + /// + public const string MasterInputConfigurationBits = "MasterInputConfigurationBits"; + + /// + /// Gets the MasterMessageTransmit L5X name value. + /// + public const string MasterMessageTransmit = "MasterMessageTransmit"; + + /// + /// Gets the MasterPositionFilterBandwidth L5X name value. + /// + public const string MasterPositionFilterBandwidth = "MasterPositionFilterBandwidth"; + + /// + /// Gets the MatchProjectToController L5X name value. + /// + public const string MatchProjectToController = "MatchProjectToController"; + + /// + /// Gets the Max L5X name value. + /// + public const string Max = "Max"; + + /// + /// Gets the MaxConsumerNumber L5X name value. + /// + public const string MaxConsumerNumber = "MaxConsumerNumber"; + + /// + /// Gets the Maxes L5X name value. + /// + public const string Maxes = "Maxes"; + + /// + /// Gets the MaximumAcceleration L5X name value. + /// + public const string MaximumAcceleration = "MaximumAcceleration"; + + /// + /// Gets the MaximumAccelerationJerk L5X name value. + /// + public const string MaximumAccelerationJerk = "MaximumAccelerationJerk"; + + /// + /// Gets the MaximumDeceleration L5X name value. + /// + public const string MaximumDeceleration = "MaximumDeceleration"; + + /// + /// Gets the MaximumDecelerationJerk L5X name value. + /// + public const string MaximumDecelerationJerk = "MaximumDecelerationJerk"; + + /// + /// Gets the MaximumFrequency L5X name value. + /// + public const string MaximumFrequency = "MaximumFrequency"; + + /// + /// Gets the MaximumNegativeTravel L5X name value. + /// + public const string MaximumNegativeTravel = "MaximumNegativeTravel"; + + /// + /// Gets the MaximumOrientationAcceleration L5X name value. + /// + public const string MaximumOrientationAcceleration = "MaximumOrientationAcceleration"; + + /// + /// Gets the MaximumOrientationDeceleration L5X name value. + /// + public const string MaximumOrientationDeceleration = "MaximumOrientationDeceleration"; + + /// + /// Gets the MaximumOrientationSpeed L5X name value. + /// + public const string MaximumOrientationSpeed = "MaximumOrientationSpeed"; + + /// + /// Gets the MaximumPendingMoves L5X name value. + /// + public const string MaximumPendingMoves = "MaximumPendingMoves"; + + /// + /// Gets the MaximumPositiveTravel L5X name value. + /// + public const string MaximumPositiveTravel = "MaximumPositiveTravel"; + + /// + /// Gets the MaximumRPI L5X name value. + /// + public const string MaximumRPI = "MaximumRPI"; + + /// + /// Gets the MaximumSpeed L5X name value. + /// + public const string MaximumSpeed = "MaximumSpeed"; + + /// + /// Gets the MaximumVoltage L5X name value. + /// + public const string MaximumVoltage = "MaximumVoltage"; + + /// + /// Gets the MaxInterval L5X name value. + /// + public const string MaxInterval = "MaxInterval"; + + /// + /// Gets the MaxLimitCollectionType L5X name value. + /// + public const string MaxLimitCollectionType = "MaxLimitCollectionType"; + + /// + /// Gets the MaxLimitType L5X name value. + /// + public const string MaxLimitType = "MaxLimitType"; + + /// + /// Gets the MaxObservedNetworkDelay L5X name value. + /// + public const string MaxObservedNetworkDelay = "MaxObservedNetworkDelay"; + + /// + /// Gets the MaxOutputFrequency L5X name value. + /// + public const string MaxOutputFrequency = "MaxOutputFrequency"; + + /// + /// Gets the MaxScanTime L5X name value. + /// + public const string MaxScanTime = "MaxScanTime"; + + /// + /// Gets the MaxShelveDuration L5X name value. + /// + public const string MaxShelveDuration = "MaxShelveDuration"; + + /// + /// Gets the MaxStationAddress L5X name value. + /// + public const string MaxStationAddress = "MaxStationAddress"; + + /// + /// Gets the MechanicalBrakeControl L5X name value. + /// + public const string MechanicalBrakeControl = "MechanicalBrakeControl"; + + /// + /// Gets the MechanicalBrakeEngageDelay L5X name value. + /// + public const string MechanicalBrakeEngageDelay = "MechanicalBrakeEngageDelay"; + + /// + /// Gets the MechanicalBrakeReleaseDelay L5X name value. + /// + public const string MechanicalBrakeReleaseDelay = "MechanicalBrakeReleaseDelay"; + + /// + /// Gets the Member L5X name value. + /// + public const string Member = "Member"; + + /// + /// Gets the MemberAlarmDefinition L5X name value. + /// + public const string MemberAlarmDefinition = "MemberAlarmDefinition"; + + /// + /// Gets the MemberAlarmDefinitionType L5X name value. + /// + public const string MemberAlarmDefinitionType = "MemberAlarmDefinitionType"; + + /// + /// Gets the Members L5X name value. + /// + public const string Members = "Members"; + + /// + /// Gets the Message L5X name value. + /// + public const string Message = "Message"; + + /// + /// Gets the MessageDataType L5X name value. + /// + public const string MessageDataType = "MessageDataType"; + + /// + /// Gets the MessageParameters L5X name value. + /// + public const string MessageParameters = "MessageParameters"; + + /// + /// Gets the MessageReferences L5X name value. + /// + public const string MessageReferences = "MessageReferences"; + + /// + /// Gets the Messages L5X name value. + /// + public const string Messages = "Messages"; + + /// + /// Gets the MessageType L5X name value. + /// + public const string MessageType = "MessageType"; + + /// + /// Gets the MetadataId L5X name value. + /// + public const string MetadataId = "MetadataId"; + + /// + /// Gets the MetadataIdList L5X name value. + /// + public const string MetadataIdList = "MetadataIdList"; + + /// + /// Gets the Min L5X name value. + /// + public const string Min = "Min"; + + /// + /// Gets the MinDurationPRE L5X name value. + /// + public const string MinDurationPRE = "MinDurationPRE"; + + /// + /// Gets the MinimumRPI L5X name value. + /// + public const string MinimumRPI = "MinimumRPI"; + + /// + /// Gets the MinInterval L5X name value. + /// + public const string MinInterval = "MinInterval"; + + /// + /// Gets the MinLimitCollectionType L5X name value. + /// + public const string MinLimitCollectionType = "MinLimitCollectionType"; + + /// + /// Gets the MinLimitType L5X name value. + /// + public const string MinLimitType = "MinLimitType"; + + /// + /// Gets the Minor L5X name value. + /// + public const string Minor = "Minor"; + + /// + /// Gets the MinorRev L5X name value. + /// + public const string MinorRev = "MinorRev"; + + /// + /// Gets the Mins L5X name value. + /// + public const string Mins = "Mins"; + + /// + /// Gets the Mode L5X name value. + /// + public const string Mode = "Mode"; + + /// + /// Gets the ModeChangeAttentionChar L5X name value. + /// + public const string ModeChangeAttentionChar = "ModeChangeAttentionChar"; + + /// + /// Gets the Module L5X name value. + /// + public const string Module = "Module"; + + /// + /// Gets the ModuleEKeyType L5X name value. + /// + public const string ModuleEKeyType = "ModuleEKeyType"; + + /// + /// Gets the Modules L5X name value. + /// + public const string Modules = "Modules"; + + /// + /// Gets the ModulesUId L5X name value. + /// + public const string ModulesUId = "ModulesUId"; + + /// + /// Gets the MotionExceptionAction L5X name value. + /// + public const string MotionExceptionAction = "MotionExceptionAction"; + + /// + /// Gets the MotionGroup L5X name value. + /// + public const string MotionGroup = "MotionGroup"; + + /// + /// Gets the MotionGroupDataType L5X name value. + /// + public const string MotionGroupDataType = "MotionGroupDataType"; + + /// + /// Gets the MotionGroupInstance L5X name value. + /// + public const string MotionGroupInstance = "MotionGroupInstance"; + + /// + /// Gets the MotionGroupParameters L5X name value. + /// + public const string MotionGroupParameters = "MotionGroupParameters"; + + /// + /// Gets the MotionGroupsUId L5X name value. + /// + public const string MotionGroupsUId = "MotionGroupsUId"; + + /// + /// Gets the MotionGroupType L5X name value. + /// + public const string MotionGroupType = "MotionGroupType"; + + /// + /// Gets the MotionModule L5X name value. + /// + public const string MotionModule = "MotionModule"; + + /// + /// Gets the MotionPolarity L5X name value. + /// + public const string MotionPolarity = "MotionPolarity"; + + /// + /// Gets the MotionResolution L5X name value. + /// + public const string MotionResolution = "MotionResolution"; + + /// + /// Gets the MotionScalingConfiguration L5X name value. + /// + public const string MotionScalingConfiguration = "MotionScalingConfiguration"; + + /// + /// Gets the MotorAdaptionSpeed L5X name value. + /// + public const string MotorAdaptionSpeed = "MotorAdaptionSpeed"; + + /// + /// Gets the MotorCatalogNumber L5X name value. + /// + public const string MotorCatalogNumber = "MotorCatalogNumber"; + + /// + /// Gets the MotorData L5X name value. + /// + public const string MotorData = "MotorData"; + + /// + /// Gets the MotorDataSource L5X name value. + /// + public const string MotorDataSource = "MotorDataSource"; + + /// + /// Gets the MotorDeviceCode L5X name value. + /// + public const string MotorDeviceCode = "MotorDeviceCode"; + + /// + /// Gets the MotorFeedbackResolution L5X name value. + /// + public const string MotorFeedbackResolution = "MotorFeedbackResolution"; + + /// + /// Gets the MotorFeedbackType L5X name value. + /// + public const string MotorFeedbackType = "MotorFeedbackType"; + + /// + /// Gets the MotorFeedbackUnit L5X name value. + /// + public const string MotorFeedbackUnit = "MotorFeedbackUnit"; + + /// + /// Gets the MotorIntegralThermalSwitch L5X name value. + /// + public const string MotorIntegralThermalSwitch = "MotorIntegralThermalSwitch"; + + /// + /// Gets the MotorMaxWindingTemperature L5X name value. + /// + public const string MotorMaxWindingTemperature = "MotorMaxWindingTemperature"; + + /// + /// Gets the MotorOverloadAction L5X name value. + /// + public const string MotorOverloadAction = "MotorOverloadAction"; + + /// + /// Gets the MotorOverloadLimit L5X name value. + /// + public const string MotorOverloadLimit = "MotorOverloadLimit"; + + /// + /// Gets the MotorOverspeedUserLimit L5X name value. + /// + public const string MotorOverspeedUserLimit = "MotorOverspeedUserLimit"; + + /// + /// Gets the MotorOvertemperatureUserLimit L5X name value. + /// + public const string MotorOvertemperatureUserLimit = "MotorOvertemperatureUserLimit"; + + /// + /// Gets the MotorPhaseLossLimit L5X name value. + /// + public const string MotorPhaseLossLimit = "MotorPhaseLossLimit"; + + /// + /// Gets the MotorPolarity L5X name value. + /// + public const string MotorPolarity = "MotorPolarity"; + + /// + /// Gets the MotorRatedContinuousCurrent L5X name value. + /// + public const string MotorRatedContinuousCurrent = "MotorRatedContinuousCurrent"; + + /// + /// Gets the MotorRatedOutputPower L5X name value. + /// + public const string MotorRatedOutputPower = "MotorRatedOutputPower"; + + /// + /// Gets the MotorRatedPeakCurrent L5X name value. + /// + public const string MotorRatedPeakCurrent = "MotorRatedPeakCurrent"; + + /// + /// Gets the MotorRatedVoltage L5X name value. + /// + public const string MotorRatedVoltage = "MotorRatedVoltage"; + + /// + /// Gets the MotorStabilityControlEnable L5X name value. + /// + public const string MotorStabilityControlEnable = "MotorStabilityControlEnable"; + + /// + /// Gets the MotorStabilityControlFilterBandwidth L5X name value. + /// + public const string MotorStabilityControlFilterBandwidth = "MotorStabilityControlFilterBandwidth"; + + /// + /// Gets the MotorStabilityControlFrequencyGain L5X name value. + /// + public const string MotorStabilityControlFrequencyGain = "MotorStabilityControlFrequencyGain"; + + /// + /// Gets the MotorStabilityControlVoltageGain L5X name value. + /// + public const string MotorStabilityControlVoltageGain = "MotorStabilityControlVoltageGain"; + + /// + /// Gets the MotorTestDataValid L5X name value. + /// + public const string MotorTestDataValid = "MotorTestDataValid"; + + /// + /// Gets the MotorTestFluxCurrent L5X name value. + /// + public const string MotorTestFluxCurrent = "MotorTestFluxCurrent"; + + /// + /// Gets the MotorTestInductance L5X name value. + /// + public const string MotorTestInductance = "MotorTestInductance"; + + /// + /// Gets the MotorTestResistance L5X name value. + /// + public const string MotorTestResistance = "MotorTestResistance"; + + /// + /// Gets the MotorTestSlipSpeed L5X name value. + /// + public const string MotorTestSlipSpeed = "MotorTestSlipSpeed"; + + /// + /// Gets the MotorTestSpeed L5X name value. + /// + public const string MotorTestSpeed = "MotorTestSpeed"; + + /// + /// Gets the MotorTestTorque L5X name value. + /// + public const string MotorTestTorque = "MotorTestTorque"; + + /// + /// Gets the MotorTestTravelLimit L5X name value. + /// + public const string MotorTestTravelLimit = "MotorTestTravelLimit"; + + /// + /// Gets the MotorThermalFaultAction L5X name value. + /// + public const string MotorThermalFaultAction = "MotorThermalFaultAction"; + + /// + /// Gets the MotorThermalOverloadUserLimit L5X name value. + /// + public const string MotorThermalOverloadUserLimit = "MotorThermalOverloadUserLimit"; + + /// + /// Gets the MotorType L5X name value. + /// + public const string MotorType = "MotorType"; + + /// + /// Gets the MotorUnit L5X name value. + /// + public const string MotorUnit = "MotorUnit"; + + /// + /// Gets the MotorWindingToAmbientCapacitance L5X name value. + /// + public const string MotorWindingToAmbientCapacitance = "MotorWindingToAmbientCapacitance"; + + /// + /// Gets the MotorWindingToAmbientResistance L5X name value. + /// + public const string MotorWindingToAmbientResistance = "MotorWindingToAmbientResistance"; + + /// + /// Gets the MoveRepeatDwell L5X name value. + /// + public const string MoveRepeatDwell = "MoveRepeatDwell"; + + /// + /// Gets the MoveRepeatMode L5X name value. + /// + public const string MoveRepeatMode = "MoveRepeatMode"; + + /// + /// Gets the MsgType L5X name value. + /// + public const string MsgType = "MsgType"; + + /// + /// Gets the NAKReceiveLimit L5X name value. + /// + public const string NAKReceiveLimit = "NAKReceiveLimit"; + + /// + /// Gets the Name L5X name value. + /// + public const string Name = "Name"; + + /// + /// Gets the NATActualAddress L5X name value. + /// + public const string NATActualAddress = "NATActualAddress"; + + /// + /// Gets the Navigation L5X name value. + /// + public const string Navigation = "Navigation"; + + /// + /// Gets the NavigationAdaptorType L5X name value. + /// + public const string NavigationAdaptorType = "NavigationAdaptorType"; + + /// + /// Gets the NavigationCollectionType L5X name value. + /// + public const string NavigationCollectionType = "NavigationCollectionType"; + + /// + /// Gets the Navigations L5X name value. + /// + public const string Navigations = "Navigations"; + + /// + /// Gets the NetEnergyOdometer L5X name value. + /// + public const string NetEnergyOdometer = "NetEnergyOdometer"; + + /// + /// Gets the NetworkDelayMultiplier L5X name value. + /// + public const string NetworkDelayMultiplier = "NetworkDelayMultiplier"; + + /// + /// Gets the NoPhaseStep L5X name value. + /// + public const string NoPhaseStep = "NoPhaseStep"; + + /// + /// Gets the NormalPollGroupSize L5X name value. + /// + public const string NormalPollGroupSize = "NormalPollGroupSize"; + + /// + /// Gets the NormalPollNodeFile L5X name value. + /// + public const string NormalPollNodeFile = "NormalPollNodeFile"; + + /// + /// Gets the Number L5X name value. + /// + public const string Number = "Number"; + + /// + /// Gets the NumberOfCaptures L5X name value. + /// + public const string NumberOfCaptures = "NumberOfCaptures"; + + /// + /// Gets the NumberOfDataLogTags L5X name value. + /// + public const string NumberOfDataLogTags = "NumberOfDataLogTags"; + + /// + /// Gets the Object L5X name value. + /// + public const string Object = "Object"; + + /// + /// Gets the ObjectType L5X name value. + /// + public const string ObjectType = "ObjectType"; + + /// + /// Gets the OCon L5X name value. + /// + public const string OCon = "OCon"; + + /// + /// Gets the OffDelay L5X name value. + /// + public const string OffDelay = "OffDelay"; + + /// + /// Gets the Offset L5X name value. + /// + public const string Offset = "Offset"; + + /// + /// Gets the OnDelay L5X name value. + /// + public const string OnDelay = "OnDelay"; + + /// + /// Gets the OnlineEditType L5X name value. + /// + public const string OnlineEditType = "OnlineEditType"; + + /// + /// Gets the OnlineSN L5X name value. + /// + public const string OnlineSN = "OnlineSN"; + + /// + /// Gets the OpenOrder L5X name value. + /// + public const string OpenOrder = "OpenOrder"; + + /// + /// Gets the OperAck L5X name value. + /// + public const string OperAck = "OperAck"; + + /// + /// Gets the OperAckAll L5X name value. + /// + public const string OperAckAll = "OperAckAll"; + + /// + /// Gets the Operand L5X name value. + /// + public const string Operand = "Operand"; + + /// + /// Gets the OperandIOI L5X name value. + /// + public const string OperandIOI = "OperandIOI"; + + /// + /// Gets the OperandIOINTT L5X name value. + /// + public const string OperandIOINTT = "OperandIOINTT"; + + /// + /// Gets the OperDisable L5X name value. + /// + public const string OperDisable = "OperDisable"; + + /// + /// Gets the OperEnable L5X name value. + /// + public const string OperEnable = "OperEnable"; + + /// + /// Gets the OperReset L5X name value. + /// + public const string OperReset = "OperReset"; + + /// + /// Gets the OperShelve L5X name value. + /// + public const string OperShelve = "OperShelve"; + + /// + /// Gets the OperSuppress L5X name value. + /// + public const string OperSuppress = "OperSuppress"; + + /// + /// Gets the OperUnshelve L5X name value. + /// + public const string OperUnshelve = "OperUnshelve"; + + /// + /// Gets the OperUnsuppress L5X name value. + /// + public const string OperUnsuppress = "OperUnsuppress"; + + /// + /// Gets the ORef L5X name value. + /// + public const string ORef = "ORef"; + + /// + /// Gets the OriginalLibrary L5X name value. + /// + public const string OriginalLibrary = "OriginalLibrary"; + + /// + /// Gets the OriginalName L5X name value. + /// + public const string OriginalName = "OriginalName"; + + /// + /// Gets the OriginalRevision L5X name value. + /// + public const string OriginalRevision = "OriginalRevision"; + + /// + /// Gets the OriginalVendor L5X name value. + /// + public const string OriginalVendor = "OriginalVendor"; + + /// + /// Gets the Out L5X name value. + /// + public const string Out = "Out"; + + /// + /// Gets the OutAliasTag L5X name value. + /// + public const string OutAliasTag = "OutAliasTag"; + + /// + /// Gets the OutAliasTagType L5X name value. + /// + public const string OutAliasTagType = "OutAliasTagType"; + + /// + /// Gets the OutputCamExecutionTargets L5X name value. + /// + public const string OutputCamExecutionTargets = "OutputCamExecutionTargets"; + + /// + /// Gets the OutputConnectionType L5X name value. + /// + public const string OutputConnectionType = "OutputConnectionType"; + + /// + /// Gets the OutputCxnPoint L5X name value. + /// + public const string OutputCxnPoint = "OutputCxnPoint"; + + /// + /// Gets the OutputData L5X name value. + /// + public const string OutputData = "OutputData"; + + /// + /// Gets the OutputDataType L5X name value. + /// + public const string OutputDataType = "OutputDataType"; + + /// + /// Gets the OutputLimit L5X name value. + /// + public const string OutputLimit = "OutputLimit"; + + /// + /// Gets the OutputLPFilterBandwidth L5X name value. + /// + public const string OutputLPFilterBandwidth = "OutputLPFilterBandwidth"; + + /// + /// Gets the OutputNotchFilterFrequency L5X name value. + /// + public const string OutputNotchFilterFrequency = "OutputNotchFilterFrequency"; + + /// + /// Gets the OutputOffset L5X name value. + /// + public const string OutputOffset = "OutputOffset"; + + /// + /// Gets the OutputPriority L5X name value. + /// + public const string OutputPriority = "OutputPriority"; + + /// + /// Gets the OutputRealTimeFormat L5X name value. + /// + public const string OutputRealTimeFormat = "OutputRealTimeFormat"; + + /// + /// Gets the OutputRedundantOwner L5X name value. + /// + public const string OutputRedundantOwner = "OutputRedundantOwner"; + + /// + /// Gets the OutputRPI L5X name value. + /// + public const string OutputRPI = "OutputRPI"; + + /// + /// Gets the OutputSize L5X name value. + /// + public const string OutputSize = "OutputSize"; + + /// + /// Gets the OutputSizeVariable L5X name value. + /// + public const string OutputSizeVariable = "OutputSizeVariable"; + + /// + /// Gets the OutputTag L5X name value. + /// + public const string OutputTag = "OutputTag"; + + /// + /// Gets the OutputTagArrayIndex L5X name value. + /// + public const string OutputTagArrayIndex = "OutputTagArrayIndex"; + + /// + /// Gets the OutputTagIOI L5X name value. + /// + public const string OutputTagIOI = "OutputTagIOI"; + + /// + /// Gets the OutputTagIOINTT L5X name value. + /// + public const string OutputTagIOINTT = "OutputTagIOINTT"; + + /// + /// Gets the OutputTagSuffix L5X name value. + /// + public const string OutputTagSuffix = "OutputTagSuffix"; + + /// + /// Gets the OutputTagType L5X name value. + /// + public const string OutputTagType = "OutputTagType"; + + /// + /// Gets the OutputTagUId L5X name value. + /// + public const string OutputTagUId = "OutputTagUId"; + + /// + /// Gets the OvertorqueLimit L5X name value. + /// + public const string OvertorqueLimit = "OvertorqueLimit"; + + /// + /// Gets the OvertorqueLimitTime L5X name value. + /// + public const string OvertorqueLimitTime = "OvertorqueLimitTime"; + + /// + /// Gets the Owner L5X name value. + /// + public const string Owner = "Owner"; + + /// + /// Gets the Parameter L5X name value. + /// + public const string Parameter = "Parameter"; + + /// + /// Gets the ParameterConnection L5X name value. + /// + public const string ParameterConnection = "ParameterConnection"; + + /// + /// Gets the ParameterConnectionCollectionType L5X name value. + /// + public const string ParameterConnectionCollectionType = "ParameterConnectionCollectionType"; + + /// + /// Gets the ParameterConnections L5X name value. + /// + public const string ParameterConnections = "ParameterConnections"; + + /// + /// Gets the ParameterConnectionType L5X name value. + /// + public const string ParameterConnectionType = "ParameterConnectionType"; + + /// + /// Gets the Parameters L5X name value. + /// + public const string Parameters = "Parameters"; + + /// + /// Gets the ParentModPortId L5X name value. + /// + public const string ParentModPortId = "ParentModPortId"; + + /// + /// Gets the ParentModule L5X name value. + /// + public const string ParentModule = "ParentModule"; + + /// + /// Gets the ParentModuleUId L5X name value. + /// + public const string ParentModuleUId = "ParentModuleUId"; + + /// + /// Gets the ParentUId L5X name value. + /// + public const string ParentUId = "ParentUId"; + + /// + /// Gets the Parity L5X name value. + /// + public const string Parity = "Parity"; + + /// + /// Gets the PassThroughConfiguration L5X name value. + /// + public const string PassThroughConfiguration = "PassThroughConfiguration"; + + /// + /// Gets the Pen L5X name value. + /// + public const string Pen = "Pen"; + + /// + /// Gets the PenCollectionType L5X name value. + /// + public const string PenCollectionType = "PenCollectionType"; + + /// + /// Gets the PendingEditsExist L5X name value. + /// + public const string PendingEditsExist = "PendingEditsExist"; + + /// + /// Gets the Pens L5X name value. + /// + public const string Pens = "Pens"; + + /// + /// Gets the PenType L5X name value. + /// + public const string PenType = "PenType"; + + /// + /// Gets the Period L5X name value. + /// + public const string Period = "Period"; + + /// + /// Gets the PeriodUnit L5X name value. + /// + public const string PeriodUnit = "PeriodUnit"; + + /// + /// Gets the PermissionSet L5X name value. + /// + public const string PermissionSet = "PermissionSet"; + + /// + /// Gets the PhaseIOI L5X name value. + /// + public const string PhaseIOI = "PhaseIOI"; + + /// + /// Gets the PhaseLossFaultAction L5X name value. + /// + public const string PhaseLossFaultAction = "PhaseLossFaultAction"; + + /// + /// Gets the PhaseName L5X name value. + /// + public const string PhaseName = "PhaseName"; + + /// + /// Gets the PhaseRotation L5X name value. + /// + public const string PhaseRotation = "PhaseRotation"; + + /// + /// Gets the PhaseShift L5X name value. + /// + public const string PhaseShift = "PhaseShift"; + + /// + /// Gets the PingMethod L5X name value. + /// + public const string PingMethod = "PingMethod"; + + /// + /// Gets the PingMethodType L5X name value. + /// + public const string PingMethodType = "PingMethodType"; + + /// + /// Gets the PLC2Mapping L5X name value. + /// + public const string PLC2Mapping = "PLC2Mapping"; + + /// + /// Gets the PLCMappingFile L5X name value. + /// + public const string PLCMappingFile = "PLCMappingFile"; + + /// + /// Gets the PMMotorExtendedSpeedPermissive L5X name value. + /// + public const string PMMotorExtendedSpeedPermissive = "PMMotorExtendedSpeedPermissive"; + + /// + /// Gets the PMMotorFluxSaturation L5X name value. + /// + public const string PMMotorFluxSaturation = "PMMotorFluxSaturation"; + + /// + /// Gets the PMMotorForceConstant L5X name value. + /// + public const string PMMotorForceConstant = "PMMotorForceConstant"; + + /// + /// Gets the PMMotorInductance L5X name value. + /// + public const string PMMotorInductance = "PMMotorInductance"; + + /// + /// Gets the PMMotorLdFluxSaturation L5X name value. + /// + public const string PMMotorLdFluxSaturation = "PMMotorLdFluxSaturation"; + + /// + /// Gets the PMMotorLdInductance L5X name value. + /// + public const string PMMotorLdInductance = "PMMotorLdInductance"; + + /// + /// Gets the PMMotorLinearBusOvervoltageSpeed L5X name value. + /// + public const string PMMotorLinearBusOvervoltageSpeed = "PMMotorLinearBusOvervoltageSpeed"; + + /// + /// Gets the PMMotorLinearMaxExtendedSpeed L5X name value. + /// + public const string PMMotorLinearMaxExtendedSpeed = "PMMotorLinearMaxExtendedSpeed"; + + /// + /// Gets the PMMotorLinearVoltageConstant L5X name value. + /// + public const string PMMotorLinearVoltageConstant = "PMMotorLinearVoltageConstant"; + + /// + /// Gets the PMMotorLqFluxSaturation L5X name value. + /// + public const string PMMotorLqFluxSaturation = "PMMotorLqFluxSaturation"; + + /// + /// Gets the PMMotorLqInductance L5X name value. + /// + public const string PMMotorLqInductance = "PMMotorLqInductance"; + + /// + /// Gets the PMMotorRatedForce L5X name value. + /// + public const string PMMotorRatedForce = "PMMotorRatedForce"; + + /// + /// Gets the PMMotorRatedTorque L5X name value. + /// + public const string PMMotorRatedTorque = "PMMotorRatedTorque"; + + /// + /// Gets the PMMotorResistance L5X name value. + /// + public const string PMMotorResistance = "PMMotorResistance"; + + /// + /// Gets the PMMotorRotaryBusOvervoltageSpeed L5X name value. + /// + public const string PMMotorRotaryBusOvervoltageSpeed = "PMMotorRotaryBusOvervoltageSpeed"; + + /// + /// Gets the PMMotorRotaryMaxExtendedSpeed L5X name value. + /// + public const string PMMotorRotaryMaxExtendedSpeed = "PMMotorRotaryMaxExtendedSpeed"; + + /// + /// Gets the PMMotorRotaryVoltageConstant L5X name value. + /// + public const string PMMotorRotaryVoltageConstant = "PMMotorRotaryVoltageConstant"; + + /// + /// Gets the PMMotorTorqueConstant L5X name value. + /// + public const string PMMotorTorqueConstant = "PMMotorTorqueConstant"; + + /// + /// Gets the PollingMode L5X name value. + /// + public const string PollingMode = "PollingMode"; + + /// + /// Gets the Port L5X name value. + /// + public const string Port = "Port"; + + /// + /// Gets the PortCollectionType L5X name value. + /// + public const string PortCollectionType = "PortCollectionType"; + + /// + /// Gets the PortEnabled L5X name value. + /// + public const string PortEnabled = "PortEnabled"; + + /// + /// Gets the Ports L5X name value. + /// + public const string Ports = "Ports"; + + /// + /// Gets the PortType L5X name value. + /// + public const string PortType = "PortType"; + + /// + /// Gets the PositionDataScaling L5X name value. + /// + public const string PositionDataScaling = "PositionDataScaling"; + + /// + /// Gets the PositionDataScalingExp L5X name value. + /// + public const string PositionDataScalingExp = "PositionDataScalingExp"; + + /// + /// Gets the PositionDataScalingFactor L5X name value. + /// + public const string PositionDataScalingFactor = "PositionDataScalingFactor"; + + /// + /// Gets the PositionDifferentialGain L5X name value. + /// + public const string PositionDifferentialGain = "PositionDifferentialGain"; + + /// + /// Gets the PositionErrorFaultAction L5X name value. + /// + public const string PositionErrorFaultAction = "PositionErrorFaultAction"; + + /// + /// Gets the PositionErrorTolerance L5X name value. + /// + public const string PositionErrorTolerance = "PositionErrorTolerance"; + + /// + /// Gets the PositionErrorToleranceTime L5X name value. + /// + public const string PositionErrorToleranceTime = "PositionErrorToleranceTime"; + + /// + /// Gets the PositionIntegralGain L5X name value. + /// + public const string PositionIntegralGain = "PositionIntegralGain"; + + /// + /// Gets the PositionIntegratorBandwidth L5X name value. + /// + public const string PositionIntegratorBandwidth = "PositionIntegratorBandwidth"; + + /// + /// Gets the PositionIntegratorControl L5X name value. + /// + public const string PositionIntegratorControl = "PositionIntegratorControl"; + + /// + /// Gets the PositionIntegratorPreload L5X name value. + /// + public const string PositionIntegratorPreload = "PositionIntegratorPreload"; + + /// + /// Gets the PositionLeadLagFilterBandwidth L5X name value. + /// + public const string PositionLeadLagFilterBandwidth = "PositionLeadLagFilterBandwidth"; + + /// + /// Gets the PositionLeadLagFilterGain L5X name value. + /// + public const string PositionLeadLagFilterGain = "PositionLeadLagFilterGain"; + + /// + /// Gets the PositionLockTolerance L5X name value. + /// + public const string PositionLockTolerance = "PositionLockTolerance"; + + /// + /// Gets the PositionLoopBandwidth L5X name value. + /// + public const string PositionLoopBandwidth = "PositionLoopBandwidth"; + + /// + /// Gets the PositionNotchFilterFrequency L5X name value. + /// + public const string PositionNotchFilterFrequency = "PositionNotchFilterFrequency"; + + /// + /// Gets the PositionProportionalGain L5X name value. + /// + public const string PositionProportionalGain = "PositionProportionalGain"; + + /// + /// Gets the PositionScalingDenominator L5X name value. + /// + public const string PositionScalingDenominator = "PositionScalingDenominator"; + + /// + /// Gets the PositionScalingNumerator L5X name value. + /// + public const string PositionScalingNumerator = "PositionScalingNumerator"; + + /// + /// Gets the PositionServoBandwidth L5X name value. + /// + public const string PositionServoBandwidth = "PositionServoBandwidth"; + + /// + /// Gets the PositionUnits L5X name value. + /// + public const string PositionUnits = "PositionUnits"; + + /// + /// Gets the PositionUnwind L5X name value. + /// + public const string PositionUnwind = "PositionUnwind"; + + /// + /// Gets the PositionUnwindDenominator L5X name value. + /// + public const string PositionUnwindDenominator = "PositionUnwindDenominator"; + + /// + /// Gets the PositionUnwindNumerator L5X name value. + /// + public const string PositionUnwindNumerator = "PositionUnwindNumerator"; + + /// + /// Gets the PostSamples L5X name value. + /// + public const string PostSamples = "PostSamples"; + + /// + /// Gets the PostSampleType L5X name value. + /// + public const string PostSampleType = "PostSampleType"; + + /// + /// Gets the PostSampleUnit L5X name value. + /// + public const string PostSampleUnit = "PostSampleUnit"; + + /// + /// Gets the PowerDeviceCompensationEnable L5X name value. + /// + public const string PowerDeviceCompensationEnable = "PowerDeviceCompensationEnable"; + + /// + /// Gets the PowerDeviceDeadTimeCompensation L5X name value. + /// + public const string PowerDeviceDeadTimeCompensation = "PowerDeviceDeadTimeCompensation"; + + /// + /// Gets the PowerLossAction L5X name value. + /// + public const string PowerLossAction = "PowerLossAction"; + + /// + /// Gets the PowerLossProgram L5X name value. + /// + public const string PowerLossProgram = "PowerLossProgram"; + + /// + /// Gets the PowerLossProgramUId L5X name value. + /// + public const string PowerLossProgramUId = "PowerLossProgramUId"; + + /// + /// Gets the PowerLossThreshold L5X name value. + /// + public const string PowerLossThreshold = "PowerLossThreshold"; + + /// + /// Gets the PowerLossTime L5X name value. + /// + public const string PowerLossTime = "PowerLossTime"; + + /// + /// Gets the PowerSupplyID L5X name value. + /// + public const string PowerSupplyID = "PowerSupplyID"; + + /// + /// Gets the PreChargeHoldControl L5X name value. + /// + public const string PreChargeHoldControl = "PreChargeHoldControl"; + + /// + /// Gets the PreChargeHoldDelay L5X name value. + /// + public const string PreChargeHoldDelay = "PreChargeHoldDelay"; + + /// + /// Gets the PreSamples L5X name value. + /// + public const string PreSamples = "PreSamples"; + + /// + /// Gets the PreSampleType L5X name value. + /// + public const string PreSampleType = "PreSampleType"; + + /// + /// Gets the PreSampleUnit L5X name value. + /// + public const string PreSampleUnit = "PreSampleUnit"; + + /// + /// Gets the Preset L5X name value. + /// + public const string Preset = "Preset"; + + /// + /// Gets the PresetUsesExpr L5X name value. + /// + public const string PresetUsesExpr = "PresetUsesExpr"; + + /// + /// Gets the PreStateRoutineName L5X name value. + /// + public const string PreStateRoutineName = "PreStateRoutineName"; + + /// + /// Gets the PrimaryActionSet L5X name value. + /// + public const string PrimaryActionSet = "PrimaryActionSet"; + + /// + /// Gets the PrimaryActionSetAdaptorType L5X name value. + /// + public const string PrimaryActionSetAdaptorType = "PrimaryActionSetAdaptorType"; + + /// + /// Gets the PrimaryActionSetCollectionType L5X name value. + /// + public const string PrimaryActionSetCollectionType = "PrimaryActionSetCollectionType"; + + /// + /// Gets the PrimaryActionSets L5X name value. + /// + public const string PrimaryActionSets = "PrimaryActionSets"; + + /// + /// Gets the PrimaryActionSetTextType L5X name value. + /// + public const string PrimaryActionSetTextType = "PrimaryActionSetTextType"; + + /// + /// Gets the PrimaryDNS L5X name value. + /// + public const string PrimaryDNS = "PrimaryDNS"; + + /// + /// Gets the PrimCxnInputSize L5X name value. + /// + public const string PrimCxnInputSize = "PrimCxnInputSize"; + + /// + /// Gets the PrimCxnOutputSize L5X name value. + /// + public const string PrimCxnOutputSize = "PrimCxnOutputSize"; + + /// + /// Gets the Priority L5X name value. + /// + public const string Priority = "Priority"; + + /// + /// Gets the Priority1 L5X name value. + /// + public const string Priority1 = "Priority1"; + + /// + /// Gets the Priority2 L5X name value. + /// + public const string Priority2 = "Priority2"; + + /// + /// Gets the PriorityPollNodeFile L5X name value. + /// + public const string PriorityPollNodeFile = "PriorityPollNodeFile"; + + /// + /// Gets the ProcessorType L5X name value. + /// + public const string ProcessorType = "ProcessorType"; + + /// + /// Gets the ProduceCount L5X name value. + /// + public const string ProduceCount = "ProduceCount"; + + /// + /// Gets the ProduceInfo L5X name value. + /// + public const string ProduceInfo = "ProduceInfo"; + + /// + /// Gets the Producer L5X name value. + /// + public const string Producer = "Producer"; + + /// + /// Gets the ProduceTagInfoType L5X name value. + /// + public const string ProduceTagInfoType = "ProduceTagInfoType"; + + /// + /// Gets the ProductCode L5X name value. + /// + public const string ProductCode = "ProductCode"; + + /// + /// Gets the ProductType L5X name value. + /// + public const string ProductType = "ProductType"; + + /// + /// Gets the ProfileDefinition L5X name value. + /// + public const string ProfileDefinition = "ProfileDefinition"; + + /// + /// Gets the ProfileReferences L5X name value. + /// + public const string ProfileReferences = "ProfileReferences"; + + /// + /// Gets the ProgAck L5X name value. + /// + public const string ProgAck = "ProgAck"; + + /// + /// Gets the ProgAckAll L5X name value. + /// + public const string ProgAckAll = "ProgAckAll"; + + /// + /// Gets the ProgDisable L5X name value. + /// + public const string ProgDisable = "ProgDisable"; + + /// + /// Gets the ProgEnable L5X name value. + /// + public const string ProgEnable = "ProgEnable"; + + /// + /// Gets the ProgFB L5X name value. + /// + public const string ProgFB = "ProgFB"; + + /// + /// Gets the Program L5X name value. + /// + public const string Program = "Program"; + + /// + /// Gets the ProgramCollectionType L5X name value. + /// + public const string ProgramCollectionType = "ProgramCollectionType"; + + /// + /// Gets the ProgrammaticallySendEventTrigger L5X name value. + /// + public const string ProgrammaticallySendEventTrigger = "ProgrammaticallySendEventTrigger"; + + /// + /// Gets the ProgrammedStopMode L5X name value. + /// + public const string ProgrammedStopMode = "ProgrammedStopMode"; + + /// + /// Gets the Programs L5X name value. + /// + public const string Programs = "Programs"; + + /// + /// Gets the ProgramsUId L5X name value. + /// + public const string ProgramsUId = "ProgramsUId"; + + /// + /// Gets the ProgReset L5X name value. + /// + public const string ProgReset = "ProgReset"; + + /// + /// Gets the ProgSuppress L5X name value. + /// + public const string ProgSuppress = "ProgSuppress"; + + /// + /// Gets the ProgTime L5X name value. + /// + public const string ProgTime = "ProgTime"; + + /// + /// Gets the ProgUnshelve L5X name value. + /// + public const string ProgUnshelve = "ProgUnshelve"; + + /// + /// Gets the ProgUnshelveAll L5X name value. + /// + public const string ProgUnshelveAll = "ProgUnshelveAll"; + + /// + /// Gets the ProgUnsuppress L5X name value. + /// + public const string ProgUnsuppress = "ProgUnsuppress"; + + /// + /// Gets the ProjectCreationDate L5X name value. + /// + public const string ProjectCreationDate = "ProjectCreationDate"; + + /// + /// Gets the ProjectSN L5X name value. + /// + public const string ProjectSN = "ProjectSN"; + + /// + /// Gets the ProjectType L5X name value. + /// + public const string ProjectType = "ProjectType"; + + /// + /// Gets the Provider L5X name value. + /// + public const string Provider = "Provider"; + + /// + /// Gets the ProvingConfiguration L5X name value. + /// + public const string ProvingConfiguration = "ProvingConfiguration"; + + /// + /// Gets the PTPEnable L5X name value. + /// + public const string PTPEnable = "PTPEnable"; + + /// + /// Gets the PublicKey L5X name value. + /// + public const string PublicKey = "PublicKey"; + + /// + /// Gets the PWMFrequencySelect L5X name value. + /// + public const string PWMFrequencySelect = "PWMFrequencySelect"; + + /// + /// Gets the Qualifier L5X name value. + /// + public const string Qualifier = "Qualifier"; + + /// + /// Gets the QuickWatchAdaptorType L5X name value. + /// + public const string QuickWatchAdaptorType = "QuickWatchAdaptorType"; + + /// + /// Gets the QuickWatchCollectionAdaptorType L5X name value. + /// + public const string QuickWatchCollectionAdaptorType = "QuickWatchCollectionAdaptorType"; + + /// + /// Gets the QuickWatchList L5X name value. + /// + public const string QuickWatchList = "QuickWatchList"; + + /// + /// Gets the QuickWatchLists L5X name value. + /// + public const string QuickWatchLists = "QuickWatchLists"; + + /// + /// Gets the QuickWatchTagAdaptorType L5X name value. + /// + public const string QuickWatchTagAdaptorType = "QuickWatchTagAdaptorType"; + + /// + /// Gets the Rack L5X name value. + /// + public const string Rack = "Rack"; + + /// + /// Gets the RackConnection L5X name value. + /// + public const string RackConnection = "RackConnection"; + + /// + /// Gets the RackConnectionType L5X name value. + /// + public const string RackConnectionType = "RackConnectionType"; + + /// + /// Gets the Radix L5X name value. + /// + public const string Radix = "Radix"; + + /// + /// Gets the RampJerkControl L5X name value. + /// + public const string RampJerkControl = "RampJerkControl"; + + /// + /// Gets the Rate L5X name value. + /// + public const string Rate = "Rate"; + + /// + /// Gets the RawDataType L5X name value. + /// + public const string RawDataType = "RawDataType"; + + /// + /// Gets the RawDefaultDataType L5X name value. + /// + public const string RawDefaultDataType = "RawDefaultDataType"; + + /// + /// Gets the RawForceDataType L5X name value. + /// + public const string RawForceDataType = "RawForceDataType"; + + /// + /// Gets the RcpGatewayAddress1 L5X name value. + /// + public const string RcpGatewayAddress1 = "RcpGatewayAddress1"; + + /// + /// Gets the RcpGatewayAddress2 L5X name value. + /// + public const string RcpGatewayAddress2 = "RcpGatewayAddress2"; + + /// + /// Gets the ReactionTimeLimit L5X name value. + /// + public const string ReactionTimeLimit = "ReactionTimeLimit"; + + /// + /// Gets the ReactiveCurrentCommand L5X name value. + /// + public const string ReactiveCurrentCommand = "ReactiveCurrentCommand"; + + /// + /// Gets the ReactiveCurrentRateLimit L5X name value. + /// + public const string ReactiveCurrentRateLimit = "ReactiveCurrentRateLimit"; + + /// + /// Gets the ReactivePowerControl L5X name value. + /// + public const string ReactivePowerControl = "ReactivePowerControl"; + + /// + /// Gets the ReactivePowerRateLimit L5X name value. + /// + public const string ReactivePowerRateLimit = "ReactivePowerRateLimit"; + + /// + /// Gets the ReactivePowerSetPoint L5X name value. + /// + public const string ReactivePowerSetPoint = "ReactivePowerSetPoint"; + + /// + /// Gets the RealEnergyConsumedOdometer L5X name value. + /// + public const string RealEnergyConsumedOdometer = "RealEnergyConsumedOdometer"; + + /// + /// Gets the RealEnergyGeneratedOdometer L5X name value. + /// + public const string RealEnergyGeneratedOdometer = "RealEnergyGeneratedOdometer"; + + /// + /// Gets the RealEnergyNetOdometer L5X name value. + /// + public const string RealEnergyNetOdometer = "RealEnergyNetOdometer"; + + /// + /// Gets the RecipePhaseNames L5X name value. + /// + public const string RecipePhaseNames = "RecipePhaseNames"; + + /// + /// Gets the RedundancyInfo L5X name value. + /// + public const string RedundancyInfo = "RedundancyInfo"; + + /// + /// Gets the RedundancyInfoType L5X name value. + /// + public const string RedundancyInfoType = "RedundancyInfoType"; + + /// + /// Gets the RedundancyType L5X name value. + /// + public const string RedundancyType = "RedundancyType"; + + /// + /// Gets the RedundancyUId L5X name value. + /// + public const string RedundancyUId = "RedundancyUId"; + + /// + /// Gets the RegionId L5X name value. + /// + public const string RegionId = "RegionId"; + + /// + /// Gets the RegionIdList L5X name value. + /// + public const string RegionIdList = "RegionIdList"; + + /// + /// Gets the RegistrationInputs L5X name value. + /// + public const string RegistrationInputs = "RegistrationInputs"; + + /// + /// Gets the Relaxed L5X name value. + /// + public const string Relaxed = "Relaxed"; + + /// + /// Gets the RemoteElement L5X name value. + /// + public const string RemoteElement = "RemoteElement"; + + /// + /// Gets the RemoteIndex L5X name value. + /// + public const string RemoteIndex = "RemoteIndex"; + + /// + /// Gets the RemoteInstance L5X name value. + /// + public const string RemoteInstance = "RemoteInstance"; + + /// + /// Gets the RemoteModeChangeFlag L5X name value. + /// + public const string RemoteModeChangeFlag = "RemoteModeChangeFlag"; + + /// + /// Gets the RemoteTag L5X name value. + /// + public const string RemoteTag = "RemoteTag"; + + /// + /// Gets the ReplyMessageWait L5X name value. + /// + public const string ReplyMessageWait = "ReplyMessageWait"; + + /// + /// Gets the ReportMinorOverflow L5X name value. + /// + public const string ReportMinorOverflow = "ReportMinorOverflow"; + + /// + /// Gets the RequestedLength L5X name value. + /// + public const string RequestedLength = "RequestedLength"; + + /// + /// Gets the Required L5X name value. + /// + public const string Required = "Required"; + + /// + /// Gets the ResistiveBrakeContactDelay L5X name value. + /// + public const string ResistiveBrakeContactDelay = "ResistiveBrakeContactDelay"; + + /// + /// Gets the Ret L5X name value. + /// + public const string Ret = "Ret"; + + /// + /// Gets the RET L5X name value. + /// + public const string RET = "RET"; + + /// + /// Gets the RetainSequenceIDOnReset L5X name value. + /// + public const string RetainSequenceIDOnReset = "RetainSequenceIDOnReset"; + + /// + /// Gets the RetIOI L5X name value. + /// + public const string RetIOI = "RetIOI"; + + /// + /// Gets the RetIOINTT L5X name value. + /// + public const string RetIOINTT = "RetIOINTT"; + + /// + /// Gets the RetRegionId L5X name value. + /// + public const string RetRegionId = "RetRegionId"; + + /// + /// Gets the RetToNormalTime L5X name value. + /// + public const string RetToNormalTime = "RetToNormalTime"; + + /// + /// Gets the Revision L5X name value. + /// + public const string Revision = "Revision"; + + /// + /// Gets the RevisionExtension L5X name value. + /// + public const string RevisionExtension = "RevisionExtension"; + + /// + /// Gets the RevisionNote L5X name value. + /// + public const string RevisionNote = "RevisionNote"; + + /// + /// Gets the RevisionNoteTextType L5X name value. + /// + public const string RevisionNoteTextType = "RevisionNoteTextType"; + + /// + /// Gets the RevisionNoteType L5X name value. + /// + public const string RevisionNoteType = "RevisionNoteType"; + + /// + /// Gets the RLLContent L5X name value. + /// + public const string RLLContent = "RLLContent"; + + /// + /// Gets the RobotJointsDirectionSenseBits L5X name value. + /// + public const string RobotJointsDirectionSenseBits = "RobotJointsDirectionSenseBits"; + + /// + /// Gets the ROCNegAcked L5X name value. + /// + public const string ROCNegAcked = "ROCNegAcked"; + + /// + /// Gets the ROCNegAlarmCnt L5X name value. + /// + public const string ROCNegAlarmCnt = "ROCNegAlarmCnt"; + + /// + /// Gets the ROCNegInAlarm L5X name value. + /// + public const string ROCNegInAlarm = "ROCNegInAlarm"; + + /// + /// Gets the ROCNegInAlarmTime L5X name value. + /// + public const string ROCNegInAlarmTime = "ROCNegInAlarmTime"; + + /// + /// Gets the ROCNegInAlarmUnack L5X name value. + /// + public const string ROCNegInAlarmUnack = "ROCNegInAlarmUnack"; + + /// + /// Gets the ROCNegLimit L5X name value. + /// + public const string ROCNegLimit = "ROCNegLimit"; + + /// + /// Gets the ROCNegLimitInv L5X name value. + /// + public const string ROCNegLimitInv = "ROCNegLimitInv"; + + /// + /// Gets the ROCNegOperAck L5X name value. + /// + public const string ROCNegOperAck = "ROCNegOperAck"; + + /// + /// Gets the ROCNegOperShelve L5X name value. + /// + public const string ROCNegOperShelve = "ROCNegOperShelve"; + + /// + /// Gets the ROCNegOperUnshelve L5X name value. + /// + public const string ROCNegOperUnshelve = "ROCNegOperUnshelve"; + + /// + /// Gets the ROCNegProgAck L5X name value. + /// + public const string ROCNegProgAck = "ROCNegProgAck"; + + /// + /// Gets the ROCNegSeverity L5X name value. + /// + public const string ROCNegSeverity = "ROCNegSeverity"; + + /// + /// Gets the ROCNegShelved L5X name value. + /// + public const string ROCNegShelved = "ROCNegShelved"; + + /// + /// Gets the ROCPeriod L5X name value. + /// + public const string ROCPeriod = "ROCPeriod"; + + /// + /// Gets the ROCPeriodInv L5X name value. + /// + public const string ROCPeriodInv = "ROCPeriodInv"; + + /// + /// Gets the ROCPosAcked L5X name value. + /// + public const string ROCPosAcked = "ROCPosAcked"; + + /// + /// Gets the ROCPosAlarmCnt L5X name value. + /// + public const string ROCPosAlarmCnt = "ROCPosAlarmCnt"; + + /// + /// Gets the ROCPosInAlarm L5X name value. + /// + public const string ROCPosInAlarm = "ROCPosInAlarm"; + + /// + /// Gets the ROCPosInAlarmTime L5X name value. + /// + public const string ROCPosInAlarmTime = "ROCPosInAlarmTime"; + + /// + /// Gets the ROCPosInAlarmUnack L5X name value. + /// + public const string ROCPosInAlarmUnack = "ROCPosInAlarmUnack"; + + /// + /// Gets the ROCPosLimit L5X name value. + /// + public const string ROCPosLimit = "ROCPosLimit"; + + /// + /// Gets the ROCPosLimitInv L5X name value. + /// + public const string ROCPosLimitInv = "ROCPosLimitInv"; + + /// + /// Gets the ROCPosOperAck L5X name value. + /// + public const string ROCPosOperAck = "ROCPosOperAck"; + + /// + /// Gets the ROCPosOperShelve L5X name value. + /// + public const string ROCPosOperShelve = "ROCPosOperShelve"; + + /// + /// Gets the ROCPosOperUnshelve L5X name value. + /// + public const string ROCPosOperUnshelve = "ROCPosOperUnshelve"; + + /// + /// Gets the ROCPosProgAck L5X name value. + /// + public const string ROCPosProgAck = "ROCPosProgAck"; + + /// + /// Gets the ROCPosSeverity L5X name value. + /// + public const string ROCPosSeverity = "ROCPosSeverity"; + + /// + /// Gets the ROCPosShelved L5X name value. + /// + public const string ROCPosShelved = "ROCPosShelved"; + + /// + /// Gets the RotaryAxis L5X name value. + /// + public const string RotaryAxis = "RotaryAxis"; + + /// + /// Gets the RotaryMotorDampingCoefficient L5X name value. + /// + public const string RotaryMotorDampingCoefficient = "RotaryMotorDampingCoefficient"; + + /// + /// Gets the RotaryMotorFanCoolingDerating L5X name value. + /// + public const string RotaryMotorFanCoolingDerating = "RotaryMotorFanCoolingDerating"; + + /// + /// Gets the RotaryMotorFanCoolingSpeed L5X name value. + /// + public const string RotaryMotorFanCoolingSpeed = "RotaryMotorFanCoolingSpeed"; + + /// + /// Gets the RotaryMotorInertia L5X name value. + /// + public const string RotaryMotorInertia = "RotaryMotorInertia"; + + /// + /// Gets the RotaryMotorMaxSpeed L5X name value. + /// + public const string RotaryMotorMaxSpeed = "RotaryMotorMaxSpeed"; + + /// + /// Gets the RotaryMotorPoles L5X name value. + /// + public const string RotaryMotorPoles = "RotaryMotorPoles"; + + /// + /// Gets the RotaryMotorRatedSpeed L5X name value. + /// + public const string RotaryMotorRatedSpeed = "RotaryMotorRatedSpeed"; + + /// + /// Gets the RotationalPosResolution L5X name value. + /// + public const string RotationalPosResolution = "RotationalPosResolution"; + + /// + /// Gets the Routine L5X name value. + /// + public const string Routine = "Routine"; + + /// + /// Gets the RoutineCollectionType L5X name value. + /// + public const string RoutineCollectionType = "RoutineCollectionType"; + + /// + /// Gets the RoutineIOI L5X name value. + /// + public const string RoutineIOI = "RoutineIOI"; + + /// + /// Gets the RoutineIOINTT L5X name value. + /// + public const string RoutineIOINTT = "RoutineIOINTT"; + + /// + /// Gets the Routines L5X name value. + /// + public const string Routines = "Routines"; + + /// + /// Gets the RoutinesUId L5X name value. + /// + public const string RoutinesUId = "RoutinesUId"; + + /// + /// Gets the RoutineType L5X name value. + /// + public const string RoutineType = "RoutineType"; + + /// + /// Gets the RPI L5X name value. + /// + public const string RPI = "RPI"; + + /// + /// Gets the RSLogix5000Content L5X name value. + /// + public const string RSLogix5000Content = "RSLogix5000Content"; + + /// + /// Gets the RTSOffDelay L5X name value. + /// + public const string RTSOffDelay = "RTSOffDelay"; + + /// + /// Gets the RTSSendDelay L5X name value. + /// + public const string RTSSendDelay = "RTSSendDelay"; + + /// + /// Gets the RunBoost L5X name value. + /// + public const string RunBoost = "RunBoost"; + + /// + /// Gets the Rung L5X name value. + /// + public const string Rung = "Rung"; + + /// + /// Gets the RungCollectionType L5X name value. + /// + public const string RungCollectionType = "RungCollectionType"; + + /// + /// Gets the RungCommentTextType L5X name value. + /// + public const string RungCommentTextType = "RungCommentTextType"; + + /// + /// Gets the RungCommentType L5X name value. + /// + public const string RungCommentType = "RungCommentType"; + + /// + /// Gets the RungId L5X name value. + /// + public const string RungId = "RungId"; + + /// + /// Gets the RungType L5X name value. + /// + public const string RungType = "RungType"; + + /// + /// Gets the SafeStoppingAction L5X name value. + /// + public const string SafeStoppingAction = "SafeStoppingAction"; + + /// + /// Gets the SafeStoppingActionSource L5X name value. + /// + public const string SafeStoppingActionSource = "SafeStoppingActionSource"; + + /// + /// Gets the SafeTorqueOffAction L5X name value. + /// + public const string SafeTorqueOffAction = "SafeTorqueOffAction"; + + /// + /// Gets the SafeTorqueOffActionSource L5X name value. + /// + public const string SafeTorqueOffActionSource = "SafeTorqueOffActionSource"; + + /// + /// Gets the SafetyControllerType L5X name value. + /// + public const string SafetyControllerType = "SafetyControllerType"; + + /// + /// Gets the SafetyEnabled L5X name value. + /// + public const string SafetyEnabled = "SafetyEnabled"; + + /// + /// Gets the SafetyFaultAction L5X name value. + /// + public const string SafetyFaultAction = "SafetyFaultAction"; + + /// + /// Gets the SafetyInfo L5X name value. + /// + public const string SafetyInfo = "SafetyInfo"; + + /// + /// Gets the SafetyInfoType L5X name value. + /// + public const string SafetyInfoType = "SafetyInfoType"; + + /// + /// Gets the SafetyLevel L5X name value. + /// + public const string SafetyLevel = "SafetyLevel"; + + /// + /// Gets the SafetyLocked L5X name value. + /// + public const string SafetyLocked = "SafetyLocked"; + + /// + /// Gets the SafetyLockPassword L5X name value. + /// + public const string SafetyLockPassword = "SafetyLockPassword"; + + /// + /// Gets the SafetyNetwork L5X name value. + /// + public const string SafetyNetwork = "SafetyNetwork"; + + /// + /// Gets the SafetyScript L5X name value. + /// + public const string SafetyScript = "SafetyScript"; + + /// + /// Gets the SafetyScriptType L5X name value. + /// + public const string SafetyScriptType = "SafetyScriptType"; + + /// + /// Gets the SafetySignature L5X name value. + /// + public const string SafetySignature = "SafetySignature"; + + /// + /// Gets the SafetySignatureID L5X name value. + /// + public const string SafetySignatureID = "SafetySignatureID"; + + /// + /// Gets the SafetySigned L5X name value. + /// + public const string SafetySigned = "SafetySigned"; + + /// + /// Gets the SafetyTagMap L5X name value. + /// + public const string SafetyTagMap = "SafetyTagMap"; + + /// + /// Gets the SafetyTaskFaultString L5X name value. + /// + public const string SafetyTaskFaultString = "SafetyTaskFaultString"; + + /// + /// Gets the SafetyUId L5X name value. + /// + public const string SafetyUId = "SafetyUId"; + + /// + /// Gets the SafetyUnlockPassword L5X name value. + /// + public const string SafetyUnlockPassword = "SafetyUnlockPassword"; + + /// + /// Gets the SampleEvent L5X name value. + /// + public const string SampleEvent = "SampleEvent"; + + /// + /// Gets the SamplePeriod L5X name value. + /// + public const string SamplePeriod = "SamplePeriod"; + + /// + /// Gets the SBR L5X name value. + /// + public const string SBR = "SBR"; + + /// + /// Gets the SbrRet L5X name value. + /// + public const string SbrRet = "SbrRet"; + + /// + /// Gets the ScalingSource L5X name value. + /// + public const string ScalingSource = "ScalingSource"; + + /// + /// Gets the ScheduledProgram L5X name value. + /// + public const string ScheduledProgram = "ScheduledProgram"; + + /// + /// Gets the ScheduledProgramCollectionType L5X name value. + /// + public const string ScheduledProgramCollectionType = "ScheduledProgramCollectionType"; + + /// + /// Gets the ScheduledPrograms L5X name value. + /// + public const string ScheduledPrograms = "ScheduledPrograms"; + + /// + /// Gets the ScheduledProgramType L5X name value. + /// + public const string ScheduledProgramType = "ScheduledProgramType"; + + /// + /// Gets the SchemaRevision L5X name value. + /// + public const string SchemaRevision = "SchemaRevision"; + + /// + /// Gets the Scope L5X name value. + /// + public const string Scope = "Scope"; + + /// + /// Gets the SecCxnInputSize L5X name value. + /// + public const string SecCxnInputSize = "SecCxnInputSize"; + + /// + /// Gets the SecCxnOutputSize L5X name value. + /// + public const string SecCxnOutputSize = "SecCxnOutputSize"; + + /// + /// Gets the SecondaryDNS L5X name value. + /// + public const string SecondaryDNS = "SecondaryDNS"; + + /// + /// Gets the SecondarySecurityAuthorityID L5X name value. + /// + public const string SecondarySecurityAuthorityID = "SecondarySecurityAuthorityID"; + + /// + /// Gets the SecondarySecurityAuthorityURI L5X name value. + /// + public const string SecondarySecurityAuthorityURI = "SecondarySecurityAuthorityURI"; + + /// + /// Gets the Security L5X name value. + /// + public const string Security = "Security"; + + /// + /// Gets the SecurityAuthorityID L5X name value. + /// + public const string SecurityAuthorityID = "SecurityAuthorityID"; + + /// + /// Gets the SecurityAuthorityURI L5X name value. + /// + public const string SecurityAuthorityURI = "SecurityAuthorityURI"; + + /// + /// Gets the SecurityInfoType L5X name value. + /// + public const string SecurityInfoType = "SecurityInfoType"; + + /// + /// Gets the SensorlessVectorBoostFilterBandwidth L5X name value. + /// + public const string SensorlessVectorBoostFilterBandwidth = "SensorlessVectorBoostFilterBandwidth"; + + /// + /// Gets the SensorlessVectorEconomyAccelDecelKi L5X name value. + /// + public const string SensorlessVectorEconomyAccelDecelKi = "SensorlessVectorEconomyAccelDecelKi"; + + /// + /// Gets the SensorlessVectorEconomyAccelDecelKp L5X name value. + /// + public const string SensorlessVectorEconomyAccelDecelKp = "SensorlessVectorEconomyAccelDecelKp"; + + /// + /// Gets the SensorlessVectorEconomyAtSpeedKi L5X name value. + /// + public const string SensorlessVectorEconomyAtSpeedKi = "SensorlessVectorEconomyAtSpeedKi"; + + /// + /// Gets the SEQContent L5X name value. + /// + public const string SEQContent = "SEQContent"; + + /// + /// Gets the SEQContentType L5X name value. + /// + public const string SEQContentType = "SEQContentType"; + + /// + /// Gets the Sequencing L5X name value. + /// + public const string Sequencing = "Sequencing"; + + /// + /// Gets the SerialPort L5X name value. + /// + public const string SerialPort = "SerialPort"; + + /// + /// Gets the SerialPortType L5X name value. + /// + public const string SerialPortType = "SerialPortType"; + + /// + /// Gets the ServiceCode L5X name value. + /// + public const string ServiceCode = "ServiceCode"; + + /// + /// Gets the ServoFeedbackType L5X name value. + /// + public const string ServoFeedbackType = "ServoFeedbackType"; + + /// + /// Gets the ServoLoopConfiguration L5X name value. + /// + public const string ServoLoopConfiguration = "ServoLoopConfiguration"; + + /// + /// Gets the ServoPolarityBits L5X name value. + /// + public const string ServoPolarityBits = "ServoPolarityBits"; + + /// + /// Gets the Severity L5X name value. + /// + public const string Severity = "Severity"; + + /// + /// Gets the SeverityInv L5X name value. + /// + public const string SeverityInv = "SeverityInv"; + + /// + /// Gets the SFC_ActionElementType L5X name value. + /// + public const string SFC_ActionElementType = "SFC_ActionElementType"; + + /// + /// Gets the SFC_SBR_RETType L5X name value. + /// + public const string SFC_SBR_RETType = "SFC_SBR_RETType"; + + /// + /// Gets the SFCContent L5X name value. + /// + public const string SFCContent = "SFCContent"; + + /// + /// Gets the SFCContentType L5X name value. + /// + public const string SFCContentType = "SFCContentType"; + + /// + /// Gets the SFCExecutionControl L5X name value. + /// + public const string SFCExecutionControl = "SFCExecutionControl"; + + /// + /// Gets the SFCForcesEnabled L5X name value. + /// + public const string SFCForcesEnabled = "SFCForcesEnabled"; + + /// + /// Gets the SFCLastScan L5X name value. + /// + public const string SFCLastScan = "SFCLastScan"; + + /// + /// Gets the SFCRestartPosition L5X name value. + /// + public const string SFCRestartPosition = "SFCRestartPosition"; + + /// + /// Gets the ShareUnusedTimeSlice L5X name value. + /// + public const string ShareUnusedTimeSlice = "ShareUnusedTimeSlice"; + + /// + /// Gets the Sheet L5X name value. + /// + public const string Sheet = "Sheet"; + + /// + /// Gets the SheetOrientation L5X name value. + /// + public const string SheetOrientation = "SheetOrientation"; + + /// + /// Gets the SheetSize L5X name value. + /// + public const string SheetSize = "SheetSize"; + + /// + /// Gets the SheetType L5X name value. + /// + public const string SheetType = "SheetType"; + + /// + /// Gets the Shelved L5X name value. + /// + public const string Shelved = "Shelved"; + + /// + /// Gets the ShelveDuration L5X name value. + /// + public const string ShelveDuration = "ShelveDuration"; + + /// + /// Gets the ShelveTime L5X name value. + /// + public const string ShelveTime = "ShelveTime"; + + /// + /// Gets the Show L5X name value. + /// + public const string Show = "Show"; + + /// + /// Gets the ShowActions L5X name value. + /// + public const string ShowActions = "ShowActions"; + + /// + /// Gets the ShutdownAction L5X name value. + /// + public const string ShutdownAction = "ShutdownAction"; + + /// + /// Gets the ShutdownParentOnFault L5X name value. + /// + public const string ShutdownParentOnFault = "ShutdownParentOnFault"; + + /// + /// Gets the SiblingDependencies L5X name value. + /// + public const string SiblingDependencies = "SiblingDependencies"; + + /// + /// Gets the SiblingDependenciesCollectionType L5X name value. + /// + public const string SiblingDependenciesCollectionType = "SiblingDependenciesCollectionType"; + + /// + /// Gets the SiblingDependency L5X name value. + /// + public const string SiblingDependency = "SiblingDependency"; + + /// + /// Gets the SiblingDependencyAdaptorType L5X name value. + /// + public const string SiblingDependencyAdaptorType = "SiblingDependencyAdaptorType"; + + /// + /// Gets the SignatureHistory L5X name value. + /// + public const string SignatureHistory = "SignatureHistory"; + + /// + /// Gets the SignatureHistoryType L5X name value. + /// + public const string SignatureHistoryType = "SignatureHistoryType"; + + /// + /// Gets the SignatureID L5X name value. + /// + public const string SignatureID = "SignatureID"; + + /// + /// Gets the SignatureRunModeProtect L5X name value. + /// + public const string SignatureRunModeProtect = "SignatureRunModeProtect"; + + /// + /// Gets the SignatureTimestamp L5X name value. + /// + public const string SignatureTimestamp = "SignatureTimestamp"; + + /// + /// Gets the Size L5X name value. + /// + public const string Size = "Size"; + + /// + /// Gets the SkipSpeed1 L5X name value. + /// + public const string SkipSpeed1 = "SkipSpeed1"; + + /// + /// Gets the SkipSpeed2 L5X name value. + /// + public const string SkipSpeed2 = "SkipSpeed2"; + + /// + /// Gets the SkipSpeed3 L5X name value. + /// + public const string SkipSpeed3 = "SkipSpeed3"; + + /// + /// Gets the SkipSpeedBand L5X name value. + /// + public const string SkipSpeedBand = "SkipSpeedBand"; + + /// + /// Gets the SLATConfiguration L5X name value. + /// + public const string SLATConfiguration = "SLATConfiguration"; + + /// + /// Gets the SLATSetPoint L5X name value. + /// + public const string SLATSetPoint = "SLATSetPoint"; + + /// + /// Gets the SLATTimeDelay L5X name value. + /// + public const string SLATTimeDelay = "SLATTimeDelay"; + + /// + /// Gets the SlavePollTimeout L5X name value. + /// + public const string SlavePollTimeout = "SlavePollTimeout"; + + /// + /// Gets the SlipAdaptionEnable L5X name value. + /// + public const string SlipAdaptionEnable = "SlipAdaptionEnable"; + + /// + /// Gets the SlipAdaptionRegulatorIqThreshold L5X name value. + /// + public const string SlipAdaptionRegulatorIqThreshold = "SlipAdaptionRegulatorIqThreshold"; + + /// + /// Gets the SlipAdaptionRegulatorKi L5X name value. + /// + public const string SlipAdaptionRegulatorKi = "SlipAdaptionRegulatorKi"; + + /// + /// Gets the SlipAdaptionRegulatorKp L5X name value. + /// + public const string SlipAdaptionRegulatorKp = "SlipAdaptionRegulatorKp"; + + /// + /// Gets the SlipandFluxRegulatorConvergenceLevel L5X name value. + /// + public const string SlipandFluxRegulatorConvergenceLevel = "SlipandFluxRegulatorConvergenceLevel"; + + /// + /// Gets the SlipandFluxRegulatorConvergenceTime L5X name value. + /// + public const string SlipandFluxRegulatorConvergenceTime = "SlipandFluxRegulatorConvergenceTime"; + + /// + /// Gets the SlipandFluxRegulatorSlewRate L5X name value. + /// + public const string SlipandFluxRegulatorSlewRate = "SlipandFluxRegulatorSlewRate"; + + /// + /// Gets the SlipandFluxRegulatorSlewTime L5X name value. + /// + public const string SlipandFluxRegulatorSlewTime = "SlipandFluxRegulatorSlewTime"; + + /// + /// Gets the Slot L5X name value. + /// + public const string Slot = "Slot"; + + /// + /// Gets the SoftOvertravelFaultAction L5X name value. + /// + public const string SoftOvertravelFaultAction = "SoftOvertravelFaultAction"; + + /// + /// Gets the SoftTravelLimitChecking L5X name value. + /// + public const string SoftTravelLimitChecking = "SoftTravelLimitChecking"; + + /// + /// Gets the SoftTravelLimitNegative L5X name value. + /// + public const string SoftTravelLimitNegative = "SoftTravelLimitNegative"; + + /// + /// Gets the SoftTravelLimitPositive L5X name value. + /// + public const string SoftTravelLimitPositive = "SoftTravelLimitPositive"; + + /// + /// Gets the SoftwareRevision L5X name value. + /// + public const string SoftwareRevision = "SoftwareRevision"; + + /// + /// Gets the SourceKey L5X name value. + /// + public const string SourceKey = "SourceKey"; + + /// + /// Gets the SourceProtectionType L5X name value. + /// + public const string SourceProtectionType = "SourceProtectionType"; + + /// + /// Gets the Specifier L5X name value. + /// + public const string Specifier = "Specifier"; + + /// + /// Gets the SSIClockFrequency L5X name value. + /// + public const string SSIClockFrequency = "SSIClockFrequency"; + + /// + /// Gets the SSICodeType L5X name value. + /// + public const string SSICodeType = "SSICodeType"; + + /// + /// Gets the SSIDataLength L5X name value. + /// + public const string SSIDataLength = "SSIDataLength"; + + /// + /// Gets the SSV L5X name value. + /// + public const string SSV = "SSV"; + + /// + /// Gets the Start L5X name value. + /// + public const string Start = "Start"; + + /// + /// Gets the StartBoost L5X name value. + /// + public const string StartBoost = "StartBoost"; + + /// + /// Gets the StartTime L5X name value. + /// + public const string StartTime = "StartTime"; + + /// + /// Gets the StartTriggerCondition L5X name value. + /// + public const string StartTriggerCondition = "StartTriggerCondition"; + + /// + /// Gets the StartTriggerExpression L5X name value. + /// + public const string StartTriggerExpression = "StartTriggerExpression"; + + /// + /// Gets the StartTriggerLogicalOperation L5X name value. + /// + public const string StartTriggerLogicalOperation = "StartTriggerLogicalOperation"; + + /// + /// Gets the StartTriggerOperation1 L5X name value. + /// + public const string StartTriggerOperation1 = "StartTriggerOperation1"; + + /// + /// Gets the StartTriggerOperation2 L5X name value. + /// + public const string StartTriggerOperation2 = "StartTriggerOperation2"; + + /// + /// Gets the StartTriggerTag1 L5X name value. + /// + public const string StartTriggerTag1 = "StartTriggerTag1"; + + /// + /// Gets the StartTriggerTag2 L5X name value. + /// + public const string StartTriggerTag2 = "StartTriggerTag2"; + + /// + /// Gets the StartTriggerTargetTag1 L5X name value. + /// + public const string StartTriggerTargetTag1 = "StartTriggerTargetTag1"; + + /// + /// Gets the StartTriggerTargetTag2 L5X name value. + /// + public const string StartTriggerTargetTag2 = "StartTriggerTargetTag2"; + + /// + /// Gets the StartTriggerTargetType1 L5X name value. + /// + public const string StartTriggerTargetType1 = "StartTriggerTargetType1"; + + /// + /// Gets the StartTriggerTargetType2 L5X name value. + /// + public const string StartTriggerTargetType2 = "StartTriggerTargetType2"; + + /// + /// Gets the StartTriggerTargetValue1 L5X name value. + /// + public const string StartTriggerTargetValue1 = "StartTriggerTargetValue1"; + + /// + /// Gets the StartTriggerTargetValue2 L5X name value. + /// + public const string StartTriggerTargetValue2 = "StartTriggerTargetValue2"; + + /// + /// Gets the StartTriggerType L5X name value. + /// + public const string StartTriggerType = "StartTriggerType"; + + /// + /// Gets the State L5X name value. + /// + public const string State = "State"; + + /// + /// Gets the State0 L5X name value. + /// + public const string State0 = "State0"; + + /// + /// Gets the State0CollectionType L5X name value. + /// + public const string State0CollectionType = "State0CollectionType"; + + /// + /// Gets the State0s L5X name value. + /// + public const string State0s = "State0s"; + + /// + /// Gets the State0TextType L5X name value. + /// + public const string State0TextType = "State0TextType"; + + /// + /// Gets the State0Type L5X name value. + /// + public const string State0Type = "State0Type"; + + /// + /// Gets the State1 L5X name value. + /// + public const string State1 = "State1"; + + /// + /// Gets the State1CollectionType L5X name value. + /// + public const string State1CollectionType = "State1CollectionType"; + + /// + /// Gets the State1s L5X name value. + /// + public const string State1s = "State1s"; + + /// + /// Gets the State1TextType L5X name value. + /// + public const string State1TextType = "State1TextType"; + + /// + /// Gets the State1Type L5X name value. + /// + public const string State1Type = "State1Type"; + + /// + /// Gets the StationAddress L5X name value. + /// + public const string StationAddress = "StationAddress"; + + /// + /// Gets the Status L5X name value. + /// + public const string Status = "Status"; + + /// + /// Gets the StatusString L5X name value. + /// + public const string StatusString = "StatusString"; + + /// + /// Gets the STContent L5X name value. + /// + public const string STContent = "STContent"; + + /// + /// Gets the STContentType L5X name value. + /// + public const string STContentType = "STContentType"; + + /// + /// Gets the Step L5X name value. + /// + public const string Step = "Step"; + + /// + /// Gets the StepName L5X name value. + /// + public const string StepName = "StepName"; + + /// + /// Gets the STLineType L5X name value. + /// + public const string STLineType = "STLineType"; + + /// + /// Gets the Stop L5X name value. + /// + public const string Stop = "Stop"; + + /// + /// Gets the StopBits L5X name value. + /// + public const string StopBits = "StopBits"; + + /// + /// Gets the StopName L5X name value. + /// + public const string StopName = "StopName"; + + /// + /// Gets the StoppingAction L5X name value. + /// + public const string StoppingAction = "StoppingAction"; + + /// + /// Gets the StoppingTimeLimit L5X name value. + /// + public const string StoppingTimeLimit = "StoppingTimeLimit"; + + /// + /// Gets the StoppingTorque L5X name value. + /// + public const string StoppingTorque = "StoppingTorque"; + + /// + /// Gets the StopTriggerCondition L5X name value. + /// + public const string StopTriggerCondition = "StopTriggerCondition"; + + /// + /// Gets the StopTriggerExpression L5X name value. + /// + public const string StopTriggerExpression = "StopTriggerExpression"; + + /// + /// Gets the StopTriggerLogicalOperation L5X name value. + /// + public const string StopTriggerLogicalOperation = "StopTriggerLogicalOperation"; + + /// + /// Gets the StopTriggerOperation1 L5X name value. + /// + public const string StopTriggerOperation1 = "StopTriggerOperation1"; + + /// + /// Gets the StopTriggerOperation2 L5X name value. + /// + public const string StopTriggerOperation2 = "StopTriggerOperation2"; + + /// + /// Gets the StopTriggerTag1 L5X name value. + /// + public const string StopTriggerTag1 = "StopTriggerTag1"; + + /// + /// Gets the StopTriggerTag2 L5X name value. + /// + public const string StopTriggerTag2 = "StopTriggerTag2"; + + /// + /// Gets the StopTriggerTargetTag1 L5X name value. + /// + public const string StopTriggerTargetTag1 = "StopTriggerTargetTag1"; + + /// + /// Gets the StopTriggerTargetTag2 L5X name value. + /// + public const string StopTriggerTargetTag2 = "StopTriggerTargetTag2"; + + /// + /// Gets the StopTriggerTargetType1 L5X name value. + /// + public const string StopTriggerTargetType1 = "StopTriggerTargetType1"; + + /// + /// Gets the StopTriggerTargetType2 L5X name value. + /// + public const string StopTriggerTargetType2 = "StopTriggerTargetType2"; + + /// + /// Gets the StopTriggerTargetValue1 L5X name value. + /// + public const string StopTriggerTargetValue1 = "StopTriggerTargetValue1"; + + /// + /// Gets the StopTriggerTargetValue2 L5X name value. + /// + public const string StopTriggerTargetValue2 = "StopTriggerTargetValue2"; + + /// + /// Gets the StopTriggerType L5X name value. + /// + public const string StopTriggerType = "StopTriggerType"; + + /// + /// Gets the StoreFwdFile L5X name value. + /// + public const string StoreFwdFile = "StoreFwdFile"; + + /// + /// Gets the StringDataType L5X name value. + /// + public const string StringDataType = "StringDataType"; + + /// + /// Gets the StringDefaultDataType L5X name value. + /// + public const string StringDefaultDataType = "StringDefaultDataType"; + + /// + /// Gets the Structure L5X name value. + /// + public const string Structure = "Structure"; + + /// + /// Gets the StructureMember L5X name value. + /// + public const string StructureMember = "StructureMember"; + + /// + /// Gets the Style L5X name value. + /// + public const string Style = "Style"; + + /// + /// Gets the SubnetMask L5X name value. + /// + public const string SubnetMask = "SubnetMask"; + + /// + /// Gets the Suffix L5X name value. + /// + public const string Suffix = "Suffix"; + + /// + /// Gets the SupervisorModeEnabled L5X name value. + /// + public const string SupervisorModeEnabled = "SupervisorModeEnabled"; + + /// + /// Gets the SupervisorPrecedence L5X name value. + /// + public const string SupervisorPrecedence = "SupervisorPrecedence"; + + /// + /// Gets the Suppressed L5X name value. + /// + public const string Suppressed = "Suppressed"; + + /// + /// Gets the SwingArmA3 L5X name value. + /// + public const string SwingArmA3 = "SwingArmA3"; + + /// + /// Gets the SwingArmA4 L5X name value. + /// + public const string SwingArmA4 = "SwingArmA4"; + + /// + /// Gets the SwingArmCouplingDirection L5X name value. + /// + public const string SwingArmCouplingDirection = "SwingArmCouplingDirection"; + + /// + /// Gets the SwingArmCouplingRatioDenominator L5X name value. + /// + public const string SwingArmCouplingRatioDenominator = "SwingArmCouplingRatioDenominator"; + + /// + /// Gets the SwingArmCouplingRatioNumerator L5X name value. + /// + public const string SwingArmCouplingRatioNumerator = "SwingArmCouplingRatioNumerator"; + + /// + /// Gets the SwingArmD3 L5X name value. + /// + public const string SwingArmD3 = "SwingArmD3"; + + /// + /// Gets the SwingArmD4 L5X name value. + /// + public const string SwingArmD4 = "SwingArmD4"; + + /// + /// Gets the SwingArmD5 L5X name value. + /// + public const string SwingArmD5 = "SwingArmD5"; + + /// + /// Gets the SynchronizeRedundancyDataAfterExecution L5X name value. + /// + public const string SynchronizeRedundancyDataAfterExecution = "SynchronizeRedundancyDataAfterExecution"; + + /// + /// Gets the SystemAccelerationBase L5X name value. + /// + public const string SystemAccelerationBase = "SystemAccelerationBase"; + + /// + /// Gets the SystemBandwidth L5X name value. + /// + public const string SystemBandwidth = "SystemBandwidth"; + + /// + /// Gets the SystemCapacitance L5X name value. + /// + public const string SystemCapacitance = "SystemCapacitance"; + + /// + /// Gets the SystemDamping L5X name value. + /// + public const string SystemDamping = "SystemDamping"; + + /// + /// Gets the SystemInertia L5X name value. + /// + public const string SystemInertia = "SystemInertia"; + + /// + /// Gets the SystemModeCharacter L5X name value. + /// + public const string SystemModeCharacter = "SystemModeCharacter"; + + /// + /// Gets the SystemType L5X name value. + /// + public const string SystemType = "SystemType"; + + /// + /// Gets the Tag L5X name value. + /// + public const string Tag = "Tag"; + + /// + /// Gets the TagCollectionType L5X name value. + /// + public const string TagCollectionType = "TagCollectionType"; + + /// + /// Gets the TagConfiguration L5X name value. + /// + public const string TagConfiguration = "TagConfiguration"; + + /// + /// Gets the TagConfigurationAdaptorType L5X name value. + /// + public const string TagConfigurationAdaptorType = "TagConfigurationAdaptorType"; + + /// + /// Gets the TagConfigurations L5X name value. + /// + public const string TagConfigurations = "TagConfigurations"; + + /// + /// Gets the TagConfigurationsAdaptorType L5X name value. + /// + public const string TagConfigurationsAdaptorType = "TagConfigurationsAdaptorType"; + + /// + /// Gets the TagIOI L5X name value. + /// + public const string TagIOI = "TagIOI"; + + /// + /// Gets the TagName L5X name value. + /// + public const string TagName = "TagName"; + + /// + /// Gets the Tags L5X name value. + /// + public const string Tags = "Tags"; + + /// + /// Gets the TagsUId L5X name value. + /// + public const string TagsUId = "TagsUId"; + + /// + /// Gets the TagType L5X name value. + /// + public const string TagType = "TagType"; + + /// + /// Gets the Target L5X name value. + /// + public const string Target = "Target"; + + /// + /// Gets the TargetClass L5X name value. + /// + public const string TargetClass = "TargetClass"; + + /// + /// Gets the TargetCount L5X name value. + /// + public const string TargetCount = "TargetCount"; + + /// + /// Gets the TargetIsEncrypted L5X name value. + /// + public const string TargetIsEncrypted = "TargetIsEncrypted"; + + /// + /// Gets the TargetLastEdited L5X name value. + /// + public const string TargetLastEdited = "TargetLastEdited"; + + /// + /// Gets the TargetName L5X name value. + /// + public const string TargetName = "TargetName"; + + /// + /// Gets the TargetObject L5X name value. + /// + public const string TargetObject = "TargetObject"; + + /// + /// Gets the TargetRevision L5X name value. + /// + public const string TargetRevision = "TargetRevision"; + + /// + /// Gets the TargetSignature L5X name value. + /// + public const string TargetSignature = "TargetSignature"; + + /// + /// Gets the TargetSubType L5X name value. + /// + public const string TargetSubType = "TargetSubType"; + + /// + /// Gets the TargetTag L5X name value. + /// + public const string TargetTag = "TargetTag"; + + /// + /// Gets the TargetType L5X name value. + /// + public const string TargetType = "TargetType"; + + /// + /// Gets the Task L5X name value. + /// + public const string Task = "Task"; + + /// + /// Gets the TaskCollectionType L5X name value. + /// + public const string TaskCollectionType = "TaskCollectionType"; + + /// + /// Gets the TaskEventInfoType L5X name value. + /// + public const string TaskEventInfoType = "TaskEventInfoType"; + + /// + /// Gets the Tasks L5X name value. + /// + public const string Tasks = "Tasks"; + + /// + /// Gets the TasksUId L5X name value. + /// + public const string TasksUId = "TasksUId"; + + /// + /// Gets the TaskType L5X name value. + /// + public const string TaskType = "TaskType"; + + /// + /// Gets the TCPIPType L5X name value. + /// + public const string TCPIPType = "TCPIPType"; + + /// + /// Gets the Template L5X name value. + /// + public const string Template = "Template"; + + /// + /// Gets the TerminalCount L5X name value. + /// + public const string TerminalCount = "TerminalCount"; + + /// + /// Gets the TerminationChars L5X name value. + /// + public const string TerminationChars = "TerminationChars"; + + /// + /// Gets the TestEdits L5X name value. + /// + public const string TestEdits = "TestEdits"; + + /// + /// Gets the TestIncrement L5X name value. + /// + public const string TestIncrement = "TestIncrement"; + + /// + /// Gets the TestModeConfiguration L5X name value. + /// + public const string TestModeConfiguration = "TestModeConfiguration"; + + /// + /// Gets the TestModeEnable L5X name value. + /// + public const string TestModeEnable = "TestModeEnable"; + + /// + /// Gets the Text L5X name value. + /// + public const string Text = "Text"; + + /// + /// Gets the TextBox L5X name value. + /// + public const string TextBox = "TextBox"; + + /// + /// Gets the TextBoxAdaptorTextType L5X name value. + /// + public const string TextBoxAdaptorTextType = "TextBoxAdaptorTextType"; + + /// + /// Gets the TextBoxAdaptorType L5X name value. + /// + public const string TextBoxAdaptorType = "TextBoxAdaptorType"; + + /// + /// Gets the TextBoxType L5X name value. + /// + public const string TextBoxType = "TextBoxType"; + + /// + /// Gets the TextIOI L5X name value. + /// + public const string TextIOI = "TextIOI"; + + /// + /// Gets the TextNTT L5X name value. + /// + public const string TextNTT = "TextNTT"; + + /// + /// Gets the TextWideType L5X name value. + /// + public const string TextWideType = "TextWideType"; + + /// + /// Gets the ThreePhaseTruePowerFactor L5X name value. + /// + public const string ThreePhaseTruePowerFactor = "ThreePhaseTruePowerFactor"; + + /// + /// Gets the Throttle L5X name value. + /// + public const string Throttle = "Throttle"; + + /// + /// Gets the TimeoutMultiplier L5X name value. + /// + public const string TimeoutMultiplier = "TimeoutMultiplier"; + + /// + /// Gets the TimeSlice L5X name value. + /// + public const string TimeSlice = "TimeSlice"; + + /// + /// Gets the Timestamp L5X name value. + /// + public const string Timestamp = "Timestamp"; + + /// + /// Gets the TimeSynchronize L5X name value. + /// + public const string TimeSynchronize = "TimeSynchronize"; + + /// + /// Gets the TimeSynchronizeType L5X name value. + /// + public const string TimeSynchronizeType = "TimeSynchronizeType"; + + /// + /// Gets the TimeSynchronizeUId L5X name value. + /// + public const string TimeSynchronizeUId = "TimeSynchronizeUId"; + + /// + /// Gets the TimeZone L5X name value. + /// + public const string TimeZone = "TimeZone"; + + /// + /// Gets the ToID L5X name value. + /// + public const string ToID = "ToID"; + + /// + /// Gets the TokenHoldFactor L5X name value. + /// + public const string TokenHoldFactor = "TokenHoldFactor"; + + /// + /// Gets the ToParam L5X name value. + /// + public const string ToParam = "ToParam"; + + /// + /// Gets the TorqueAdaptionEnable L5X name value. + /// + public const string TorqueAdaptionEnable = "TorqueAdaptionEnable"; + + /// + /// Gets the TorqueAdaptionRegulatorKi L5X name value. + /// + public const string TorqueAdaptionRegulatorKi = "TorqueAdaptionRegulatorKi"; + + /// + /// Gets the TorqueAdaptionRegulatorKp L5X name value. + /// + public const string TorqueAdaptionRegulatorKp = "TorqueAdaptionRegulatorKp"; + + /// + /// Gets the TorqueAdaptionRegulatorLimitNegative L5X name value. + /// + public const string TorqueAdaptionRegulatorLimitNegative = "TorqueAdaptionRegulatorLimitNegative"; + + /// + /// Gets the TorqueAdaptionRegulatorLimitPositive L5X name value. + /// + public const string TorqueAdaptionRegulatorLimitPositive = "TorqueAdaptionRegulatorLimitPositive"; + + /// + /// Gets the TorqueCalibrationFactorMotoring L5X name value. + /// + public const string TorqueCalibrationFactorMotoring = "TorqueCalibrationFactorMotoring"; + + /// + /// Gets the TorqueCalibrationFactorRegenerating L5X name value. + /// + public const string TorqueCalibrationFactorRegenerating = "TorqueCalibrationFactorRegenerating"; + + /// + /// Gets the TorqueDataScaling L5X name value. + /// + public const string TorqueDataScaling = "TorqueDataScaling"; + + /// + /// Gets the TorqueDataScalingExp L5X name value. + /// + public const string TorqueDataScalingExp = "TorqueDataScalingExp"; + + /// + /// Gets the TorqueDataScalingFactor L5X name value. + /// + public const string TorqueDataScalingFactor = "TorqueDataScalingFactor"; + + /// + /// Gets the TorqueEstimateCrossoverSpeed L5X name value. + /// + public const string TorqueEstimateCrossoverSpeed = "TorqueEstimateCrossoverSpeed"; + + /// + /// Gets the TorqueEstimateNotch1Depth L5X name value. + /// + public const string TorqueEstimateNotch1Depth = "TorqueEstimateNotch1Depth"; + + /// + /// Gets the TorqueEstimateNotch1Frequency L5X name value. + /// + public const string TorqueEstimateNotch1Frequency = "TorqueEstimateNotch1Frequency"; + + /// + /// Gets the TorqueEstimateNotch1Gain L5X name value. + /// + public const string TorqueEstimateNotch1Gain = "TorqueEstimateNotch1Gain"; + + /// + /// Gets the TorqueEstimateNotch1Width L5X name value. + /// + public const string TorqueEstimateNotch1Width = "TorqueEstimateNotch1Width"; + + /// + /// Gets the TorqueEstimateNotch2Depth L5X name value. + /// + public const string TorqueEstimateNotch2Depth = "TorqueEstimateNotch2Depth"; + + /// + /// Gets the TorqueEstimateNotch2Frequency L5X name value. + /// + public const string TorqueEstimateNotch2Frequency = "TorqueEstimateNotch2Frequency"; + + /// + /// Gets the TorqueEstimateNotch2Gain L5X name value. + /// + public const string TorqueEstimateNotch2Gain = "TorqueEstimateNotch2Gain"; + + /// + /// Gets the TorqueEstimateNotch2Width L5X name value. + /// + public const string TorqueEstimateNotch2Width = "TorqueEstimateNotch2Width"; + + /// + /// Gets the TorqueIntegralTimeConstant L5X name value. + /// + public const string TorqueIntegralTimeConstant = "TorqueIntegralTimeConstant"; + + /// + /// Gets the TorqueLeadLagFilterBandwidth L5X name value. + /// + public const string TorqueLeadLagFilterBandwidth = "TorqueLeadLagFilterBandwidth"; + + /// + /// Gets the TorqueLeadLagFilterGain L5X name value. + /// + public const string TorqueLeadLagFilterGain = "TorqueLeadLagFilterGain"; + + /// + /// Gets the TorqueLimitBipolar L5X name value. + /// + public const string TorqueLimitBipolar = "TorqueLimitBipolar"; + + /// + /// Gets the TorqueLimitNegative L5X name value. + /// + public const string TorqueLimitNegative = "TorqueLimitNegative"; + + /// + /// Gets the TorqueLimitPositive L5X name value. + /// + public const string TorqueLimitPositive = "TorqueLimitPositive"; + + /// + /// Gets the TorqueLimitSource L5X name value. + /// + public const string TorqueLimitSource = "TorqueLimitSource"; + + /// + /// Gets the TorqueLoopBandwidth L5X name value. + /// + public const string TorqueLoopBandwidth = "TorqueLoopBandwidth"; + + /// + /// Gets the TorqueLowPassFilterBandwidth L5X name value. + /// + public const string TorqueLowPassFilterBandwidth = "TorqueLowPassFilterBandwidth"; + + /// + /// Gets the TorqueLowPassFilterBandwidthMin L5X name value. + /// + public const string TorqueLowPassFilterBandwidthMin = "TorqueLowPassFilterBandwidthMin"; + + /// + /// Gets the TorqueNotchFilter2Depth L5X name value. + /// + public const string TorqueNotchFilter2Depth = "TorqueNotchFilter2Depth"; + + /// + /// Gets the TorqueNotchFilter2Frequency L5X name value. + /// + public const string TorqueNotchFilter2Frequency = "TorqueNotchFilter2Frequency"; + + /// + /// Gets the TorqueNotchFilter2Gain L5X name value. + /// + public const string TorqueNotchFilter2Gain = "TorqueNotchFilter2Gain"; + + /// + /// Gets the TorqueNotchFilter2Width L5X name value. + /// + public const string TorqueNotchFilter2Width = "TorqueNotchFilter2Width"; + + /// + /// Gets the TorqueNotchFilter3Depth L5X name value. + /// + public const string TorqueNotchFilter3Depth = "TorqueNotchFilter3Depth"; + + /// + /// Gets the TorqueNotchFilter3Frequency L5X name value. + /// + public const string TorqueNotchFilter3Frequency = "TorqueNotchFilter3Frequency"; + + /// + /// Gets the TorqueNotchFilter3Gain L5X name value. + /// + public const string TorqueNotchFilter3Gain = "TorqueNotchFilter3Gain"; + + /// + /// Gets the TorqueNotchFilter3Width L5X name value. + /// + public const string TorqueNotchFilter3Width = "TorqueNotchFilter3Width"; + + /// + /// Gets the TorqueNotchFilter4Depth L5X name value. + /// + public const string TorqueNotchFilter4Depth = "TorqueNotchFilter4Depth"; + + /// + /// Gets the TorqueNotchFilter4Frequency L5X name value. + /// + public const string TorqueNotchFilter4Frequency = "TorqueNotchFilter4Frequency"; + + /// + /// Gets the TorqueNotchFilter4Gain L5X name value. + /// + public const string TorqueNotchFilter4Gain = "TorqueNotchFilter4Gain"; + + /// + /// Gets the TorqueNotchFilter4Width L5X name value. + /// + public const string TorqueNotchFilter4Width = "TorqueNotchFilter4Width"; + + /// + /// Gets the TorqueNotchFilterDepth L5X name value. + /// + public const string TorqueNotchFilterDepth = "TorqueNotchFilterDepth"; + + /// + /// Gets the TorqueNotchFilterFrequency L5X name value. + /// + public const string TorqueNotchFilterFrequency = "TorqueNotchFilterFrequency"; + + /// + /// Gets the TorqueNotchFilterGain L5X name value. + /// + public const string TorqueNotchFilterGain = "TorqueNotchFilterGain"; + + /// + /// Gets the TorqueNotchFilterHighFrequencyLimit L5X name value. + /// + public const string TorqueNotchFilterHighFrequencyLimit = "TorqueNotchFilterHighFrequencyLimit"; + + /// + /// Gets the TorqueNotchFilterLowFrequencyLimit L5X name value. + /// + public const string TorqueNotchFilterLowFrequencyLimit = "TorqueNotchFilterLowFrequencyLimit"; + + /// + /// Gets the TorqueNotchFilterTuningThreshold L5X name value. + /// + public const string TorqueNotchFilterTuningThreshold = "TorqueNotchFilterTuningThreshold"; + + /// + /// Gets the TorqueNotchFilterWidth L5X name value. + /// + public const string TorqueNotchFilterWidth = "TorqueNotchFilterWidth"; + + /// + /// Gets the TorqueNotchFilterWidthMax L5X name value. + /// + public const string TorqueNotchFilterWidthMax = "TorqueNotchFilterWidthMax"; + + /// + /// Gets the TorqueNotchFilterWidthMin L5X name value. + /// + public const string TorqueNotchFilterWidthMin = "TorqueNotchFilterWidthMin"; + + /// + /// Gets the TorqueOffset L5X name value. + /// + public const string TorqueOffset = "TorqueOffset"; + + /// + /// Gets the TorqueProveCurrent L5X name value. + /// + public const string TorqueProveCurrent = "TorqueProveCurrent"; + + /// + /// Gets the TorqueRateLimit L5X name value. + /// + public const string TorqueRateLimit = "TorqueRateLimit"; + + /// + /// Gets the TorqueScaling L5X name value. + /// + public const string TorqueScaling = "TorqueScaling"; + + /// + /// Gets the TorqueThreshold L5X name value. + /// + public const string TorqueThreshold = "TorqueThreshold"; + + /// + /// Gets the TotalDCBusCapacitance L5X name value. + /// + public const string TotalDCBusCapacitance = "TotalDCBusCapacitance"; + + /// + /// Gets the TotalInertia L5X name value. + /// + public const string TotalInertia = "TotalInertia"; + + /// + /// Gets the TotalMass L5X name value. + /// + public const string TotalMass = "TotalMass"; + + /// + /// Gets the TotalRealPower L5X name value. + /// + public const string TotalRealPower = "TotalRealPower"; + + /// + /// Gets the TrackingGroups L5X name value. + /// + public const string TrackingGroups = "TrackingGroups"; + + /// + /// Gets the TrackMoverCenterofMassOffset L5X name value. + /// + public const string TrackMoverCenterofMassOffset = "TrackMoverCenterofMassOffset"; + + /// + /// Gets the TrackMoverLength L5X name value. + /// + public const string TrackMoverLength = "TrackMoverLength"; + + /// + /// Gets the TransferOfControlSource L5X name value. + /// + public const string TransferOfControlSource = "TransferOfControlSource"; + + /// + /// Gets the TransferOfControlTarget L5X name value. + /// + public const string TransferOfControlTarget = "TransferOfControlTarget"; + + /// + /// Gets the TransformDimension L5X name value. + /// + public const string TransformDimension = "TransformDimension"; + + /// + /// Gets the Transition L5X name value. + /// + public const string Transition = "Transition"; + + /// + /// Gets the TransitionName L5X name value. + /// + public const string TransitionName = "TransitionName"; + + /// + /// Gets the TransmissionRatioInput L5X name value. + /// + public const string TransmissionRatioInput = "TransmissionRatioInput"; + + /// + /// Gets the TransmissionRatioOutput L5X name value. + /// + public const string TransmissionRatioOutput = "TransmissionRatioOutput"; + + /// + /// Gets the TransmitRetries L5X name value. + /// + public const string TransmitRetries = "TransmitRetries"; + + /// + /// Gets the TransportClass L5X name value. + /// + public const string TransportClass = "TransportClass"; + + /// + /// Gets the TravelMode L5X name value. + /// + public const string TravelMode = "TravelMode"; + + /// + /// Gets the TravelRange L5X name value. + /// + public const string TravelRange = "TravelRange"; + + /// + /// Gets the Trend L5X name value. + /// + public const string Trend = "Trend"; + + /// + /// Gets the TrendGroupCollectionType L5X name value. + /// + public const string TrendGroupCollectionType = "TrendGroupCollectionType"; + + /// + /// Gets the TrendGroupCollectionUId L5X name value. + /// + public const string TrendGroupCollectionUId = "TrendGroupCollectionUId"; + + /// + /// Gets the TrendGroupType L5X name value. + /// + public const string TrendGroupType = "TrendGroupType"; + + /// + /// Gets the Trends L5X name value. + /// + public const string Trends = "Trends"; + + /// + /// Gets the TrendxVersion L5X name value. + /// + public const string TrendxVersion = "TrendxVersion"; + + /// + /// Gets the TriggerCondition L5X name value. + /// + public const string TriggerCondition = "TriggerCondition"; + + /// + /// Gets the TriggerExpression L5X name value. + /// + public const string TriggerExpression = "TriggerExpression"; + + /// + /// Gets the TrustedSlots L5X name value. + /// + public const string TrustedSlots = "TrustedSlots"; + + /// + /// Gets the TuneFriction L5X name value. + /// + public const string TuneFriction = "TuneFriction"; + + /// + /// Gets the TuneInertiaMass L5X name value. + /// + public const string TuneInertiaMass = "TuneInertiaMass"; + + /// + /// Gets the TuneLoadOffset L5X name value. + /// + public const string TuneLoadOffset = "TuneLoadOffset"; + + /// + /// Gets the TuningConfigurationBits L5X name value. + /// + public const string TuningConfigurationBits = "TuningConfigurationBits"; + + /// + /// Gets the TuningDirection L5X name value. + /// + public const string TuningDirection = "TuningDirection"; + + /// + /// Gets the TuningSelect L5X name value. + /// + public const string TuningSelect = "TuningSelect"; + + /// + /// Gets the TuningSpeed L5X name value. + /// + public const string TuningSpeed = "TuningSpeed"; + + /// + /// Gets the TuningTorque L5X name value. + /// + public const string TuningTorque = "TuningTorque"; + + /// + /// Gets the TuningTravelLimit L5X name value. + /// + public const string TuningTravelLimit = "TuningTravelLimit"; + + /// + /// Gets the Type L5X name value. + /// + public const string Type = "Type"; + + /// + /// Gets the TypeMemberCollectionType L5X name value. + /// + public const string TypeMemberCollectionType = "TypeMemberCollectionType"; + + /// + /// Gets the UDIDefinitionCollectionType L5X name value. + /// + public const string UDIDefinitionCollectionType = "UDIDefinitionCollectionType"; + + /// + /// Gets the UDIDefinitionType L5X name value. + /// + public const string UDIDefinitionType = "UDIDefinitionType"; + + /// + /// Gets the UDIDependencyCollectionType L5X name value. + /// + public const string UDIDependencyCollectionType = "UDIDependencyCollectionType"; + + /// + /// Gets the UDIDependencyType L5X name value. + /// + public const string UDIDependencyType = "UDIDependencyType"; + + /// + /// Gets the UDILocalTagCollectionType L5X name value. + /// + public const string UDILocalTagCollectionType = "UDILocalTagCollectionType"; + + /// + /// Gets the UDILocalTagType L5X name value. + /// + public const string UDILocalTagType = "UDILocalTagType"; + + /// + /// Gets the UDIParameterCollectionType L5X name value. + /// + public const string UDIParameterCollectionType = "UDIParameterCollectionType"; + + /// + /// Gets the UDIParameterType L5X name value. + /// + public const string UDIParameterType = "UDIParameterType"; + + /// + /// Gets the UId L5X name value. + /// + public const string UId = "UId"; + + /// + /// Gets the UndertorqueLimit L5X name value. + /// + public const string UndertorqueLimit = "UndertorqueLimit"; + + /// + /// Gets the UndertorqueLimitTime L5X name value. + /// + public const string UndertorqueLimitTime = "UndertorqueLimitTime"; + + /// + /// Gets the Unicast L5X name value. + /// + public const string Unicast = "Unicast"; + + /// + /// Gets the UnicastPermitted L5X name value. + /// + public const string UnicastPermitted = "UnicastPermitted"; + + /// + /// Gets the UnitID L5X name value. + /// + public const string UnitID = "UnitID"; + + /// + /// Gets the UnshelveTime L5X name value. + /// + public const string UnshelveTime = "UnshelveTime"; + + /// + /// Gets the Unused L5X name value. + /// + public const string Unused = "Unused"; + + /// + /// Gets the Upstream L5X name value. + /// + public const string Upstream = "Upstream"; + + /// + /// Gets the URL L5X name value. + /// + public const string URL = "URL"; + + /// + /// Gets the URLAdaptorCollectionType L5X name value. + /// + public const string URLAdaptorCollectionType = "URLAdaptorCollectionType"; + + /// + /// Gets the URLAdaptorType L5X name value. + /// + public const string URLAdaptorType = "URLAdaptorType"; + + /// + /// Gets the URLs L5X name value. + /// + public const string URLs = "URLs"; + + /// + /// Gets the Usage L5X name value. + /// + public const string Usage = "Usage"; + + /// + /// Gets the Use L5X name value. + /// + public const string Use = "Use"; + + /// + /// Gets the UseAsFolder L5X name value. + /// + public const string UseAsFolder = "UseAsFolder"; + + /// + /// Gets the Used L5X name value. + /// + public const string Used = "Used"; + + /// + /// Gets the UseProgTime L5X name value. + /// + public const string UseProgTime = "UseProgTime"; + + /// + /// Gets the User L5X name value. + /// + public const string User = "User"; + + /// + /// Gets the UserDefinedCatalogNumber L5X name value. + /// + public const string UserDefinedCatalogNumber = "UserDefinedCatalogNumber"; + + /// + /// Gets the UserDefinedMajor L5X name value. + /// + public const string UserDefinedMajor = "UserDefinedMajor"; + + /// + /// Gets the UserDefinedMinor L5X name value. + /// + public const string UserDefinedMinor = "UserDefinedMinor"; + + /// + /// Gets the UserDefinedProductCode L5X name value. + /// + public const string UserDefinedProductCode = "UserDefinedProductCode"; + + /// + /// Gets the UserDefinedProductType L5X name value. + /// + public const string UserDefinedProductType = "UserDefinedProductType"; + + /// + /// Gets the UserDefinedVendor L5X name value. + /// + public const string UserDefinedVendor = "UserDefinedVendor"; + + /// + /// Gets the UserModeCharacter L5X name value. + /// + public const string UserModeCharacter = "UserModeCharacter"; + + /// + /// Gets the Validation L5X name value. + /// + public const string Validation = "Validation"; + + /// + /// Gets the Value L5X name value. + /// + public const string Value = "Value"; + + /// + /// Gets the ValuesToUseOnReset L5X name value. + /// + public const string ValuesToUseOnReset = "ValuesToUseOnReset"; + + /// + /// Gets the ValuesToUseOnStart L5X name value. + /// + public const string ValuesToUseOnStart = "ValuesToUseOnStart"; + + /// + /// Gets the VdIqDecouplingGain L5X name value. + /// + public const string VdIqDecouplingGain = "VdIqDecouplingGain"; + + /// + /// Gets the VelocityDataScaling L5X name value. + /// + public const string VelocityDataScaling = "VelocityDataScaling"; + + /// + /// Gets the VelocityDataScalingExp L5X name value. + /// + public const string VelocityDataScalingExp = "VelocityDataScalingExp"; + + /// + /// Gets the VelocityDataScalingFactor L5X name value. + /// + public const string VelocityDataScalingFactor = "VelocityDataScalingFactor"; + + /// + /// Gets the VelocityDroop L5X name value. + /// + public const string VelocityDroop = "VelocityDroop"; + + /// + /// Gets the VelocityErrorTolerance L5X name value. + /// + public const string VelocityErrorTolerance = "VelocityErrorTolerance"; + + /// + /// Gets the VelocityErrorToleranceTime L5X name value. + /// + public const string VelocityErrorToleranceTime = "VelocityErrorToleranceTime"; + + /// + /// Gets the VelocityFeedbackDelayCompensation L5X name value. + /// + public const string VelocityFeedbackDelayCompensation = "VelocityFeedbackDelayCompensation"; + + /// + /// Gets the VelocityFeedforwardGain L5X name value. + /// + public const string VelocityFeedforwardGain = "VelocityFeedforwardGain"; + + /// + /// Gets the VelocityIntegralGain L5X name value. + /// + public const string VelocityIntegralGain = "VelocityIntegralGain"; + + /// + /// Gets the VelocityIntegratorBandwidth L5X name value. + /// + public const string VelocityIntegratorBandwidth = "VelocityIntegratorBandwidth"; + + /// + /// Gets the VelocityIntegratorControl L5X name value. + /// + public const string VelocityIntegratorControl = "VelocityIntegratorControl"; + + /// + /// Gets the VelocityIntegratorPreload L5X name value. + /// + public const string VelocityIntegratorPreload = "VelocityIntegratorPreload"; + + /// + /// Gets the VelocityLimitBipolar L5X name value. + /// + public const string VelocityLimitBipolar = "VelocityLimitBipolar"; + + /// + /// Gets the VelocityLimitNegative L5X name value. + /// + public const string VelocityLimitNegative = "VelocityLimitNegative"; + + /// + /// Gets the VelocityLimitPositive L5X name value. + /// + public const string VelocityLimitPositive = "VelocityLimitPositive"; + + /// + /// Gets the VelocityLockTolerance L5X name value. + /// + public const string VelocityLockTolerance = "VelocityLockTolerance"; + + /// + /// Gets the VelocityLoopBandwidth L5X name value. + /// + public const string VelocityLoopBandwidth = "VelocityLoopBandwidth"; + + /// + /// Gets the VelocityLowPassFilterBandwidth L5X name value. + /// + public const string VelocityLowPassFilterBandwidth = "VelocityLowPassFilterBandwidth"; + + /// + /// Gets the VelocityNegativeFeedforwardGain L5X name value. + /// + public const string VelocityNegativeFeedforwardGain = "VelocityNegativeFeedforwardGain"; + + /// + /// Gets the VelocityOffset L5X name value. + /// + public const string VelocityOffset = "VelocityOffset"; + + /// + /// Gets the VelocityProportionalGain L5X name value. + /// + public const string VelocityProportionalGain = "VelocityProportionalGain"; + + /// + /// Gets the VelocityScaling L5X name value. + /// + public const string VelocityScaling = "VelocityScaling"; + + /// + /// Gets the VelocityServoBandwidth L5X name value. + /// + public const string VelocityServoBandwidth = "VelocityServoBandwidth"; + + /// + /// Gets the VelocityStandstillWindow L5X name value. + /// + public const string VelocityStandstillWindow = "VelocityStandstillWindow"; + + /// + /// Gets the VelocityThreshold L5X name value. + /// + public const string VelocityThreshold = "VelocityThreshold"; + + /// + /// Gets the VelocityWindow L5X name value. + /// + public const string VelocityWindow = "VelocityWindow"; + + /// + /// Gets the Vendor L5X name value. + /// + public const string Vendor = "Vendor"; + + /// + /// Gets the Verified L5X name value. + /// + public const string Verified = "Verified"; + + /// + /// Gets the Version L5X name value. + /// + public const string Version = "Version"; + + /// + /// Gets the VerticalLoadControl L5X name value. + /// + public const string VerticalLoadControl = "VerticalLoadControl"; + + /// + /// Gets the Visible L5X name value. + /// + public const string Visible = "Visible"; + + /// + /// Gets the VisiblePins L5X name value. + /// + public const string VisiblePins = "VisiblePins"; + + /// + /// Gets the VLANID L5X name value. + /// + public const string VLANID = "VLANID"; + + /// + /// Gets the VqIdDecouplingGain L5X name value. + /// + public const string VqIdDecouplingGain = "VqIdDecouplingGain"; + + /// + /// Gets the WallClockTime L5X name value. + /// + public const string WallClockTime = "WallClockTime"; + + /// + /// Gets the WallClockTimeType L5X name value. + /// + public const string WallClockTimeType = "WallClockTimeType"; + + /// + /// Gets the WallClockTimeUId L5X name value. + /// + public const string WallClockTimeUId = "WallClockTimeUId"; + + /// + /// Gets the Watchdog L5X name value. + /// + public const string Watchdog = "Watchdog"; + + /// + /// Gets the WatchTag L5X name value. + /// + public const string WatchTag = "WatchTag"; + + /// + /// Gets the WebServerEnabled L5X name value. + /// + public const string WebServerEnabled = "WebServerEnabled"; + + /// + /// Gets the Width L5X name value. + /// + public const string Width = "Width"; + + /// + /// Gets the Wire L5X name value. + /// + public const string Wire = "Wire"; + + /// + /// Gets the X L5X name value. + /// + public const string X = "X"; + + /// + /// Gets the XONXOFFEnable L5X name value. + /// + public const string XONXOFFEnable = "XONXOFFEnable"; + + /// + /// Gets the Y L5X name value. + /// + public const string Y = "Y"; + + /// + /// Gets the ZeroAngleOffset1 L5X name value. + /// + public const string ZeroAngleOffset1 = "ZeroAngleOffset1"; + + /// + /// Gets the ZeroAngleOffset2 L5X name value. + /// + public const string ZeroAngleOffset2 = "ZeroAngleOffset2"; + + /// + /// Gets the ZeroAngleOffset3 L5X name value. + /// + public const string ZeroAngleOffset3 = "ZeroAngleOffset3"; + + /// + /// Gets the ZeroAngleOffset4 L5X name value. + /// + public const string ZeroAngleOffset4 = "ZeroAngleOffset4"; + + /// + /// Gets the ZeroAngleOffset5 L5X name value. + /// + public const string ZeroAngleOffset5 = "ZeroAngleOffset5"; + + /// + /// Gets the ZeroAngleOffset6 L5X name value. + /// + public const string ZeroAngleOffset6 = "ZeroAngleOffset6"; + + /// + /// Gets the ZeroSpeed L5X name value. + /// + public const string ZeroSpeed = "ZeroSpeed"; + + /// + /// Gets the ZeroSpeedTime L5X name value. + /// + public const string ZeroSpeedTime = "ZeroSpeedTime"; +} \ No newline at end of file diff --git a/src/L5Sharp/Utilities/L5XParser.cs b/src/L5Sharp/Utilities/L5XParser.cs new file mode 100644 index 00000000..94383f78 --- /dev/null +++ b/src/L5Sharp/Utilities/L5XParser.cs @@ -0,0 +1,124 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace L5Sharp.Core; + +/// +/// Static class containing mappings for converting string values (XML typed value) to the strongly type object. +/// +public static class L5XParser +{ + private static readonly Dictionary> Parsers = new() + { + //Types + {typeof(BOOL), BOOL.Parse}, + {typeof(SINT), SINT.Parse}, + {typeof(INT), INT.Parse}, + {typeof(DINT), DINT.Parse}, + {typeof(LINT), LINT.Parse}, + {typeof(REAL), REAL.Parse}, + {typeof(LREAL), LREAL.Parse}, + {typeof(USINT), USINT.Parse}, + {typeof(UINT), UINT.Parse}, + {typeof(UDINT), UDINT.Parse}, + {typeof(ULINT), ULINT.Parse}, + {typeof(AtomicType), Atomic.Parse}, + {typeof(STRING), s => new STRING(s)}, + {typeof(DateTime), s => DateTime.Parse(s)}, + //Enums + {typeof(CaptureSizeType), CaptureSizeType.FromValue}, + {typeof(ComponentType), ComponentType.FromValue}, + {typeof(ConnectionPriority), ConnectionPriority.FromValue}, + {typeof(ConnectionType), ConnectionType.FromValue}, + {typeof(DataFormat), DataFormat.FromValue}, + {typeof(DataTypeClass), DataTypeClass.FromValue}, + {typeof(DataTypeFamily), DataTypeFamily.FromValue}, + {typeof(ElectronicKeying), ElectronicKeying.FromValue}, + {typeof(ExternalAccess), ExternalAccess.FromValue}, + {typeof(Keyword), Keyword.FromValue}, + {typeof(OnlineEditType), OnlineEditType.FromValue}, + {typeof(Operator), Operator.FromValue}, + {typeof(PassThroughOption), PassThroughOption.FromValue}, + {typeof(PenType), PenType.FromValue}, + {typeof(ProductionTrigger), ProductionTrigger.FromValue}, + {typeof(ProgramType), ProgramType.FromValue}, + {typeof(Radix), Radix.FromValue}, + {typeof(RoutineType), RoutineType.FromValue}, + {typeof(RungType), RungType.FromValue}, + {typeof(SamplesType), SamplesType.FromValue}, + {typeof(Scope), Scope.FromValue}, + {typeof(SFCExecutionControl), SFCExecutionControl.FromValue}, + {typeof(SFCLastScan), SFCLastScan.FromValue}, + {typeof(SFCRestartPosition), SFCRestartPosition.FromValue}, + {typeof(SheetOrientation), SheetOrientation.FromValue}, + {typeof(SheetSize), SheetSize.FromValue}, + {typeof(TagType), TagType.FromValue}, + {typeof(TagUsage), TagUsage.FromValue}, + {typeof(TaskEventTrigger), TaskEventTrigger.FromValue}, + {typeof(TaskType), TaskType.FromValue}, + {typeof(TransmissionType), TransmissionType.FromValue}, + {typeof(TriggerOperation), TriggerOperation.Parse}, + {typeof(TriggerTargetType), TriggerTargetType.FromValue}, + {typeof(TriggerType), TriggerType.FromValue}, + {typeof(Use), s => Use.FromName(s)}, + //Common + {typeof(Argument), Argument.Parse}, + {typeof(Address), s => new Address(s)}, + {typeof(Dimensions), Dimensions.Parse}, + {typeof(NeutralText), s => new NeutralText(s)}, + {typeof(ProductType), ProductType.Parse}, + {typeof(Revision), s => new Revision(s)}, + {typeof(ScanRate), s => ScanRate.Parse(s)}, + {typeof(ScanRate?), s => ScanRate.Parse(s)}, + {typeof(TagName), s => new TagName(s)}, + {typeof(TaskPriority), s => TaskPriority.Parse(s)}, + {typeof(Vendor), Vendor.Parse}, + {typeof(Watchdog), s => Watchdog.Parse(s)}, + }; + + /// + /// Parses the provided string input to the specified type using the predefined L5X parser functions. + /// + /// The string input to parse. + /// The type of property to return. + /// The resulting parsed value. + /// When a parser was not found to the specified type. + /// If the resulting parsed type does not match the specified generic type parameter. + public static T Parse(this string input) + { + var parser = GetParser(typeof(T)); + + var value = parser(input); + + if (value is not T typed) + throw new ArgumentException($"The input '{input}' could not be parsed to type '{typeof(T)}"); + + return typed; + } + + /// + /// Gets a parser function for the specified type. + /// + /// + /// Simply looks to predefined parser functions and returns one if found for the specified type. + /// Otherwise will use the of the current type and return the ConvertFrom function + /// if is capable of converting from a string type. + /// + /// The type to parse. + /// + /// A func that can convert from a string to an object for the provided type is the func is defined. + /// + private static Func GetParser(Type type) + { + if (Parsers.TryGetValue(type, out var parser)) + return parser; + + var converter = TypeDescriptor.GetConverter(type); + + if (converter.CanConvertFrom(typeof(string))) + return s => converter.ConvertFrom(s); + + throw new InvalidOperationException($"No parse function has been defined for type '{type}'"); + } +} \ No newline at end of file diff --git a/src/L5Sharp/Utilities/L5XTypeAttribute.cs b/src/L5Sharp/Utilities/L5XTypeAttribute.cs new file mode 100644 index 00000000..a8f52fb0 --- /dev/null +++ b/src/L5Sharp/Utilities/L5XTypeAttribute.cs @@ -0,0 +1,52 @@ +using System; + +namespace L5Sharp.Core; + +/// +/// A custom attribute that defines the L5X type name and container name for a logic type class in order to match XML +/// elements to a given class. This attribute is mostly needed for class types whose names do not match the L5X element type +/// found in the L5X, or for classes that support multiple L5X element types. +/// +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +public class L5XTypeAttribute : Attribute +{ + /// + /// Creates a new instance with provided configuration parameters. + /// + /// The L5X type name for the class. + /// typeName is null. + public L5XTypeAttribute(string typeName) + { + TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName)); + ContainerName = $"{TypeName}s"; + } + + /// + /// Creates a new instance with provided configuration parameters. + /// + /// The L5X type name for the class. + /// The L5X container name for the class. + /// typeName or containerName is null. + public L5XTypeAttribute(string typeName, string containerName) + { + TypeName = typeName ?? throw new ArgumentNullException(nameof(typeName)); + ContainerName = containerName ?? throw new ArgumentNullException(nameof(containerName)); + } + + /// + /// The L5X type name. This name corresponds to the name of the L5X/XML element, and is + /// used to retrieve elements from an L5X file for the type. + /// + /// A representing the name of the element for the type. (e.g. DataType, Tag, etc.) + public string TypeName { get; } + + /// + /// The L5X container name. This name corresponds to the name of the L5X/XML element that is the parent or + /// container of the type. + /// + /// A representing the name of the container element for the type. (e.g. DataTypes, Tags, etc.) + /// This value will default to the with an 's' appended if not overriden from + /// the constructor. + /// + public string ContainerName { get; } +} \ No newline at end of file