tnunnink / L5Sharp

A library for intuitively interacting with Rockwell's L5X import/export files.
MIT License
55 stars 6 forks source link

Creating AOIs Efficiently #24

Closed jojoguy10 closed 6 months ago

jojoguy10 commented 6 months ago

I'm attempting to create an AOI,. I'm importing an L5X of the AOI, and I want to create the AOI tag and the required Input/Output/InOut tags. Currently, the AddOnInstruction class is very nice, but I can't figure out a way to, neatly, create the necessary tags for the AOI.

When I try to create the AOI tag (i.e. the tag that the AOI is tied to), I can't figure out a nice way to add in the proper tag Members using the AOI class Parameter property.

devenv_WZdVRGeFFz

tnunnink commented 6 months ago

Thanks for sharing this use case. I agree it would be nice to have something to convert the AOI instance to a Tag instance with populated members based on the parameters of the AOI. Perhaps even the same for DataType to Tag.

I added a few things to accomplish this, but before I publish it, I want to make sure we are on the same page. Is something like the following what you have in mind?

var aoi = new AddOnInstruction("MyAoi");
aoi.Parameters.Add(new Parameter("Param01", new DINT(123)));
aoi.Parameters.Add(new Parameter("Param02", new BOOL(true)));
aoi.Parameters.Add(new Parameter("Param02", new REAL(1.23f)));

var tag = aoi.ToTag("MyAoiTag");

tag.Should().NotBeNull();
tag.Name.Should().Be("MyAoiTag");
tag.Value.Should().BeOfType<ComplexData>();
tag.Value.Name.Should().Be("MyAoi");
tag.Members().ToList().Should().HaveCount(6); //this inclused built in EnableIn, EnableOut, root tag itself, as well as parameters.

In your case you would be able to do this

AOI = Content.Instructions.First();
AOITag = AOI.ToTag(aoiTagName);

The ToTag method builds the tag using the provided name, the AOI name, and the AOI parameters. This only generates tag members for parameters that are configured as Input or Output. Studio requires those parameters to be atomic data types, and these are the only parameter types I see showing up as data members for an AOI tag. This is actually a good thing for us, since if we had to create complex types, there would be no way to generate the full data structure unless it was defined somewhere (either in the L5X or statically).

Let me know what you think.

jojoguy10 commented 6 months ago

That looks great! However, I'm using an AOI that does use an InOut parameter to reference a device. I understand the problem you said about the parameters. Maybe it can create the tag with a user-defined string tag type and we assume (scary!) the data type has been created?

Here's the AOI L5X if you want to take a look at it.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<!--Use this AOI to map all inputs using a single integer (DO_01.XX).-->
<RSLogix5000Content SchemaRevision="1.0" SoftwareRevision="36.00" TargetName="AOI_OB16_Mapping" TargetType="AddOnInstructionDefinition" TargetRevision="1.0 " TargetLastEdited="2024-04-25T07:19:42.160Z" ContainsContext="true" ExportDate="Mon Apr 29 11:54:21 2024" ExportOptions="References NoRawData L5KData DecoratedData Context Dependencies ForceProtectedEncoding AllProjDocTrans">
    <Controller Use="Context" Name="autoGeneratedProject">
        <DataTypes Use="Context">
</DataTypes>
        <AddOnInstructionDefinitions Use="Context">
            <AddOnInstructionDefinition Use="Target" Name="AOI_OB16_Mapping" Revision="1.0" Vendor="Koops" ExecutePrescan="false" ExecutePostscan="false" ExecuteEnableInFalse="false" CreatedDate="2024-04-25T07:19:42.149Z" CreatedBy="ProgramGenerator" EditedDate="2024-04-25T07:19:42.160Z" EditedBy="ProgramGenerator" SoftwareRevision="33.0">
                <Description>
<![CDATA[Use this AOI to map all inputs using a single integer (DO_01.XX).]]></Description>
                <Parameters>
                    <Parameter Name="EnableIn" TagType="Base" DataType="BOOL" Usage="Input" Radix="Decimal" Required="false" Visible="false" ExternalAccess="Read Only">
                        <Description>
<![CDATA[Enable Input - System Defined Parameter]]></Description>
                    </Parameter>
                    <Parameter Name="EnableOut" TagType="Base" DataType="BOOL" Usage="Output" Radix="Decimal" Required="false" Visible="false" ExternalAccess="Read Only">
                        <Description>
<![CDATA[Enable Output - System Defined Parameter]]></Description>
                    </Parameter>
                    <Parameter Name="Device" TagType="Base" DataType="AB:5000_DO16:O:0" Usage="InOut" Required="true" Visible="true" Constant="false"/>
                    <Parameter Name="Output_Data" TagType="Base" DataType="INT" Usage="Input" Radix="Decimal" Required="true" Visible="true" ExternalAccess="Read Only">
                        <DefaultData Format="L5K">
<![CDATA[0]]></DefaultData>
                        <DefaultData Format="Decorated">
                            <DataValue DataType="INT" Radix="Decimal" Value="0"/>
                        </DefaultData>
                    </Parameter>
                </Parameters>
                <LocalTags/>
                <Routines>
                    <Routine Name="Logic" Type="RLL">
                        <RLLContent>
                            <Rung Number="0" Type="N">
                                <Text>
<![CDATA[NOP();]]></Text>
                            </Rung>
                            <Rung Number="1" Type="N">
                                <Comment>
<![CDATA[MAP INPUT CARD TO SINGLE INTEGER
---
INPUT CARD = AB:5000_DI16:O:0]]></Comment>
                                <Text>
<![CDATA[NOP();]]></Text>
                            </Rung>
                            <Rung Number="2" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt00.Data)OTE(Output_Data.0);]]></Text>
                            </Rung>
                            <Rung Number="3" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt01.Data)OTE(Output_Data.1);]]></Text>
                            </Rung>
                            <Rung Number="4" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt02.Data)OTE(Output_Data.2);]]></Text>
                            </Rung>
                            <Rung Number="5" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt03.Data)OTE(Output_Data.3);]]></Text>
                            </Rung>
                            <Rung Number="6" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt04.Data)OTE(Output_Data.4);]]></Text>
                            </Rung>
                            <Rung Number="7" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt05.Data)OTE(Output_Data.5);]]></Text>
                            </Rung>
                            <Rung Number="8" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt06.Data)OTE(Output_Data.6);]]></Text>
                            </Rung>
                            <Rung Number="9" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt07.Data)OTE(Output_Data.7);]]></Text>
                            </Rung>
                            <Rung Number="10" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt08.Data)OTE(Output_Data.8);]]></Text>
                            </Rung>
                            <Rung Number="11" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt09.Data)OTE(Output_Data.9);]]></Text>
                            </Rung>
                            <Rung Number="12" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt10.Data)OTE(Output_Data.10);]]></Text>
                            </Rung>
                            <Rung Number="13" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt11.Data)OTE(Output_Data.11);]]></Text>
                            </Rung>
                            <Rung Number="14" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt12.Data)OTE(Output_Data.12);]]></Text>
                            </Rung>
                            <Rung Number="15" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt13.Data)OTE(Output_Data.13);]]></Text>
                            </Rung>
                            <Rung Number="16" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt14.Data)OTE(Output_Data.14);]]></Text>
                            </Rung>
                            <Rung Number="17" Type="N">
                                <Text>
<![CDATA[XIC(Device.Pt15.Data)OTE(Output_Data.15);]]></Text>
                            </Rung>
                        </RLLContent>
                    </Routine>
                </Routines>
            </AddOnInstructionDefinition>
        </AddOnInstructionDefinitions>
    </Controller>
</RSLogix5000Content>
tnunnink commented 6 months ago

In your example Device would be a separate tag from the AOI tag instance though, correct?

Since InOut tags are references to other tags, you could just create them from the parameters, and then add them to the L5X alongside the AOI tag.

var content = L5X.Load("PathToFile");

var aoi = content.Instructions.Get("AOI_OB16_Mapping");

var aoiTag = aoi.ToTag("AOI_OB16_Mapping_Tag");

var aoiInOutTags = aoi.Parameters
    .Where(p => p.Usage == TagUsage.InOut)
    .Select(p => new Tag(p.Name, new ComplexData(p.DataType)));

content.Tags.Add(aoiTag);
content.Tags.AddRange(aoiInOutTags);

content.Save("PathToFile");

When you load this L5X, the Device tag should be created with the members matching the specified AB:5000_DO16:O:0 data type, assuming that type exists in the project. ComplexData is a "generic" data type container that you can just give a name (and optionally add members if desired), and it will refer to some type we don't know about until the Studio resolves it. In this case it looks like a module defined type, so that would depend on whether that module is also added to the project file.

Am I missing something you are still after? Do you want an easier way to just create a tag from a parameter? Do you want a method on AOI (maybe like GetTags() or something) that can generate all these tags (both AOI and InOut) in one go? Or does this solve your use case?

jojoguy10 commented 6 months ago

Oh that's a great point! I guess I wasn't confident that I knew what the use case would be for a ComplexData instance without any members. In theory, Studio should be able to resolve the AB:5000_DO16:O:0 data type if it loads in the Modules before it loads in the AOIs.

With that being said, being able to create tags for the Parameters of an AOI would be great!

tnunnink commented 6 months ago

Version 2.3.0 adds ToTag methods for AOI and Parameter as discussed here.

For parameter here is an example.

var parameter = new Parameter
{
    Name = "Test",
    DataType = "MyDataType"
};

var tag = parameter.ToTag("MyParameterTag");

tag.Should().NotBeNull();
tag.Name.Should().Be("MyParameterTag");
tag.DataType.Should().Be("MyDataType");
tag.Value.Should().BeOfType<ComplexData>();