Some Basic Pointers


Recommended Posts

A customer just asked me to review their document and offer a few pointers.  Three of them are very common "mistakes" made by many DAQFactory customers, and as such I thought I'd list them here in hopes that everyone will avoid them.


1) don't duplicate pages.  Let's say you have a system with 3 boilers.  The boilers are identical, each with their own set of channels.  You need a page that shows the detailed status of each boiler.  The easy way is to create a single page for one boiler, then duplicate that page two more times and edit each one, changing the channel names.  This is a bad idea.  Its very hard to maintain and doesn't scale when you have 20 boilers.  Now whenever you want to make a slight tweak to the page, you have to repeat that tweak 3 times.  


Instead you should create one page and use either a string variable or an object to specify which boiler's data you want displayed.  I'll skip the object discussion for now and simply offer the string solution.  To use this, you need to name your channels in such a way that they use a prefix that identifies the boiler (even if its just B1_, B2_, B3_) and then use common channel names for all three.  So, the boiler temperature might be B1_Temp, B2_Temp and B3_Temp.  Then you can create a global string variable:

global string currentBoiler = "B1"

Next, in your common boiler page, instead of specifying the channel directly in your expression, say "B1_Temp[0]", use the evaluate() function:  

evaluate(currentBoiler + "_Temp[0]")

Repeat for all your others.  Same for graphs, though of course without the [0].


Now, when you want to display data for a particular boiler, change to your common boiler page, then set the currentBoiler variable to "B1", "B2" or "B3"


2) same goes for sequences.  You want to do a little loop to monitor your three boilers.  The easy way is to write a sequence then duplicate it and change the channels out.  Again, hard to maintain and doesn't scale.  Here we won't use a global variable, but rather a single sequence function with your control code.  Let's say its a loop:

function controlBoiler(string boiler)
       if (evaluate(boiler + "_Temp[0]") > 100)
          execute(boiler + "_Heater = 0")
       /// do other stuff

This will turn off the heater if the temp goes above 100.  The trick, however, is that you can't start sequences with parameters (yet...), so you still have to create three more sequences.  But these are dead simply.  You'd call them say, B1ControlLoop, B2ControlLoop, etc.  and each would have one line of script, for example for B1:


That's it.  Just run the B1ControlLoop, B2ControlLoop etc sequences and they'll all three run the controlBoiler loop at the same time.  Now you can edit controlBoiler once and it will carry to all three.


3) don't name your channels based on the I/O location.  I see this all the time, channels with names like "AIN_ET7019_01_00".  (ET7019 in this example is a type of DAQ device).  This name is nice, sure, because you can look at it and tell exactly where the wire goes.  BUT, its otherwise useless, and it makes the rest of your application much harder to understand.  Instead you should always name your channels based on what they are, what they mean and/or what they do, not where they are wired.  Which is more clear:

if (AIN_ET7019_01_00[0] > 100)
   DO_ET7013_01_01 = 0


if (Boiler1_Temp[0] > 100)
   Boiler1_power = 0

I know what you are saying: "I've got my wiring memorized, so AIN_ET7019_01_00 means Boiler1_Temp to me!"  Sure, it does now.  But what about in 6 months after you've moved onto another project and then come back?  What about when someone else has to try and read your code?  Either way, you'll have to look at some sort of cross reference sheet every time.  And really, how often do you need to know where something is wired into?  If you want to track that with the channel, simply put it in the Notes section of the channel.  


Finally on this, what happens if your ET7019 dies 5 years from now and is no longer made, so you had to replace it with a LabJackUE1000 (some currently non-existent DAQ device)?  Are you going to rename the channel, then go through and rename all the references to AIN_ET7019_01_00 to LabJackUE1000_03_05?  You'll undoubtedly miss some place and your program will die.


And don't say "by then we'll have rewritten the program".  DAQFactory has been around for 12 years and some of our first customers are still using it and still using the same application they created 12 years ago!  

Link to comment
Share on other sites

  • 3 weeks later...



I'd like to do something like 1), except I'd like all the "boilers" would be on the same page. That is, a user component I can re-use which (perhaps) has some property I can set once which does the job of the "currentBoiler" string you suggested in 1). 


This is how it would work, ideally:

1. make all the channels for a new boiler, prefixed with say "B4_"

2. place the user component (which contains a bunch of sub-components with displays for the boiler)

3. set a property (or similar) on the user component to tell it that its prefix is "B4_"

4. all the sub-components can use "evaluate(prefix + 'RestOfChannelName[0]')"



Can you advise a way to achieve something like this?


I tried to do it by giving all of the components for each boiler an identical component name, which would allow me to give them all a property, but that failed when I realised I need a few of the components to have unique names. 


Is there some way to set a property of a group which can be accessed by all the group members?

Or is there a way to loop through all the members of a group from the group's code?




Link to comment
Share on other sites

The way I handle this sort of thing is this:


1) give each component in the group a unique name.  It only has to be unique to the group, because once you group it, you won't be able to access it by name from the global scope

2) group the components

3) in the group's OnLoad event, create a new property for holding the variable:




4) in the group's paint event, use that property to update the expressions or displays for each of the group's components.  Note that from the group's point of view, the components in the group are direct member variables.  So where from global scope you might have to do:




from a group's onPaint or any other event, you can just do:




I've attached a user component I created once for a specific application.  Its a menu button and demonstrates most of the features of group's and events and might help you.  The button is designed to be used in a group.  Each button can be assigned an action that gets executed when clicked.  But also, when clicked, that button gets a red border (a red panel behind the button itself) and all the others get their red background cleared.  It was used to create a menu where the current page is indicated by the red background.


The group has three custom properties, Label (what's displayed on the button), Action (what to do when clicked, it is execute()'d), and Group.  It also uses a Listener so that a click will send a message to all components in the Group of menu buttons (not the group that makes up a single button) to clear their red background.  All these are created in OnLoad.  


Take a look at it and see if it helps.  Just copy the file into your DAQFactory folder and restart DAQFactory.  The component will appear in your right click menu for components at the bottom under "Buttons".


Note that you can create your own user components out of your group when you are done, allowing you to reuse and share the new "component" with others.  Just select the group, right click and select "Create User Component"



Link to comment
Share on other sites


This topic is now archived and is closed to further replies.