General Daqfactory Questions


EOlsen

Recommended Posts

Am just getting started with DAQFactory and have a number of general questions about the software.

 

Is it only possible to create a function as a sequence that is called from a sequence or can sequence callable functions be created in some other way? Would rather have my list of sequences just be running sequences and not filled with a bunch of function sequences.

 

Can V channels have persistent histories? If so, how is that setup?

 

How do you know which sequences are running in the main application thread (see description of FileOpenDialog)?

 

Where can I get a complete list of functions? The user's guide does not list them all like evaluate(). At least I didn'f find it. Also the toJson() fromJson() functions. Is there an alphabetical list and one grouped by related functions?

 

I couldn't find a good description of how and when I/O is read and written to? Is this usually done just one channel at a time - reading based on time interval and writing done when a value changes? With all I/O it is much more efficient to read and write blocks of I/O (channels) particularly serial I/O.

 

I saw something about grouping channels together based on the I/O device they used. Does this force the drivers to read and write in blocks?

 

Is there a built-in way to synchronize processing of script logic with I/O reads and writes - like waituntil(newread(mychannel))?

 

Thanks for any help with the above questions.

 

 

Link to comment
Share on other sites

Let me answer each in turn:

 

Is it only possible to create a function as a sequence that is called from a sequence or can sequence callable functions be created in some other way? Would rather have my list of sequences just be running sequences and not filled with a bunch of function sequences.

 

The best way to do this is to create a class for your global functions.  The class declaration would be in one sequence.  Something like:

class globalFuncs
   function x()
      ...
   endfunction
   function y()
   endfunction
endclass

global g = new(globalFuncs)

Then you can call those functions like:

 

g.x()

 

Of course you can use different names.  I know someone has had success simply formatting a sequence like this:

 

function junk()

endfunction

 

function x()

   ...

endfunction

 

etc.

 

And it worked.  Junk() is just a placeholder to handle the fact that sequences typically only have one function.  And then the other functions declared actually appear as global functions.  Its not really a supported feature and I was actually amazed this worked when I saw the person's application.  Its certainly something we'd be heading towards officially supporting, but I would suggest sticking with the class / object method.

 

Can V channels have persistent histories? If so, how is that setup?

No, not unless you handle the persist itself.  Truthfully, it still amazes me that people use V channels.  V channels were created back when DAQFactory didn't support variables.  I typically recommend people to use variables for short term things without histories (they are faster than V channels), and use real channels for things that you want to persist, or have history or otherwise leverage the features of Channels.  You can create a channel, device type "Test", and set the Timing to 0, then give the channel values by doing:

 

mychannel.addValue(newValue)

 

If you really want to use = to assign values, make it a Test D to A channel.  Its a little slower, but then you can do:

 

mychannel = newValue

 

How do you know which sequences are running in the main application thread (see description of FileOpenDialog)?

 

They'll only be running in the main app thread if you call them as a function from an event tied to a screen component, or from a manual command in the command / alert window.  There are other ways, for example if you called a sequence from the OnAck event of an alarm and you triggered the Ack from a button, then the event gets called in the primary thread.  But if you run a sequence using beginseq() or through the menus then it runs in its own thread.

 

Where can I get a complete list of functions? The user's guide does not list them all like evaluate(). At least I didn'f find it. Also the toJson() fromJson() functions. Is there an alphabetical list and one grouped by related functions?

 

Evaluate() I believe is in the chapter on Expressions.  That's where most of the functions are listed, at least the ones that aren't specific to an internal object (like a component, or alarm, or channel).  Those are listed in the appropriate chapter on that internal object.  ToJson/FromJson are specific to DAQFactory OOP which remains an officially undocumented feature.  As such the only info is available on these forums.  OOP remains undocumented due to two deficiencies:

1) error messages show the line number from the beginning of the member function, not the beginning of the sequence where the class is declared

2) you can't call member functions or subset twice in the same symbol.  So, for example, you can't do:

 

x[3].y[4]

 

or

 

x[3].foo(4)

 

Instead you have to do:

 

private temp = x[3]

temp.foo(4)

 

These are easy to workaround, but annoying and thus make us feel that OOP shouldn't be officially documented in the help.  

 

I couldn't find a good description of how and when I/O is read and written to? Is this usually done just one channel at a time - reading based on time interval and writing done when a value changes? With all I/O it is much more efficient to read and write blocks of I/O (channels) particularly serial I/O.

 

Writing is always async, so its done when you tell it to.  DAQFactory won't do any block I/O on writes unless you explicitly ask for it using a function (for example with device.myDevice.forceCoil() you can pass an array of values to set and it will do it in one block if possible)  Reading depends.  If its a channel with Timing, its done at the timing interval.  If you are using Modbus and your channels contain sequential registers of the same type and the same Timing, then DAQFactory will automatically block the reads.

 

I saw something about grouping channels together based on the I/O device they used. Does this force the drivers to read and write in blocks?

 

Grouping channels is for organization only.  If, however, you want to trigger reads from a sequence rather than using Timing, you can use channel.ReadGroup() to trigger the read of an entire group of inputs, and DAQFactory is able to optimize the reads into blocks.  If you instead do

 

read(chan1)

read(chan2)

 

then these are read sequentially whether they could have been blocked or not.

 

Is there a built-in way to synchronize processing of script logic with I/O reads and writes - like waituntil(newread(mychannel))?

 

I'm not quite sure at what level of synchronizing you are talking about.  There is no waituntil for reading a channel.  If you do read(myChannel) then that function will block until the read is complete or a timeout occurs (if serial/ethernet).  If you are using Timing, its usually best to use the Event of the channel.  This is script that executes every time a new value comes in on that channel.

Link to comment
Share on other sites

I see a lot of potential with DF. All of bases seem to be covered for what I need to do - custom I/O interface capabilities, control logic processing, alarms, data logging, etc. But I'm still struggling to understand how all the pieces of DF work together and based on that how to best go about implementing the systems I need to implement. So I'll describe what I have to work with and hope you can point me in the most productive direction.

 

Their are a number systems in different locations, all similar, all using mostly the same type of I/O, Opto22 Optomux modules on racks with B1 & B2 brain boards. Racks vary in size from 4 to 16 points per rack. All communications is via RS422, buad rates vary from locatioin to location. Generally one com port is used, but sometimes two.

 

This is mostly refrigeration chamber control, but some other controls in addition which vary by location. Number of temperature sensors and type vary some as well. Because some locations have a large number of I/O racks and communications is serial port based, efficient I/O communications is important. Current, old software, reads all the inputs 1 rack at a time, processes the control logic, then writes and changes out to racks for racks where output values have changed 1 rack at a time. Reasonably efficient I/O handling.

 

Temperature control is done based on average temperature of several sensors. Operator can choose which sensors to include in the average (needs to be persistent value). Each sensor is calibrated and calibration factor needs to be persistent.

 

Chambers need to be defrosted. Defrosting involves turning off and on several I/O points at the same time. More efficient to block write these changes than to do it one at a time. Time of last defrost needs to be persistent. Temperature set point needs to be persistent. Data needs to be logged for at least 1 year. Digital points need to be logged when they change values. Most analog needs to be logged every 5 minutes. Need to log both individual probes and temperature averages.

 

-------------------------------------------------------------------

 

Based on the user guide and forums looks like the best route for I/O handling is to develop a new device type and protocol so it can be moved easily from one system to the next. Still a bit confused by how com ports, protocols, I/O types, and all fit together with devices. Looks like a separate device is required for each com port, but all I/O interface devices (in this case racks, modules, and brain boards) on one port are part of one device. Is this correct?

 

Then it looks like I/O types have to defined for each protocol. This seems a little odd as many I/O devices share the same basic types of digital and analog inputs and outputs. But accepting I/O types must be created for each device type I don't understand the 'Number' field under New I/O Type. What does it do? After you create an I/O type how do you edit it or delete it?

 

With user defined devices how would you use the channel.ReadGroup() function?

 

-------------------------------------------------------------------

 

The projects really require a way to map I/O points for reading and writing effectively given the large number of points and slow serial com link, and a way to map the I/O points and variables for efficient logic processing. Simple way to do this is create the I/O points and variables as needed and then place them in arrays for referencing by I/O reads and writes and organizing them into other arrays for logic processing access. But this isn't an option with DF

 

So as I see it there are a few approaches to handling the I/O and variable requirements.

 

One approach is to define all I/O points and variables such as set points as channels. Variables such as set points would be assigned to a Null or Test device type. To make their values persistent, history and persist length would be set to 1. Using channels makes data logging easier. The down side to this is a very big list of channels (varies up to several thousand). Awkward access in logic - have to use a lot of evaluate() calls or very many duplicate sequences and in general probably a lot of processing overhead slowing everything down.

 

Another approach is to define only I/O points as channels and perhaps any variables such as temperature averages that needed to be logged. When would be the right time and place to calculate average of several probes? Expression in the channel definition of average with time delays? Would need to use variables indicating which probes to include in the calculations.

 

Another approach would be to forget about channels and try to do everything using variables. This approach would make data logging more difficult (sequence based) but impossible. Would have to use toJson() and fromJson() or something similar to store persistent values. How would using user defined devices work for communicating with the I/O? Would have to somehow coordinate reading, writing, alarms, and logging in sequences.

 

As you can see I having trouble finding the best route to get started. If you could point me down the most productive path, it would be appreciated.

 

Thanks.

Link to comment
Share on other sites

For an application of your scale requiring flexibility in size, I would avoid channels altogether and do the whole thing using OOP.  What the main object should be I'm not sure.  It depends on what gets repeated, maybe per fridge?  Then you could instantiate these objects as needed to scale.  It would be best if each object also happened to correspond to a device on a separate connection (which means you could communicate with them concurrently), but it sounds like this is not the case for you.  In your case, I would create a sequence for each connection point (which is the limiting factor on concurrency), or perhaps an object, which would loop around and do the I/O.  Each iteration, it would ask all its associated objects what Inputs need to be read.  After asking for the inputs, it would sort them for optimization and do the reads, then put the values back into the appropriate objects.  After doing the inputs, it would ask each object what outputs need to be set and repeat the process.  For outputs, since they aren't set each iteration, I'd have a queue in each object for the outputs that want to be set.  So when a screen control says set something to something, it wouldn't directly send the output command, but instead add it to the queue to perform the next iteration.  This allows you to optimize the communication. For example, instead of reading all the inputs, you might read 1/4 of them, then dump the output queue, then the next 1/4, etc to ensure the outputs are handled quickly.  By doing is all in one sequence, you avoid any collisions on the comm port.  As I said, you could create multiple comm handling objects for each port, since DAQFactory can communicate on different ports concurrently.

 

The comm handling objects is also where I'd put the protocol handling.  I wouldn't put it in a user comm device.  They are harder to debug, and don't really benefit you because when you want to create a new app for another customer, you are going to cut and paste your class declarations anyway.  Then you'll simply change the instantiation and initialization of those classes based on the new customer's needs.  Its all basic OOP.

 

That's my suggested route for a larger, repetitive app like you have.  I did one that was designed to handle up to 100 "objects" and each had something like 100 tags each.  The objects themselves handled the comm, alarming, logging and all.  The user interface was easily able to switch among objects by using a variable to hold a reference to the "object" it was working with.  In this case, each "object" was on its own IP address, so DAQFactory was able to do very rapid communication with the end devices.

 

As for the logging, I would consider logging to a MySQL database instead of trying to do it all with flat files.  Flat files are nice for small systems, and the files are a bit more portable, but you can't query them easily for doing things like reporting or historic trending.  Also, if you used a MySQL database, you could create some tables to handle the persistance instead of trying to use to/fromJson (or use those functions anyway, but store it in a table in a database).  MySQL has a dump facility so its easy to replicate systems.  Hell, you could pretty easily design a database driven system where you could use the exact same .ctl document for each customer, and only change the database tables.  Even the customer's logo could be pulled from the database.  

Designing these systems are definitely more advanced.  DAQFactory makes it a lot easier to do than most other languages and tools, but its still an advanced app that requires good planning and proper architecture.  We can certainly offer more specific pointers as they came up.  Just keep posting your inquiries to the forum.

Link to comment
Share on other sites

Thanks for the suggestions. The applications while similar are not identical - will not be able to use the same .ctl document for each customer. The number and location of sensors and equipment differs, the number of refrigeration zones per chamber differs, sometimes the type of refrigeration and related equipment differs. So while similar each one differs enough to require a unique .ctl document. Periodically equipment changes are made to a system and previously unused I/O points are put into service. Sometimes additional I/O racks are required. Occasionally a different type of I/O equipment needs to be incorporated. Have one coming up that needs to incorporate a USB based I/O device together with Opto racks.

 

A big part of my reason for looking to DF as platform for these projects was because DF already had forms setup for adding channels and I/O drivers, managed communications ports, handled alarms, conversions, data logging, PID loops, charts, exporting data, connection to server, etc. If I bypass most of the built-in aspects of DF, seems like it defeats the purpose of using DF as a foundation for the projects. Could do most of it just as easily in C++. Was also hoping to avoid using a database to make archiving large amounts of data to other drives and locations easier. Also I'm thinking in order to use a database to handle I/O and persistent variables would require building tools to view and modify database records, and this isn't something built into DF or have I missed something?

 

I have a smaller project of this type I need to implement right away, and need to make a final decision soon if DF is the way to go.

 

My most recent thought was I could probably get by with processing outputs one at a time if I could read inputs as needed in blocks. As I understand it DF would manage the com ports so there wouldn't be problems with several writes being called for at the same time. So my thought was to use channels for all I/O and maybe a few variables like temperature averages setting all of the timing values to 0 and do the reads controlled by a script. To make this work I would write protocols as needed, define one device for each com port, use the device # for the RS422 drop address, channel numbers to identify positions on the rack. Does this seem like a reasonable approach? Is there a way to create a list of input channels for device number so block reads could be done in a script?

 

I would like to assign each channel to a group that is associated with a chamber to make finding all the related I/O easier. Sometimes they are scattered about on different racks (device numbers). If required, I could use the group id to specify all I/O on a particular rack. Too bad there can't be more than one group assignment.

 

Is this approach reasonable, or would the system have too much overhead with a large number of channels used for I/O?

 

Thanks for your help.

Link to comment
Share on other sites

Couple more questions.

 

If I was to use a database to hold I/O variables and interfacing device objects, would I have to use toJson to save them or could I pass the object in binary format to the database?

 

On display pages when the form for determining the action of a button or switch or similar component asks for a channel would I be able to use an object property?

Link to comment
Share on other sites

There are lots and lots of possible architectures available with DAQFactory and I presented one. Yes it involves giving up some of the built in features of DAQFactory, but you are still able to leverage many others that make creating a similar application in C++ or other lower level programming language much, much harder.  Things like multithreading, comm port handling, screen generation, and much more.  But let's move forward and prove that to you:

 

The big issue for you sounds like the I/O portion and block reading.  There is no built in facility for block reading in script, only in C level protocol DLL's and that I suppose is an option for you if you have a C++ compiler and know C++ (which it sounds like you do).  You'd still be able to leverage the comm port handling, channels, etc of DAQFactory from C, but you'd have the flexibility of doing block reading.  Email us directly if you want to go this route.

 

If you didn't, I would probably change the architecture of my protocol driver a bit.  In the I/O type handler, instead of actually doing a read, fill a queue with details of the channel you want to read (use an object for this).  Then at the end call another function in the protocol that looks at that queue, blocks everything and does the actual I/O.  Then in your sequence you'd do something like:

 

channel.readGroup("groupA")

channel.readGroup("groupB")

//...

device.mydevice.endGroupRead()

 
You could also use listall() to list all your channels and call read() on them.  A little slower, but then you wouldn't have to remember which groups to name.  Note that using this method you can group your channels based on what is convenient for you, not based on what device they are connected to.
 
As to your two other questions:
1) you cannot persist a DAQFactory object in binary format.  It can only be persisted using toJson(), or by some other scripting method you might choose.  Note that persisting objects isn't limited to databases either.  There is no reason you can't persist objects to a flat file.  Also note that you can persist whole object trees using a single toJson().  So, if you had a global object, say CDoc, and inside that you had a few member variables that were arrays of other objects, you could call toJson() on the CDoc object and it will persist all the member objects as well as a single json structure.  You can then restore from the json string using the global fromJson() instead of the member function fromJson() and DAQFactory will reinstantiate all the objects in the tree.
 
2) yes, although many components still say "Set Channel", you can use any variable you want.  Just remember the limitation of Objects, that you can't do: x[3].y[1].z, though you can do x[3].y
Link to comment
Share on other sites

This is the code I copied from the forum that gives an error. Are you saying the call toJson() will not work as shown?

 

class CSettings
// change to list all the settings variables
   local somesetting = 3
   local string somestringsetting = "defaultValue"
   
/// do not change anything below this point
   local string transient settingsPath = "c:\DAQFactory\settings.json"
   
   
function loadSettings()
      private handle = file.Open(settingsPath, 1, , , 1)
      private string datain = file.Read(handle)
      file.Close(handle)
      fromJson(datain)
   endfunction
   
   
function saveSettings()
      private handle = file.Open(settingsPath, , 1, , 1)      
      file
.Write(handle, toJson())
      file.Close(handle)
   endfunction
   
endclass



global gSettings = new(CSettings)

Link to comment
Share on other sites

I'm not sure what error you get?  I cut and pasted the code above, changing only the path (to d:) and it worked correctly.  I ran the sequence, then in the command alert I did (what I typed in bold, non-bold is the output):

 

? gsettings
Object(s)
? gsettings.somesetting
3
gsettings.saveSettings()
gsettings.somesetting = 2
? gsettings.somesetting
2
gsettings.loadSettings()
? gsettings.somesetting
3
 
I also looked and saw this in the file:
{"_ClassName":"csettings","somesetting":3.0,"somestringsetting":"defaultValue"}
 
The reason toJson() works in this case is because it is inside a member function, so its calling the fellow member function toJson().  If saveSettings() were a global function it would not work because toJson() does not exist in the global scope.  LoadSettings() would work as a global function because fromJson() can be both a member function and global function, but in the case of the global version, it returns an object reference of the newly created object, so does not act the same way as the code above.  In the code above, since its calling the member function fromJson() from within another member function, it applies the parsed json to the current object.
Link to comment
Share on other sites

Archived

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