Jump to content

Changing control typedef at runtime


Recommended Posts

Greetings,

First post to this forum. I have a medium-sized application that is going through it's first major revision. The issue I'm struggling with is something I've encountered before so I thought I'd get some input from others.

The application reads in data from an embedded instrument on a serial port. The data stream consists of packets, each packet containing a header and some data. I have a front panel indicator that's a strict typedef'ed cluster representing the packet header (the cluster contains a bunch of string and numeric indicators). So far so good.

The issue now is that the structure of the packet header is changing in this next revision. My Labview application needs to be able to handle both the old-style data stream and the new-style data stream. I'd like the application to be able to change the typedef of my indicator to match the correct version of the data stream it's receiving but I don't think that's possible at run-time, is it?

A brute force approach is obviously to have two indicators and just show the correct one and hide the other one. I'd like to avoid this, though, because I'm guessing there will be more revisions in the coming months/years and I really don't want to have an ever-increasing number of front-panel objects to handle. It's also a little more complicated since this header cluster gets referenced in the code a lot. Having multiple FP objects to represent the header means having to jump through extra hoops when determining which object to reference. Not impossible but just messy. I'd like to just have one FP cluster whose contents can be changed at run time.

It sounds like this might be a good application of the LVOOP stuff but I've never gone down that road and don't have the time to explore it at this point. If anyone has any suggestions besides the brute force approach I'd love to hear them.

Thanks,

dj

Link to comment

As it sounds like you're talking about internal code, and not just user interface representation, the only thing that I can think of is changing the design of your current typedef such that it allows varying content such that old/new/future data could always fit into the data type. Perhaps something along the lines of an array of clusters, where each cluster consists of a property identifing tag (or strict type def enum) and a value in the form of a variant. This way, all dependent code could just inspect the tags to determine how to handle the data, and any kind of data will always fit. You could also create conversion VIs which could bring the various data content back to your original cluster for minimal disruption to your existing code.

As for the user interface, I can't think of anything other than what your suggesting with multiple indicators layered on top of eachother with only one visible. Could also be done with each version of the data on a tab control (tab control could be made transparent to the user so that they are not aware that's it's present - with the tabs hidden of course). If your property/value array were to include some sort of "revision" pair, then you could use this to switch the display as needed.

Link to comment

Another thing you might want to look at would be a X-control. It can in essence modify itself at run time. At least with respect to what is visible and what is not. All of your code using this X-control would have a single control/indicator and wouldn't need to worry about which version of the packet header is being used. Probably the best approach would be to combine the LVOOP object with an X-control.

Link to comment

QUOTE (neBulus @ Apr 17 2009, 09:55 AM)

You know yourself better thatn I can ever but LVOOP is the best way to do this.

Otherwise....

Convert your existing stuff to use sub-panels then cone these off for the new version and make the required changes.

load the correct version into the sub-panel when running.

Ben

First, I'm pretty sure you can't change a type def at run time in an executable- it's a compiled component of the code. That said, if you're running in the dev environment, maybe you can script that? I don't know, I'm not a LV scripter.

I think LVOOP is an interesting idea, but let me make sure I understand how this is going to work. You create a parent class that supports the header and a generic message body (byte array?). When a message comes in, you then read the header with the parent and then cast the object to the required type by passing the parent wire into the child that's specialized for that data type. Then the child unpacks the body into the correct structure (control). Then that child has to display the control somehow - thru opening itself in a subpanel, perhaps? The child classes could be called dynamically from a library of VI's or built into the exe. If you have the children built into the exe, you'll have to recompile and re-distribute whenever a new definition is required. If you deploy the child classes as libraries (folders of VIs on the target machine), then you could use a scheme where the header definition maps to an entry in some list of children (maybe in an ini file), read the list from your main app, then use the entry in that list to load the child dynamically. Now, you have an app that you can extend forever (as long as the header defintion doesn't change) without ever replacing the exe - just add children to the library to support the new data structures.

Mark

Link to comment

Thanks to everyone for the suggestions. They've confirmed my suspicion that there's no way to change the typedef at runtime and given me some ideas to try. What I'm leaning towards now is changing my typedef to be a "cluster of clusters" (each cluster corresponding to a different version of header format), and also containing a version field. The version field will get populated with the version number identifying the format of the data stream and then only the appropriate header cluster within the typedef will get populated with header data. The rest will remain at some default value. Code on the diagram then just needs to check the version field to determine which member of the typedef to read.

I'll have to get creative to create the front panel indicator though since I only want to display the correct cluster. That can probably be done with subpanels or a picture control or something.

I welcome additional suggestions as well.

Thanks again,

dj

Link to comment

QUOTE (jabson @ Apr 17 2009, 10:11 AM)

Thanks to everyone for the suggestions. They've confirmed my suspicion that there's no way to change the typedef at runtime and given me some ideas to try. What I'm leaning towards now is changing my typedef to be a "cluster of clusters" (each cluster corresponding to a different version of header format), and also containing a version field. The version field will get populated with the version number identifying the format of the data stream and then only the appropriate header cluster within the typedef will get populated with header data. The rest will remain at some default value. Code on the diagram then just needs to check the version field to determine which member of the typedef to read.

I think you would be better off with a cluster containing an enum and a variant. The enum says which type it is, and then wherever you use the data, you have a case structure driven by the enum and inside that case structure you convert the variant data into the correct cluster. I have done this with plenty of programs and it works fine, and the separate clusters are more manageable than a big supercluster.

LVOOP gives you a cleaner way to do the exact same thing, but for one you don't want to get into that now, and for two, doing with an enum and a variant first will give you more appreciation for LVOOP when you finally get a chance to wrap your head around it.

Link to comment

Everyone will have their own methods and solutions to this one. It's an old problem with many equally correct solutions.

If you give an example stream and header structure we would probably be able to justify each approach better and you could pick the solution that best fits your preferences.

I find, generally, the best option, if possible, is to find the super set of the protocols and choose a display method that can cope with all of them.

what do I mean by this...Well...

Lets say your devices both send a header consisting of a command byte, a length byte and the payload (nice and simple). In device 1 the length byte is 2bytes long but in device 2 it is 4 bytes long. The length byte of both devices can be represented as a 32 bit number so you would choose that to display the length. Just in case the hardware engineers decide to use a bigger length in the future you could even go for 64 bit integer.

Now. slightly different (but following the same approach).

Lets say we want to represent the command byte with human readable strings.

We could use a menu ring (since arbitrary and non consecutive values can be assigned to its strings). We could then assign each string with the value directly from the command byte - job done for device 1. Now there are two ways of coping with device 2 depending on how much similarity there is.

1. If most of command bytes are exactly the same and the extra ones are just extensions to the original protocol (device 1, MkII) then we have already done most of the work and we only need to add the extra values to the menu ring.

2. If the command byte of device 2 use the same values as device 1 but mean completely different things (usually different devices) then add...say 100 to the command byte and assign the menu ring strings for that device command byte +100.

So. 2 devices, 2 different headers and we display a menu ring and a U64.

If they add more commands...we just add to our menu ring. If they change the number of bytes (up to 8 anyway), we don't need to change anything. You can extend this for the payload too (up to a point ;) )

Link to comment

QUOTE (jdunham @ Apr 17 2009, 11:07 AM)

Thanks for the suggestions. Sounds like a clean way to do this so I'll give it a shot.

QUOTE (ShaunR @ Apr 17 2009, 12:21 PM)

Everyone will have their own methods and solutions to this one. It's an old problem with many equally correct solutions.

If you give an example stream and header structure we would probably be able to justify each approach better and you could pick the solution that best fits your preferences.

I find, generally, the best option, if possible, is to find the super set of the protocols and choose a display method that can cope with all of them.

Thanks for the reply. I hear where you're coming from. In my case, though, It's not just the contents of the fields that have changed but the number of fields. Specifically, the header is 62 bytes in both the original code and the new version. In the original version one of the fields was a 16 byte string used as an instrument ID string. I needed to add some extra timing information in this next revision so I added a U32 field to the header for the new info and shortened the instrument ID string from 16 bytes to 12 bytes (making the header longer had other implications). So now my Labview code needs to have 2 different types of clusters to handle both the old and new data. I was hoping I could just create 2 typedefs and instantiate the correct one but that's not possible.

There's probably an easy way to handle this specific case, but in the future this header could grow and have all sorts of new stuff in it. The suggested approach of just using a variant to hold the contents and a case statement to determine how the variant is interpreted seems like a good one. I've never worked with variants before but I guess this is exactly the type of situation they were created for, situations where the exact data type is not known at compile time.

dj

Link to comment

QUOTE (jabson @ Apr 17 2009, 12:38 PM)

There's probably an easy way to handle this specific case, but in the future this header could grow and have all sorts of new stuff in it. The suggested approach of just using a variant to hold the contents and a case statement to determine how the variant is interpreted seems like a good one. I've never worked with variants before but I guess this is exactly the type of situation they were created for, situations where the exact data type is not known at compile time.

Yes, that's why variants exist.

One other note: When you use the enum to control a case structure, it will usually create a default case. Remove the default unless you absolutely need it. That way when you get a new type of variant to handle, and you add an item to your enum, then each of your relevant VIs will break until you add the required code to handle the new type. If you leave default cases around, it's very easy to forget one of them, and then you will have bugs.

Oh, and make sure the enum is a typedef!

Link to comment

OK. I understand.

I would be very tempted to just make your display a 2D array of strings (first column can be a label if you want). Then regardless of what data it is you can just format it in whatever manner you think is fit and append it. If it doesn't exist it will just appear as a blank (or you can insert N/A or whatever). If you hide the fact that it is an array, it will just look like a multi-line label. If you add more fields later, just make more elements visible. if you redefine fields , just change the labels (if you have any).

The question I haven't asked properly is this. Is your problem just a method to display the info, or is it some intermediate stage that the data in your fields will be processed later?

Link to comment

Will the header contain any strings which will indicate the "version"? i.e. whether it is the "old style" or "new style"? If so you can do this in the following way:

1. BEFORE you update the typedef to the new version, make a cluster constant copy of the old typedef on your block diagram.

2. Disconnect that copy from the typedef.

3. Place the code that converts the incoming header string into the typedef cluster inside a case structure.

4. Parse out the version info. from the incoming header string and use that to control the case structure.

5. The Default case will do exactly what you're doing right now. i.e. convert the string to the latest typedef.

6. The "Old Version" case will convert the string to a cluster using the cluster constant copy you created. Remember that is disconnected from the typedef.

7. In the "Old Version" case you can unbundle the newly created cluster and rebundle just the appropriate items into the typedef.

See the attached example. It uses an old and new version of a Config XML file but the idea is the same. Save Config.xml and Config_Ver2.xml to some directory. Then Run the ConfigModule_UnitTest and set the path to either of the XML files. It will work just fine. In your case instead of an XML file you have a header string.

Hope that helps!

Link to comment

QUOTE (2and4 @ Apr 18 2009, 03:31 PM)

Will the header contain any strings which will indicate the "version"? i.e. whether it is the "old style" or "new style"? If so you can do this in the following way:

1. BEFORE you update the typedef to the new version, make a cluster constant copy of the old typedef on your block diagram.

2. Disconnect that copy from the typedef.

3. Place the code that converts the incoming header string into the typedef cluster inside a case structure.

4. Parse out the version info. from the incoming header string and use that to control the case structure.

5. The Default case will do exactly what you're doing right now. i.e. convert the string to the latest typedef.

6. The "Old Version" case will convert the string to a cluster using the cluster constant copy you created. Remember that is disconnected from the typedef.

7. In the "Old Version" case you can unbundle the newly created cluster and rebundle just the appropriate items into the typedef.

See the attached example. It uses an old and new version of a Config XML file but the idea is the same. Save Config.xml and Config_Ver2.xml to some directory. Then Run the ConfigModule_UnitTest and set the path to either of the XML files. It will work just fine. In your case instead of an XML file you have a header string.

Hope that helps!

Thanks for the example. Your approach is very similar to what I ended up doing but probably would have saved me some time converting everything to the new format.

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
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.