libplctag / libplctag.NET

A .NET wrapper for libplctag.
https://libplctag.github.io/
Mozilla Public License 2.0
220 stars 55 forks source link

UDT ID / Tag Type #408

Closed Powerchamp closed 2 weeks ago

Powerchamp commented 3 months ago

Hi there.

Wondering if there is a relationship between UDT ID and Tag Type?

I am looking to dynamically read tags by fetching the UDT's first and then tags etc.

Any help appreciated.

TN

kyle-github commented 3 months ago

I am not sure I understand the question. When you read the tag metadata (at least on Rockwell PLCs) you get a 16-bit combined type value. The top 4 bits are used to indicate what kind of type it is:

Bit Meaning 0-11 tag or element type 12 Reserved 13-14 Array dims, 0 = not an array, 1 = one dimension, 2 = two dimensions, 3 = three dimensions 15 0 = atomic type (DINT, SINT etc.), 1 = structure/UDT

When you read a tag, you get a pseudo type back. I say pseudo because it is not a full type if the tag is an array or UDT. In the case of a UDT, you get a hash calculated across the field names and types of fields. The hash is not the UDT ID. The hash is so small that it is definitely not unique.

If the tag is an atomic type or an array of an atomic type, you get the atomic type ID and that is really the ID.

I have no insight as to why Rockwell chose to do this with the UDT hashes. It seems like just returning the 16-bit type value they used in the metadata would have been equally easy and much, much nicer for anyone trying to understand the data.

Powerchamp commented 3 months ago

Hi Kyle, thanks for the fast reply and info.

What I am looking for is a way to determine the UDT ID from the pseudo element type.

E.g I fetch all tags and UDT's, then use tag element types to obtain the UDT ID's and decode UDT tags with no prior knowledge of the UDT structures.

Please excuse me if there is better way of doing this. I am a long time fan and user of the base library but have using it for bulk reads of arrays of known data types.

TN

Powerchamp commented 3 months ago

Hi Kyle, thanks for the fast reply and info.

What I am looking for is a way to determine the UDT ID from the pseudo element type.

E.g I fetch all tags and UDT's, then use tag element types to obtain the UDT ID's and decode UDT tags with no prior knowledge of the UDT structures.

Please excuse me if there is better way of doing this. I am a long time fan and user of the base library but have using it for bulk reads of arrays of known data types.

TN

Sorry, yes talking Rockwell here.

kyle-github commented 3 months ago

You cannot. It might work to compare hashes, it might not. Depends on whether or not the hashes happen to be unique. If they are, you can match up the hash in the type data in the tag and the hash in the UDT definition. But you cannot get the ID from the data that comes back from the tag and you cannot look up the UDT by hash. Thanks, Rockwell!

Note that fetching all the UDTs is not straightforward. You need to have the UDT instance ID. That is NOT returned when you read a tag. There are up to 4095 of them and you will need to try each ID one at a time. You might be able to use service 0x55 (I think, maybe 0x5F?) to get all the instance IDs of the UDT class (0x6C I think).

The CIP request following might get you all the IDs (that fit, you may have to do another read starting with the last ID plus one to get more).

0x55, 0x02, 0x20, 0x6c, 0x24, 0x00

That (or use service 0x5F), might return you instance IDs of the UDT class. I am not sure that 0x55 works on all classes.

Once you have the UDT instance ID, you can use service 0x03 to get the fields you want to get the UDT metadata:

0x03, - get attribute list service 0x02, - path is two words (16-bit) long 0x20, 0x6c, 0x24, 0x42, - instance 0x42's of class 0x6c 0x04, - get 4 attributes 0x04, - number of 32-bit words in the template definition 0x05, - size of the UDT in bytes 0x02, - number of members/fields 0x01 - handle/type of the structure

It is in eip_cip_special.c in src/protocols/ab/.

kyle-github commented 3 months ago

Oops, the count of attributes and the attribute IDs should all be 16-bit.

raentzmi commented 3 months ago

IMHO - I am a retired Rockwell architect - and there have been a lot of conversations internally about this subject and being retired I feel I can comment - in a positive way.

We wanted a way to have a unique way to id a data type other than the name of the udt. We did not feel that the name could be as unique as we wanted.

What I would suggest is use a member of the udt as your ID - if the udt has a element that is named id - and you have knowledge of the contents (use a DINT) of 123456 as an example you may want to use this as a reference to a known data type and build content based on that - I would say its a weak data reference (contract).

You will see some data types that contain a property called InfoID that do this in the VFD drives starting with firmware 12 - other firmware for a lot of the Intelligent Devices will start to implement this InfoID concept. The drive uses a concept of symbols to do this so the data is visible on FactoryTalk LiveData.

This is not to replace control of the devices - or I/O control - but from an information need this was considered a consistent way to deliver data without impacting controllers.

I have not tried to go directly to a device other than a PLC with this library yet - right now I would map the data from a device to a UDT of my design and tag it with a ID that I can depend on.

I am thinking that if I were an OEM or an SI this would allow me to build content and deploy much faster than hand building content.

RAE

On Fri, Jul 26, 2024 at 2:11 PM Kyle Hayes @.***> wrote:

Oops, the count of attributes and the attribute IDs should all be 16-bit.

— Reply to this email directly, view it on GitHub https://github.com/libplctag/libplctag.NET/issues/408#issuecomment-2253322604, or unsubscribe https://github.com/notifications/unsubscribe-auth/AL4TWK73KB6NHSCYBHUTQZTZOKNMZAVCNFSM6AAAAABLQJ26HWVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDENJTGMZDENRQGQ . You are receiving this because you are subscribed to this thread.Message ID: @.***>

kyle-github commented 3 months ago

Wow! Thanks for dropping by, @raentzmi! I hope we can pick your brain for insights!

As far as I understand the OP's question/problem, they want to read the value of a tag, then use the type information that comes with that to determine which UDT type the tag is. The challenge is that the 2 or 4-byte type info that comes with the tag data does not contain the UDT ID. And this is where I wonder why it was done this way. Just returning the UDT instance ID, which is necessary to look up pretty much anything about the UDT, would have been hugely helpful and takes the same number of bits.

There are a couple ways of doing this:

  1. Load all the UDTs. Using service 0x55 to get selected attributes from all instances of class 0x6C (UDTs) would be a good start. Because of the potential size of the result, a fairly minimal set of data should be retrieved like the UDT hash, UDT class instance ID and maybe a couple more. Using the hash returned in the tag read response, you can try to match the UDT. As mentioned above, this is not guaranteed to work because the hashes can easily collide. You might be able to use other things like the size to disambiguate the hash values. A 12-bit hash is really too small to be safe.
  2. Load a small subset of the attributes for all tags. Just the tag name, tag class instance ID, and the 2-byte tag type will be sufficient. When you have a tag you want to get a type for, look up the name in the data you loaded and get the 16-bit tag type. That will have the UDT ID as the lower 12 bits if the tag is a UDT type. Then you can use the normal 0x03 service to get the attributes of the UDT that you want.
  3. Do a little more work and use service 0x55 to get the minimum data for the both tags and for the UDTs. Then you have a direct match right away. I am not sure this really saves any steps.

I am leaning toward the second option. Using service 0x55 makes it feasible to load just the bare minimum information for all tags. Then you can match the first symbolic segment of the tag name and pull whatever UDT data you need. This allows you to only load extra attributes about tags and UDTs when you actually need them. Very few applications look at all the tags in a PLCs. Generally the number viewed is a lot lower than the total number of tags.

In my tests, I have used 0x55 to get tag names and IDs for more than 200 tags at a time in one request. So this part can be quite fast. There does not seem to be a way to limit the number of results that come back.

There are some additional challenges such as on Micro8x0 PLCs where they appear to not support the UDT class. Or at least do not support all the same services. Omron is its own special thing and would need to be treated differently though I think there are rough equivalents to the Rockwell services.

I really want to find a way to do this that only loads tag metadata on demand rather than loading it all. What is currently implemented (and documented) in Rockwell Logix-class PLCs is fine for tools like Logix Studio etc. that need to be able to show all tags and all UDT types. But if you just want the data for a few tags...

For example, any tag that is either a single atomic value or an array of atomic values does have its base type in the data returned when you read the tag's value. A large portion of the tags in a PLC program are like this and thus the total number of tags for which you might need to go get more metadata to get the UDT instance ID and definition are small.

Omron has this really cool feature where you can use service 0x11 (get all attributes) on a tag name. You just encode the name as a standard extended symbolic segment and use it instead of the usual CIP class/instance pair. It is really nice. But, they do not include the tag instance ID in that data!

Rockwell documents a service on one of the main system classes (message router?) that will take a symbolic name and return the class/instance value for it. Unfortunately, that service is optional and I have no PLCs on which it is implemented.

Sorry for the long response. Not enough sleep makes me chatty.

kyle-github commented 3 months ago

@Powerchamp to get back to your original question, if you load all the tags and all the UDTs then you do most of what the existing "@tags" functionality does now, actually a bit more. You need to get at least the following:

Using that information, you can look up the info about the UDTs that are used. You could read them all in at once using something like service 0x55, but I just read them one at a time with the "@udt/xxxx" tag type. This seems to be a good balance between too much overhead and time (UDT definitions are big) and complete data.

Powerchamp commented 3 months ago

Amazing, thanks for the detailed replies Kyle - and a big hello / thank you to Raentzmi. Always super neat to get some insight into the design process first hand.

My current use case includes reading data from existing systems which I would prefer to avoid making changes to, but I am interested to hear about the InfoID member. Will investigate further to see how it could be leveraged in future projects.

I will have a play this week with your suggestions Kyle and report back, likely with more questions hah.

Many thanks again,

TN