[FEATURE] - Radix support when reading tag values
Type of Feature Request
- [ ] missing feature
- [ ] feature available in another library: please specify
- [ ] change to API
- [x] enhancing a current feature
- [ ] removal of a broken/unsupported/etc feature
- [ ] other: please specify
Feature Description I was wondering if it would be possible to get the radix for a symbol from the controller, and perhaps auto format the read values into that radix (maybe like a getter that auto formats the value to a string in the correct radix, or a getter that returns the radix so the developer can format the value correctly)
Possible Solution
Not sure, I took a look at ADF file cip107r07v01.ADF and nothing stood out to me as to if the radix was stored there. I will do some testing later to confirm. Do you know if the radix is stored in the controller and where that might be?
Thanks, Trevor
This may be possible to reverse engineer, but I have not tried it. Attribute 6 of the Symbol object may be one possible location for it, according to this Micro800 doc it's called software control and it is 32 bits for whatever. I was able to figure out that bit 26 indicated if a tag was an alias to another or not, but that's as far as I went. If it's stored in that, should be able brute force a bunch of tags and see if there is a pattern. As for formatting the value, yeah that's probably possible. Since radix only determines how the value is displayed, I think implementing it as part of the __str__ method on the data types makes the most sense.
Hmm, I'll take a look today and see what I can find. I think implementing it in __str__ would be great.
OK, you are correct, it is in software control (attr 6). I will work on decoding which bits to what it means.
OK, on RSLogix 5000 v20 (I don't have anything newer at home to test with) here are the values for the supported radix. (These are the only radix that RSLogix 5000 v20 has in the drop downs.
Bits 28 - 31 = Radix in Attribute 6 of the symbol class
Hex Values:
0x2 = Binary (Default even when no radix can be selected in Studio/RSLogix 5000
0x3 = Octal
0x4 = Decimal
0x5 = Hex
0x6 = Exponential
0x7 = Float
0x8 = ASCII
0xA = Date/Time
Other values may be used in newer versions of studio, I imagine there may be values used for unsigned datatypes. When I get back to work next week I will do some testing on v35 and see what other radix are supported.
~~EDIT: Are you sure it was bit 26? That would put it in the middle of the radix bits... Or maybe I have not had enough coffee yet this morning and have my Little/Big Endian backwards. Either way I better drink another cup of coffee and look at it a bit later.~~
I definitely needed more coffee. I was looking at bits 28-31. I corrected it above.
Oh that's awesome, thanks for getting that so fast.
No problem. I am more than happy to do testing once your ready. As mentioned, once I get back to work, I'll check newer versions for more radix types.
A radix value of 0x0 is used for some built-in types like timers to indicate a null radix.
I added initial support based on what we know currently, you can try it out on the radix branch. I'm not sure I can add it to the __str__ method though, because the tag values are converted into normal python types (int, float, list, etc) after they're read. Instead, after reading the tag, the radix can be looked up and it will be up to the user to decide what to do, e.g.:
tag = plc.read('some_tag')
formatted_tag = format_tag(tag.value, plc.tags[tag.tag]['radix'])
In this fabled rewrite I've been working on, I've created custom objects for all of the CIP data types and it would be much easier to override their __str__ methods then. Like right now the DINT class is never really instantiated, only the encode and decode class methods are used to convert between bytes and ints. It the new system the DINT class is a subclass of int and I have much more control over classes.
We could add it as a property to the Tag class and use it for it's __str__, but that would be a breaking change. That class is a NamedTuple, so adding anymore attributes would break for anyone unpacking it as a tuple (tag_name, value, type, error = tag). V2 will have a number of breaking changes, so I think I'd prefer to do it then (and have a different response dataclass in place of Tag).
I think that would be fine for now. I'll do some testing today on the radix branch with my v16 and v20 ControlLogix controllers. Unfortunately I do not have a micro800 to test on at home (or even at work) to see if the radix is stored differently.
I have not yet figured out where the radix for struct members is stored. Currently I'm looking for that, along with the external access information for struct members. (I would also love to know where the tag descriptions are stored for v21 and above, but I won't be able to test anything until I get back to work and have access to L7x and L8xE controllers)
Also, unrelated to the radix, I am also grabbing if a tag is flagged as constant right now. It is nice to know so that I do not accidently try to write to a constant tag, and I can display to the user that a tag is a constant. Attribute 11 of the symbol class stored as an SINT where 0 is not a constant and 1 is a constant. That may perhaps be another useful piece of information to grab in the future.
OK, I've made progress with structs. Still confused on some parts of it, and it is not the same as tags, but I do know how to get the radix.
The radix info is only stored on user created types, and is in the extra payload at the end of the template name ;.
See line 780: https://github.com/ottowayi/pycomm3/blob/f3c41ebd2fbb29484bf13d0bab0005fee4b8f0c0/pycomm3/logix_driver.py#L780
So the payload of the info is after the semicolon.
The structure is 0x6e, followed by 32bits per top level member. So if a struct member is of a type of another struct, only the member has an entry, not the member's sub-members. Hidden members (who's name starts with ZZZZZZZZZZ do have an entry)
In each 32bit entry, the lowest 4 bits are the radix. The values I have found are:
Struct Radix:
BitString/Unknown for ZZZZZZZZZZ members: 0x2
Binary: 0x3
Octal: 0x4
Decimal: 0x5
Hex: 0x6
Float: 0x8
Exponential: 0x7
ASCII: 0x9
Date/Time: 0xB
Examples:
Payload: 6e 4b41
Payload: 6e 4341 4441 4541 4641 4941 4841 4741
Payload: 6e 4542 4343 4341 4441 4841 4141 4141 4141
Got Member: ZZZZZZZZZZTEST10
Got Member: B1
Got Member: I1
Got Member: I2
Got Member: F1
Got Member: S1
Got Member: Val1
Got Member: Val2
Got Member: S2
Got Member: ZZZZZZZZZZSTRUCT20
Got Member: Flag
Got Member: Data
Got Member: Val1
Got Member: Val2
Got Member: S3
Got Member: ZZZZZZZZZZSTRUCT210
Got Member: Flag
Got Member: Data
Got Member: Val1
Got Member: Val2
I hope I explained that OK, if you have questions, please let me know.
Interestingly, external access settings for the members are not stored here (I figured they would be). Still on the hunt for them.