Skip to content

Commit

Permalink
Added new static factory method to LogixData.cs to create instance of…
Browse files Browse the repository at this point in the history
… type given a name.

Added new constructors to ArrayData.cs to allow instance to be created via name and dimensions or type and dimensions.

Added new methods to DataType.cs and AddOnInstruction.cs to allow them to generate a Tag instance from its configuration. Also added methods for generating LogixData instance which they require.

Added ToMember methods for DataTypeMember.cs and Parameter.cs

Added new constructor to allow Member.cs to be created by name and data type name which rely on factory method. Also, then added similar constructor to Tag.

Added and fixed tests and documentation. Renamed some test folder and namespaces.
  • Loading branch information
tnunnink committed May 4, 2024
1 parent a5176ef commit c07bc8e
Show file tree
Hide file tree
Showing 135 changed files with 1,453 additions and 457 deletions.
272 changes: 206 additions & 66 deletions src/.idea/.idea.L5Sharp/.idea/workspace.xml

Large diffs are not rendered by default.

49 changes: 47 additions & 2 deletions src/L5Sharp.Core/Components/AddOnInstruction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,49 @@ public IEnumerable<NeutralText> LogicFor(Instruction instruction)
.ToList();
}

/// <summary>
/// Creates a new <see cref="Tag"/> instance with data configured from this <see cref="AddOnInstruction"/> component.
/// </summary>
/// <param name="name">The name of the tag to create.</param>
/// <returns>
/// A <see cref="Tag"/> component with the provided name and set of data based on the configuration of
/// this instruction.
/// </returns>
/// <remarks>This is a helper to allow callers to quickly generate in memory tag instances from a configured instruction.</remarks>
public Tag ToTag(string name) => new(name, ToData());

/// <summary>
/// Creates a new <see cref="LogixData"/> object given the current AOI configuration.
/// </summary>
/// <returns>
/// An <see cref="ComplexData"/> instance having the name of the current instruction and members generated
/// from the configured <see cref="Parameters"/>.
/// </returns>
/// <remarks>
/// This is a helper that allows us to easily generate a complex data structure from a given AOI instance. This allows
/// us to (more) easily instantiate an instance of a Tag having the data of the configured AOI.
/// </remarks>
public LogixData ToData()
{
if (string.IsNullOrEmpty(Name))
throw new InvalidOperationException("Can not create data with null or empty data type name");

//This will be some predefined type or a generic complex type, depending on whether it is statically defined.
var data = LogixData.Create(Name);

//If it is not a complex data (meaning more derived) then it was defined, and we can return it.
if (data is not ComplexData complexData) return data;

//Generate the members from the parameters that are configured as input/output parameters.
//These are the only members that are used as members of an AOI tag structure.
var members = Parameters
.Where(p => p.Usage == TagUsage.Input || p.Usage == TagUsage.Output)
.Select(p => p.ToMember());

complexData.AddRange(members.ToList());
return complexData;
}

/// <summary>
/// Returns the default built in EnableIn parameter.
/// </summary>
Expand All @@ -340,7 +383,8 @@ private static Parameter EnableIn()
Radix = Radix.Decimal,
Required = false,
Visible = false,
ExternalAccess = ExternalAccess.ReadOnly
ExternalAccess = ExternalAccess.ReadOnly,
Default = new BOOL()
};
}

Expand All @@ -359,7 +403,8 @@ private static Parameter EnableOut()
Radix = Radix.Decimal,
Required = false,
Visible = false,
ExternalAccess = ExternalAccess.ReadOnly
ExternalAccess = ExternalAccess.ReadOnly,
Default = new BOOL()
};
}
}
13 changes: 13 additions & 0 deletions src/L5Sharp.Core/Components/Controller.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,19 @@ public Controller(XElement element) : base(element)
{
}

/// <summary>
/// Created a new <see cref="Controller"/> initialized with the provided name, processor type, and revision.
/// </summary>
/// <param name="name">The name of the controller.</param>
/// <param name="processor">The catalog number specifying the processor type.</param>
/// <param name="revision">AThe <see cref="Core.Revision"/> of the controller.</param>
public Controller(string name, string processor, Revision revision) : this()
{
Element.SetAttributeValue(L5XName.Name, name);
ProcessorType = processor;
Revision = revision;
}

/// <summary>
/// The catalog number representing the processor of the controller component.
/// </summary>
Expand Down
46 changes: 44 additions & 2 deletions src/L5Sharp.Core/Components/DataType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class DataType : LogixComponent
L5XName.Description,
L5XName.Members
];

/// <summary>
/// Creates a new <see cref="DataType"/> with default values.
/// </summary>
Expand Down Expand Up @@ -96,7 +96,7 @@ public LogixContainer<DataTypeMember> Members
get => GetContainer<DataTypeMember>();
set => SetContainer(value);
}

/// <inheritdoc />
public override IEnumerable<LogixComponent> Dependencies()
{
Expand All @@ -114,4 +114,46 @@ public override IEnumerable<LogixComponent> Dependencies()

return dependencies.Distinct();
}

/// <summary>
/// Creates a new <see cref="Tag"/> instance by converting this <see cref="DataType"/> definition into a
/// <see cref="LogixData"/> instance and using that along with the provided tag name to create the component.
/// </summary>
/// <param name="tagName">The name of the tag.</param>
/// <returns>A <see cref="Tag"/> with the provided name and populated data structure using this data type definition.</returns>
/// <remarks>
/// If this data type has nested (user) data types, then internally this feature will attempt to retrieve
/// and create them either statically or from the attached L5X. If this type is not attached or the nested types
/// are not able to be resolved, it will default to creating <see cref="ComplexData"/> objects with no members
/// as there is no way to know the structure.
/// </remarks>
public Tag ToTag(string tagName) => new(tagName, ToData());

/// <summary>
/// Converts the <see cref="DataType"/> component into a <see cref="LogixData"/> object using the configured
/// name and members.
/// </summary>
/// <returns>A <see cref="LogixData"/> object populated with the members defined by this data type configuration.</returns>
/// <remarks>
/// If this data type has nested (user) data types, then internally this feature will attempt to retrieve
/// and create them either statically or from the attached L5X. If this type is not attached or the nested types
/// are not able to be resolved, it will default to creating <see cref="ComplexData"/> objects with no members
/// as there is no way to know the structure.
/// </remarks>
public LogixData ToData()
{
if (string.IsNullOrEmpty(Name))
throw new InvalidOperationException("Can not create data with null or empty data type name");

//This will be some predefined type or a generic complex type, depending on whether it is statically defined.
var data = LogixData.Create(Name);

//If it is not a complex data then it was defined, and we can return it.
if (data is not ComplexData complexData) return data;

//Otherwise, we need to build the members using the configured Members collection.
var members = Members.Where(m => m.Hidden is not true).Select(m => m.ToMember()).ToList();
complexData.AddRange(members);
return complexData;
}
}
64 changes: 48 additions & 16 deletions src/L5Sharp.Core/Components/Module.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,25 +25,21 @@ public class Module : LogixComponent
L5XName.Communications,
L5XName.ExtendedProperties
];

/// <inheritdoc />
public Module() : base(L5XName.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;
Inhibited = false;
MajorFault = false;
SafetyEnabled = false;
Keying = ElectronicKeying.CompatibleModule;
Ports = [];
Communications = new Communications();
}

/// <inheritdoc />
public Module(XElement element) : base(element)
{
Expand Down Expand Up @@ -176,7 +172,7 @@ public bool Inhibited
}

/// <summary>
/// An indication of whether the module the module will cause a major fault when faulted.
/// An indication of whether the module will cause a major fault when faulted.
/// </summary>
public bool MajorFault
{
Expand All @@ -185,7 +181,7 @@ public bool MajorFault
}

/// <summary>
/// An indication of whether whether the module has safety features enabled.
/// An indication of whether the module has safety features enabled.
/// </summary>
public bool SafetyEnabled
{
Expand Down Expand Up @@ -243,6 +239,7 @@ public Communications? Communications
}

//Properties or methods extending the functionality of a Module component.

#region Extensions

/// <summary>
Expand All @@ -264,7 +261,7 @@ public Communications? Communications
/// This property looks for an Ethernet type <see cref="Port"/> with a valid IPv4 address.
/// </remarks>
public IPAddress? IP =>
Ports.FirstOrDefault(p => p is {Type: "Ethernet", Address.IsIPv4: true})?.Address.ToIPAddress();
Ports.FirstOrDefault(p => p is { Type: "Ethernet", Address.IsIPv4: true })?.Address.ToIPAddress();

/// <summary>
/// Gets the parent module of this module component defined in the current L5X document.
Expand Down Expand Up @@ -375,7 +372,7 @@ public void Add(Module child, Address? address = default)
if (parentPort.Address.IsSlot && IsAttached && address is null) address = NextSlot();
address ??= Address.Slot();

var childPort = new Port {Id = 1, Type = parentPort.Type, Address = address, Upstream = true};
var childPort = new Port { Id = 1, Type = parentPort.Type, Address = address, Upstream = true };

child.ParentModule = Name;
child.ParentModPortId = parentPort.Id;
Expand All @@ -394,7 +391,7 @@ public void Add(Module child, Address? address = default)
/// </summary>
/// <param name="name">The name of the module</param>
/// <param name="catalogNumber">The catalog number to lookup a catalog entry for.</param>
/// <param name="address"></param>
/// <param name="address">The optional <see cref="Address"/> defining the slot or IP address of the device.</param>
/// <returns>A new <see cref="Module"/> object initialized with data return by the catalog service.</returns>
/// <exception cref="InvalidOperationException">The module catalog service could not load the installed catalog
/// database file -or- catalog number does not exist in the catalog database.</exception>
Expand All @@ -403,7 +400,7 @@ public void Add(Module child, Address? address = default)
/// This factory method uses the <see cref="ModuleCatalog"/> service to lookup info for the specified
/// catalog number. If RSLogix is not installed on the current environment, this will throw an exception.
/// </remarks>
public static Module New(string name, string catalogNumber, Address? address = null)
public static Module Create(string name, string catalogNumber, Address? address = null)
{
var catalog = new ModuleCatalog();
var entry = catalog.Lookup(catalogNumber);
Expand All @@ -428,6 +425,41 @@ public static Module New(string name, string catalogNumber, Address? address = n
};
}

/// <summary>
/// Creates a new <see cref="Module"/> instance that represents the local controller module for a project.
/// </summary>
/// <param name="processor">The processor catalog number for the module.</param>
/// <param name="revision">The software revision of the controller.</param>
/// <returns>A new <see cref="Module"/> representing a default local controller module instance.</returns>
public static Module Local(string processor, Revision? revision = default)
{
var catalog = new ModuleCatalog();

var entry = catalog.Lookup(processor);

return new Module
{
Name = "Local",
CatalogNumber = entry.CatalogNumber,
Vendor = entry.Vendor,
ProductType = entry.ProductType,
ProductCode = entry.ProductCode,
Revision = revision ?? entry.Revisions.Max(),
ParentModule = "Local",
ParentModPortId = 1,
MajorFault = true,
Keying = ElectronicKeying.Disabled,
Ports = new LogixContainer<Port>(
entry.Ports.Select(p => new Port
{
Id = p.Number,
Type = p.Type,
Address = p.Type == "Ethernet" ? Address.IP() : Address.Slot(),
Upstream = !p.DownstreamOnly
}).ToList()),
};
}

/// <summary>
/// Gets the next largest slot number for the current module by introspecting the slot numbers of all other
/// child modules of this parent module.
Expand Down Expand Up @@ -460,6 +492,6 @@ public static class ModuleExtensions
/// </summary>
/// <param name="modules">A collection of modules.</param>
/// <returns>A single <see cref="Module"/> which is named local if found; Otherwise, <c>null</c>.</returns>
/// <remarks>This is a helper to concisely get the controller or root local module from the modules collection.</remarks>
/// <remarks>This is a helper to concisely get the controller or root local module from the module collection.</remarks>
public static Module? Local(this IEnumerable<Module> modules) => modules.SingleOrDefault(m => m.Name == "Local");
}
24 changes: 21 additions & 3 deletions src/L5Sharp.Core/Components/Tag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,34 @@ public Tag(XElement element) : base(element)
/// <summary>
/// Creates a new <see cref="Tag"/> initialized with the provided name and value.
/// </summary>
/// <param name="name">The name of the Tag.</param>
/// <param name="value">The <see cref="LogixData"/> value of the Tag.</param>
/// <param name="description">the optional description of the tag.</param>
/// <param name="name">The name of the tag.</param>
/// <param name="value">The <see cref="LogixData"/> value of the tag.</param>
/// <param name="description">The optional description of the tag.</param>
public Tag(string name, LogixData value, string? description = default) : this()
{
Element.SetAttributeValue(L5XName.Name, name);
Value = value;
SetDescription(description);
}

/// <summary>
/// Creates a new <see cref="Tag"/> initialized with the provided name, data type, and optional description.
/// </summary>
/// <param name="name">The name of the tag.</param>
/// <param name="dataType">The name of the data type of the tag.</param>
/// <param name="description">The optional description of the tag.</param>
/// <remarks>
/// This constructor will use the <see cref="LogixData.Create(string)"/> factory method to instantiate the <see cref="Value"/>
/// data for the tag. If <paramref name="dataType"/> represents a complex type that is not statically defined,
/// it will defualt to creating a <see cref="ComplexData"/> instance having the provided name.
/// </remarks>
public Tag(string name, string dataType, string? description = default) : this()
{
Element.SetAttributeValue(L5XName.Name, name);
Value = LogixData.Create(dataType);
SetDescription(description);
}

/// <summary>
/// Creates a new <see cref="Tag"/> initialized with default value and having an element with the provided name.
/// </summary>
Expand Down
Loading

0 comments on commit c07bc8e

Please sign in to comment.