I’m working on a project that will need to convert between imperial and metric units. Numbers will be coming in as Imperial, and be converted to metric if that is selected. I’m currently using a session property where FALSE = Imperial (normal) and TRUE = Metric. I did have this in derived tags, but that is not a long-term, scalable solution especially when it comes to historizing. I have a few ideas on how to implement the conversions at the UI, but what I’m struggling to figure out is where I should put the official repository of what units are converted to what and what those scaling factors are. For instance, my project has three kinds of process values: pressure, flow, and level. For pressure, imperial units are psi, metric units are mbar. Flow is in gpm for imperial and m³/h in metric. Level is in ft or m. Those conversions are all fairly straightforward. You multiply one number by a factor and get the other. If temperature is ever added, that’s trickier of course, because it’s more than just a multiplier but also an offset, but I could probably store that too.
I want all that conversion information in one place, a “single source of truth.” I want to store the multipliers, offsets (if required), and engineering unit strings (gpm,ft,m,m³/h, etc.). This project is running on the Cloud, and utilized project inheritance, so wherever I put that “source of truth,” it needs to be inheritable, so session properties are probably out unless I can write a “startup” script that pushes those values into a session property when a new session starts. Then, everything in the entire project can access those conversion factors at runtime, and pretty easily.
My questions are, can I script that? If so, should I store all that conversion information hard-coded in the script itself, OR in some kind of memory tag that contains a dataset? I’m kind of liking the tag idea because I could conceivably make that configurable by project as well. If someone wants to make metric pressures read in bar instead of millibar, for example, I can make an interface for them to do that. Is there any reason not to do it that way? How would that handle “non-standard” characters? I’m kind of a stickler for using the proper symbols on the user interface side. For instance, I like putting m³ instead of m3, or °F instead of degF. If I’m passing that through a script, will those “alt-key” characters pass through?
I recommend storing all conversions in a database. Store the factors and offsets for conversion in each direction in columns of the table. Perhaps create a UI to manipulate conveniently.
Use the .engUnit property of your tags to driver the UI, and to present the conversion options from the DB.
Use the appropriate unicode characters for your units. Remember that script string constants need the u'°F' prefixed syntax to not scramble the unicode.
If you cannot use a DB, consider storing a two-level dictionary keyed by unit string. Placed in the root project of your inheritance tree.
I would need to run this same application on Cloud and Edge editions, so I couldn’t use a Database on the Edge unfortunately. When you say store a dictionary, do you mean in a tag or somewhere else?
You’re saying store the dictionary in a json file in the ignition install folder and then use a startup script to read from that and push it into the session props?
OK. So store them in my script library, and then when I need the conversion values, have my views/templates pull those values into custom properties and use those in expression transforms to do the conversions?
So I’d basically have a script in my library that all it does is return a dictionary of conversion values? I imagine I wouldn’t want to have the scripts actually do the conversion as that would probably be a lot slower than using expression transforms.
I don't think it would matter much whether your script did the conversion or you did it in expressions. Script would be cleaner and easier to maintain, expression would be faster.
Or a script where I gave it some variables like the value to convert and what kind of process calculation I was doing, it returned a converted value, and I utilized the runScript expression in a binding on the UI side of things. You’re saying that wouldn’t matter much but you’d prefer to do the math in a script? I’m inclined to agree just because just in case temperatures get added to my process, the script can handle that math more cleanly and my UI templates can be a lot more uniform/I would need fewer of them.
I’d only pause regarding doing the math in a script if I had a lot of those calculations to do on a single view. Like if I was converting trend values, for example.
Right. I'd use a script that takes a value, a "from" unit string, and a "to" unit string.
It would look up both units in the outer dictionary--whichever is present would indicate whether a forward or reverse conversion--then look up the other in the inner dictionary. That should yield an object (I'd use a tuple) of multipliers and offsets for each direction.
I'd use a separate script to simply enumerate the inner dictionary keys for an outer key, so you can display unit choices in a drop-down in your UI. That selection would go in a session property.
I apologize for having a bit of difficulty understanding.
By inner dictionary do you mean the one that my project script generates? The one that returns the multipliers/offsets for different unit conversions? And outer dictionary is the JSON file in the ignition installation folder?
Your collection of conversions needs to use the unit strings as keys. You have two keys for each conversion, so you need an outer dictionary keyed by "from", and an inner dictionary keyed by "to", and the inner dictionary would hold the numbers. The entire nested construct would be in the JSON in your filesystem.
So, outer dictionary I’m imagining a table of strings like psi, gpm, etc. as the keys, and then the values would be the values they convert TO in metric? So for example my outer table is:
key | value
-----------
psi | mbar
ft | m
mbar| psi
m | ft
And then my inner table would be something like:
key | value
-----------
mbar| 63.9478
m | 0.304
psi | 0.01564
ft | 3.29
Am I following? I know the numbers are probably wrong but you get the idea.
For the next part, my session property would be an object with my outer keys as the first layer, the next layer being my inner keys, and the final layer taking those tuples and enumerating them like “fwdMult,fwdOffset…” Is that right?
For the next part, my session property would be an object with my outer keys as the first layer, the next layer being my inner keys, and the final layer taking those tuples and enumerating them like “fwdMult,fwdOffset…” Is that right?
True. reverse multipliers are always going to be 1/fwdMult and reverse offsets are always going to be fwdOffset * -1 It would help prevent human error if I made this configurable.
If I’m on the right track, I can see how I could use the .EngUnits property of a tag as that would fill in half the info I needed already. I’d just need to supply the template with units to convert to as a parameter. But then again I could throw that into a custom property of the tag itself as well.
Only thing is, on the Cloud I’d have to use a database like you said because I don’t know if Ignition would have access to its VM’s file system.
The session property would simply be the dictionary holding source units as keys and display units as values. Omit any source unit keys that aren't to be converted.