Jump to content

Configuration File (.INI) Best Practices


Recommended Posts

What is the standard, or is there one, for storing configuration data that will be used to initialize I/O channels and GUI "stuff"?

Example Part 1: Executable needs to read from a file some UI initialization Info:

[GUI]

type = touchscreen

size = 800x600

InstrumentName = GS342

Language = Englusskie

Example part 2: We need to know what class of I/O to use for each channel:

[GS342.CH1]

ID = MicroFlex

ComPort = Com1

PropotionalBand1 = .35

ProportionalBand2 = .67

[GS342.CH2]

ID = USB-TCAI

Node = 0

InPort = 0

OutPort1 = 0

OutPort2 = 0

Slope = 3894

Offset = 49920

[And on and on...]

The "ID" tells me which class to use for that channel and the rest of the stuff has to go into that classes vi set somewhere.

My question is: Is there a better way to do this? Remember, users need to be able to change these values and I want them to be readable for troubleshooting. Also, is it best to have a library of all possible values and read them into a typedef cluster or should I read them as an array of Name/Val pairs and then pull them out in the class vi that needs them?

I cannot just white them into each class because they change (Proportional Bands need to be tweaked, etc...) I also would like to keep the property options somewhat standardized (ID, Max, Min, Pb, Com, etc...) so when I create new I/O classes I can just unbundle the options I need.

Thanks, Jeremy

Link to comment

The only standard for an INI file that I'm aware of is how the file itself is formatted, not the structure of the information inside it. I expect your best method of laying out the data is to mirror how you would put the data into a cluster/data structure.

Link to comment

What is the standard, or is there one, for storing configuration data that will be used to initialize I/O channels and GUI "stuff"?

Nope, there is no standard. Different needs have different solutions. On one recent project the customer didn't want the users to be able to mess with the configuration settings, so I stored it in binary format. If the config file needs to be human readable your main options are the XML palette or the Config File palette... unless you want to write your own parser.

As for how to get the config data into an object, one thing I've done in the past is write Config classes with multiple Create methods. For example, there might be a CreateConfig(Path).vi that takes a path as the argument for times when I need to load the config from disk, and another CreateConfig(Data).vi that accepts an array of key value pairs for those times when I already have the data in memory.

One other thing, I have found it beneficial to use unique config objects for different devices rather than one giant config object containing all the configuration data. That way I can just send the config object to the device object instead of issuing multiple configuration commands.

Link to comment
One other thing, I have found it beneficial to use unique config objects for different devices rather than one giant config object containing all the configuration data. That way I can just send the config object to the device object instead of issuing multiple configuration commands.

What I was thinking was to load all the key/vals into an array and then dole it out to the proper config object for each device. That way I am only reading the .ini file at the "Initialization" stage and I can send everything where it is needed as message objects using that handy dandy LapDog.

It just seems like that's going back to the old String/variant cluster that LVOOP and LapDog made obsolete. I would have to search though it and transfer the needed values into the CreateConfig(data).vi. BUT, if I have a CreateConfig(file).vi for each Config object then I need a Section in the .ini named after that object. What happens if I use that object for more than one channel? Have the CreatConfig(file).vi take a file and a channel number!

Now I think I get it, each object gets its own section and there's an [instrument] section that tells me which objects to launch for each channel. I read [instrument]'s values into a for loop of case structure which creates all my I/O objects. Those objects read their section to get going and now we're ready to blow something up :thumbup1: Please let me know if I'm off the mark here.

Link to comment

What I was thinking was to load all the key/vals into an array and then dole it out to the proper config object for each device. That way I am only reading the .ini file at the "Initialization" stage and I can send everything where it is needed as message objects using that handy dandy LapDog.

[snip]

Since you're using objects, why not just open the config file and pass the reference to each object? Each object would be responsible for reading its own section rather than parsing through an array.

  • Like 1
Link to comment

That's where the train of thought led me:

there's an [instrument] section that tells me which objects to launch for each channel. I read [instrument]'s values into a for loop of case structure which creates all my I/O objects. Those objects read their section to get going and now we're ready to blow something up

I'll open the file, read the list of Objects and their respective channel number, then send the file reference into a ConfigObject.vi for each object I need to configure. Those objects just tack the channel number onto their name and read all the values in that section [(ObjectName).(ChannelNumber)].

I was going to read the key values right into the object since a class "object" is a cluster but I just found out you cannot use a property node to read the name or write the value of items inside a class control. I will have to place typedefs of controls into the object and do it that way.

Link to comment

post-15786-0-98772900-1328671316_thumb.j

Here I use 2 typedefs to compose a config object. One for string key values and one for numeric. This way I can fill in default values and only use the config.ini file to override those defaults. This VI is called when I want to initialize a particular controller (UCA Temp controller) and the object out will go into a Dynamically Dispatched Launch.vi

Is this ridiculous? Suggestions for a simpler way?

Link to comment

For smaller, relatively unstructured systems (especially when the requirements are still largely unknown), I tend to drop the config file contents into a variant as a set of name/value variant attributes. This maps pretty well to the native ini format. I can pass the top level variant around, and pull out the values as if from a dictionary. There's still the downside of needing to typecast things as they're pulled out of the variant attributes, but overall, it's a very flexible method.

I'm not sure how this would work for object initialization. I suppose that each object could contain a single top level variant dictionary, corresponding to it's own section(s) from the ini file.

Like most flexible methods, this can be taken too far. Keep in mind that variant attributes hide information, and can make debugging a nightmare.

Joe Z.

  • Like 1
Link to comment

We let the Flatten To XML and Unflatten From XML handle the messiness.

We create a typedef'd cluster (usually, doesn't have to be a cluster; it isn't an object since a class does not have an editable control that we can put on a front panel) that defines a tightly connected set of data and use the Write To XML method to write the cluster data to a file (<clusterName.xml>). Appropriate classes have the cluster in their private data. (Note that a class can have 0..n of these, and multiple classes can use the same cluster.) Then initializing the object is as simple as what we show in the attached file.

post-6989-0-16677600-1328728917.png

(OK, our full, flexible, configuration editor has a lot of features that make it a bit more complicated, but this is the essence of it.)

Link to comment

I've used both the XML's that Paul suggested and conventional .ini style files. The ini version is better if you really expect users to make changes - XML is fine if you just want human readable for troubleshooting or such. I also think Daklu's suggestion to use individual config files for unique devices is a good one since changes to any one device don't affect others and it's just really easy to grab the config you need where you need it. I do use clusters in the class private data to faclitate the read/write. And I would suggest you check out the Read/Write anything VIs (http://www.mooregood...abViewStuff.htm) since these are efficient and easy to use. Also, use the Get System Directory VI from the File Constants palette to put the files into a folder for your app in the Public Application Data directory. This makes management relatively easy and safe. For ini files, I'll use the Get LV Class Path VI and strip the name to use for naming the ini file. This makes the ini files effectively self-naming and easy to manage. I also have reuse code that will look for a config file and if it doesn't find it, create one for that class. That helps because I don't have to distribute config files with the source but they're still relatively easy to deploy with the executable.

Mark

Mark

  • Like 1
Link to comment

There's still the downside of needing to typecast things as they're pulled out of the variant attributes

That's what gets me, having to carry a "dictionary" to tell me what I'm unflattening...even worse, a dictionary for every class. I think I would prefer to just save it all as text and change it once it is needed. Then I can convert it to an int or dbl, path or string, depending on what I need the value for at that moment.

We create a typedef'd cluster (usually, doesn't have to be a cluster; it isn't an object since a class does not have an editable control that we can put on a front panel)

This bothers me that you cannot treat an object as a control cluster privately. I had the thought that I could use the Actor's object as the config cluster since the only other thing it carries is a CallerQueue. Using my Actor-ish / M-V-C (I'm now reading that book you referenced!) / LapDog abusing framework, I only need the Actor object for LaunchActor so it doesn't seem to create more overhead by loading all the config info right into it. Just can't figure out any way to pull the label.text property out of an object's controls. It seems silly to need a typedef to represent an object. I guess if the object is just wrapped around the typedef it's ok but I don't like all those extra bits in my project.

Looks like I'm sticking to this unless somebody tells me it just "ain't right". I'm not ready to add XML to my LVOOP newly-stuffed neurons just yet.

post-15786-0-41050700-1328748552_thumb.j

Edited by jbjorlie
Link to comment
The ini version is better if you really expect users to make changes

I do expect the user to make changes, especially the PhD's!

I also think Daklu's suggestion to use individual config files for unique devices is a good one

I like that, but for troubleshooting with a customer on the other side of the world it is just easier to have them send me one config file.

For ini files, I'll use the Get LV Class Path VI and strip the name to use for naming the ini file.

Won't this land you in the Program Files directory? That's where my .exe is and I thought you cannot write to that in Windows 7 without Admin privileges?

I used to store the config.ini file there but was thinking of moving it to the root directory and allowing edits through the application builder. I don't want to bury it in AppData or UserFiles/x/y/z/1000FoldersDeep. This application normally runs on an embedded PC with not much else on it so I want to store test files, test setup files, and config files in a big folder that's easy to get to in a couple of touchscreen taps. It's hard enough getting in/out of windows folders with a touchscreen and desktop shortcuts seem to get lost often by users with big greasy fingers.

Link to comment

Won't this land you in the Program Files directory? That's where my .exe is and I thought you cannot write to that in Windows 7 without Admin privileges?

I used to store the config.ini file there but was thinking of moving it to the root directory and allowing edits through the application builder. I don't want to bury it in AppData or UserFiles/x/y/z/1000FoldersDeep. This application normally runs on an embedded PC with not much else on it so I want to store test files, test setup files, and config files in a big folder that's easy to get to in a couple of touchscreen taps. It's hard enough getting in/out of windows folders with a touchscreen and desktop shortcuts seem to get lost often by users with big greasy fingers.

I don't use the complete path to the class - I strip the class name so a class like TempSensor.lvclass would have config file named <Public Application Data>\MyProject\TempSensor.ini or something like that. And the reason I use the <Public Application Data> is that it works on all the Windows targets I use for all users (don't need admin rights to modify). I don't have to deploy to Windows embedded, so I can't speak to that, so your needs may be different.

Mark

Link to comment
This bothers me that you cannot treat an object as a control cluster privately. I had the thought that I could use the Actor's object as the config cluster since the only other thing it carries is a CallerQueue. Using my Actor-ish / M-V-C (I'm now reading that book you referenced!) / LapDog abusing framework, I only need the Actor object for LaunchActor so it doesn't seem to create more overhead by loading all the config info right into it. Just can't figure out any way to pull the label.text property out of an object's controls. It seems silly to need a typedef to represent an object. I guess if the object is just wrapped around the typedef it's ok but I don't like all those extra bits in my project.

For the record, LabVIEW allows you to create an XControl that is a friend of a class as a way of sort of creating an editable control for a class. (I don't do this myself since XControls are too complex and limited, and I think the "friends" concept is not valuable, but it is an option.)

The ini version is better if you really expect users to make changes - XML is fine if you just want human readable for troubleshooting or such.

For clarification, in our situation our users edit the values in a configuration editor we created, not the XML files directly. On the other hand, since there is only a small amount of information in each XML file, it is quite reasonable to edit the XML file directly if necessary, although certainly an ini file might be easier.

Link to comment

Now I think I get it, each object gets its own section and there's an [instrument] section that tells me which objects to launch for each channel... Please let me know if I'm off the mark here.

Yep, that's what I was thinking if a single readable config file is a requirement.

Looks like I'm sticking to this unless somebody tells me it just "ain't right". I'm not ready to add XML to my LVOOP newly-stuffed neurons just yet.

Meh, if it works for you it's right... but here are some things you may or may not have considered:

-Using FP controls to store data in memory is somewhat common, but imo it is needlessly confusing. It's also much slower to read/write than block diagram data. As a general rule I only put something on the FP if it's a sub vi input, output, or UI stuff.

-Be sure to think about how you will handle versioning issues with the ini file. What happens if the software is expecting version 2 and the ini file is still using version 1, or vice versa. Like Paul, I use typedefs whenever class data is being serialized. It feels redundant but I find it makes it easier to manage versioning.

-We developers like writing cool and elegant code that adapts to different situations. It more fun and it's way more interesting than boring mundane code. On the other hand, simple mundane solutions are usually easier to write and read than the clever solutions.

post-7603-0-65365300-1328826328_thumb.pn

Link to comment

Hey, that seems like a reasonable way to go. Thanks for taking the time to paint a picture. I can just subvi that for loop and have a nice plug-in file reader where all I need to do is delete unused defaults from the array CONST_Config... but then I have a front panel control in the subvi don't I. This is only a problem because I want to know all the config key options in one maintainable list somewhere.

-Be sure to think about how you will handle versioning issues with the ini file.

Are you talking about changes in what the possible keys are from v1 to v2, etc? If so, that's what I mean by wanting a master list of possible keys to use for initializing any "actor" class.

On the mundane vs clever note, After spending many nights learning LVOOP and complex messaging systems I sometimes think spaghetti would be easier to follow. However, the flexibility and maintenance of LVOOP seems worthwhile. So long as I can get config data into the objects in a standard lexicon/typedef of keys and figure out some way to standardize my message objects for masses of actors all will be good.

BTW - LapDog rocks! Perfect blend of "LV-legacy" and LV-OOP messaging capabilities.

Edited by jbjorlie
Link to comment

I can just subvi that for loop and have a nice plug-in file reader where all I need to do is delete unused defaults from the array CONST_Config...

If by "plug-in" file reader you mean, "I can drop it anywhere and it will read the ini file," then yes, you could do that. (Though typically you'd only want to read the ini file once and just keep the settings in memory.) If you mean "I can use it as a generic sub vi to read an arbitrary ini file," then no, you cannot. You'll have to create a unique copy for each ini file.

This is only a problem because I want to know all the config key options in one maintainable list somewhere.

What I often do is wrap my constants in a vi and give it a really obvious name, like "_CONST_ConfigurationKeys.vi." That makes it really easy to find in the project. If I also want to store the default values I might have a second string array output terminal for the default values. You could use a 2d array as well. Then all the ini file keys and default values are stored on the block diagram of an easy to find vi, and that's *all* that's on the block diagram. Makes maintenance pretty easy.

BTW - LapDog rocks! Perfect blend of "LV-legacy" and LV-OOP messaging capabilities.

Glad you like it. Keeping it simple has been one of my primary goals. That brings the total number of LapDog.Messaging users up to... 3. :D

Link to comment

I really like this thread, as this is an area I am always trying to improve.

I used to write config files like this (bar the input into class just output a cluster, esp for <2009 RT apps):

post-7603-0-65365300-1328826328_thumb.png

The only thing I can add is that I found it easier using an enum as the key due to typos, esp when the number of keys get large, but that was just personal preference.

Where variants can be used, I prefer to leverage existing APIs (such as OpenG) as it just so much less work to read and write to disk and extends the LabVIEW Config API by supporting other datatypes (e.g. arrays).

Here is how I do configs with LVOOP.

Mentioned in other threads I like to write the Object's private data to disk from within the Object.

The data that is required to be persisted is stored in a non-typedef cluster.

I usually end up having reusable Objects, application Objects (including general Config Objects) that I want to flatten to disk.

These can be basic Objects, extended Objects, MFVIs/modules, arrays of Objects and Objects composed of other Objects - I am able to do this albeit there is a little more work for the last two.

Typically I like to use a single ini file (as I like the format).

I'll use a simple application as an example

Here is the startup code for the application, it is quite simple and contains reuse modules (the purple banner object extends the blue banner object):

post-10325-0-96385500-1328914118_thumb.p

Inside the second VI from the left (curves settings constructor) the structure of the file is setup (header, sections):

post-10325-0-65937000-1328914127_thumb.p

When the Read VI (reuse) is called in the startup (third VI from the left) it performs checks, reads the header (meta data) and then the section data

post-10325-0-31187100-1328914125_thumb.p

The section data is an override VI which extends the base case and is custom for every application. It calls the Object's read to disk method

post-10325-0-31611800-1328914123_thumb.p

The reading is delegated to a File Interface (reuse) Object (not shown) which has a variant interface - it may not be the best interface but I found this was the easiest way for reuse.

In the base implementation it uses OpenG ini format (abstracted under a few layers) as this is my preferred approach plus you can't really have abstracted Classes in LabVIEW etc...

post-10325-0-04210800-1328914121_thumb.p

In the end this is what the file looks like, and it is minimal effort to create using the reuse modules:

post-10325-0-12708500-1328914118.png

Additionally I can support versioning if required for that application in the future.

  • Like 1
Link to comment

I wrote a big windy response to this and then clicked on one of your pictures...bam! response deleted...I hope HTML 5 fixes that kind of thing.

Anyway, here's what I said: Perfect! Thanks for the graphic response, it's just what I had in mind only couldn't get quite right. I love that you pointed me to the OpenG Get Data Name vi. That solves the problem of needing a front panel control and property nodes to get the name. I'll have to save as variant but that may be better anyway.

One question:

The data that is required to be persisted is stored in a non-typedef cluster.

Why not use a typedef?

That brings the total number of LapDog.Messaging users up to... 3. :D

:book: I do believe that to be untrue based on all the interest of other posts on the subject!

Link to comment

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Unfortunately, your content contains terms that we do not allow. Please edit your content to remove the highlighted words below.
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

By using this site, you agree to our Terms of Use.