04-14-2010 11:25 AM
Summary of Queries:
- Would you imagine my design to be suitable,
should the need arise to scale up even further.
- How could I
improve on potential race conditions in data logging.
I
work in Catalyst Research, where I support about 100 scientists which
includes building automated test rigs. I have a joint honours degree
and 5 years working experience in both electronics and software
engineering, of which 3 years is LabVIEW development.
I can
bounce around electronics ideas on site, but as I am the only
software person, I was hoping some of you could take 10 minutes and
review my general software architecture.
The rigs will usually
have several pieces of hardware attached, Usually in the form of:
-
NI USB DAQ Device
- Eurotherm Controllers (Mini8 controller, 2000
series, 3000 series) on RS232, 485 or TCP.
-
The hardware protocol is irrelevant, as I communicate with them via
OCP
- Other RS232/RS485 instruments, communicating via NI VISA.
-
Other USB instruments, communicating via directx, or device drivers,
et al.
A typically run will last upwards of 12 hours, and so
they need to be safe to leave unattended. Mostly this is in the form
of hardware interlocks, as I don't trust computers not to lock up.
Furthermore, this means actual error handling in the software rather
than popping an error message up on screen 😛
I like my
software designs to be modular and abstracted (although never to
excess). Each hardware device is managed in its own class data
structure; Values are read, buffered, and saved to a shift register.
Values are continually compared against safe operating limits. The
only time an external, or higher level VI is called from the
controller is a config data structure during initialisation. Actions
can be sent to the device controller through a queue. If a value goes
outside of safe operating conditions, the particular condition is
sent to an alarm handler via a queue.
Thankfully, an
experiment can often be organised into a sequence of user
configurable steps. This takes the form of an array of clusters. The
cluster instance stores setpoints for temperatures, dwell times,
device states and so on. When a sequence step is advanced, the
setpoints are fed by queues to each device controller. This all works
fine and dandy.
A level above that, test runs can be queued as
well; a user will put a sample into the rig, log on to the system,
create a test run sequence, and leave it to get on with it. I should
also add that sequences and run conditions are saved to disk, so
should the PC crash, things can get up and running again
quickly.
With regards to the user interface; that is all it
is. None of the VIs in the UI deal with the running of the rig.
Changes made to the UI are written to the config file data structure,
directly to the device controllers, alarm handler, or some
miscellaneous process controller. The UI displayed readings have a
set update rate, which queries data from each device controller
At
the requests of the scientists, all data that can be logged, is
logged, at a user defined frequency. Once the log file reaches a
number of lines, it is closed and an incremented filename is
continued. This is so they can be imported into excel, or to ease
strain on a computer post processing the data. Each log iteration,
data is queried from wherever readings are taken.
I seem to
have a stable, reliable and safe way of building the rigs, but I
remain unsatisfied with my tried methods of presenting data to the
user and logging data. Given that data is being read from a number of
sources, I want a good way of keeping the data reasonably
synchronised. (Reasonable being under 50ms) Temperatures, pH levels,
pressures, Mass Flow Controllers, and motorised actuators don't
fluctuate enough that an accuracy of more than a few hundred Hertz
will matter (although oversampling and smoothing occur inside the data controller / on the device).
My current method is creating a temporary notifier and adding it to the front of the device queue. On the next device iteration, it is popped off the queue and the last buffered value is returned via the notifier to the calling function. I favour this over an action engine; because as there was no easy way to indicate when to take a reading, every time the device controller took a reading, it was necessary to update the action engine. I am still unhappy with my current method of querying data, as I cannot ensure that new values have been updated in the device controllers when the data logging section queries the data. This may even cause the same value to be logged. (Race condition) This problem would still persist with an action engine.
Solved! Go to Solution.
04-14-2010 11:29 AM
04-15-2010 04:58 AM
Just some ideas:
* timestamp each reading
* Instead of logging the timestamp to the file, you can raise a flag 'Old data' or the like. I think the OPC protocol has some flag that indicates how new/recent any value is. Maybe you want to copy it from this standard.
* In such scenarios, I'd just have the readings broadcasting the data via a notifier all the time instead of the temproary callback notifier. The logger (or whatever) just goes through the list of notifiers and takes the most recent value.
Felix
04-15-2010 08:16 AM
Cheers Felix, I reckon my best bet would be to broadcast.
I do think it's a bit of a shame that there is always lots of help for those learning a programming language, but it dwindles a bit once you need guidelines on how to design a large system 😞 I guess it is largely to do with needing to know the problem intimately before designing the system, so casual aid is a lot more difficult
04-19-2010 04:55 AM
Hi,
Following Felix's excellent comments, I thought about pointing you towards this page, if you haven't already seen it! It doesn't discuss anything specifically but I noticed your comment about information on large system development and its very true and perhaps the link and the links inside the link will help.
04-19-2010 06:17 AM
I don't know your (Labview) Knowledge, neither what you already have.
An advice I can give you, keeping in mind that:
-it's an experimental setup
-auto reconnect can be needed
-there are multiple I/O connection
-multi user, but one programmer
-...
Instead of initialising all connections only at the beginning of a program, it's better to make it possible everywhere, using a FSM (Finite state machine) for every connection.
When not, you could have to restart your program for every single connection breakdown.
How I do it: Enum-Typedef-Case, thats very known.
My cases contain ReadIniFile - ConnectionInit - Wait - TryToConnect - Connected - ConnectionError - Closedown
Depending on the state of the connection, the user interface, the measurement made, errors etc you can now easily/automaticly jump from one to another, reinitialising connections etc.
Success !
04-19-2010 07:56 AM
yenknip wrote:Cheers Felix, I reckon my best bet would be to broadcast.
I do think it's a bit of a shame that there is always lots of help for those learning a programming language, but it dwindles a bit once you need guidelines on how to design a large system 😞 I guess it is largely to do with needing to know the problem intimately before designing the system, so casual aid is a lot more difficult
1) To get good feedback on architectures, an Architect is handy.
Did you take a look at the desings docs I posted in this thread (see reply #22) ?
2) Answering trivia questions about LV is something I can do ont the way too of from my breaks. Architectures takes some thought since no two apps are the same and there are an infiniate number of ways to implment them, not all of which are good.
3) Architectures are what I get paid to develop/implement so I am constantly watching myself to ensure I don't "give away the shop".
4) If you have a specific Q about the architecture please summarize the Q and I'll try to help out.
Ben
04-22-2010 06:06 AM
Hi all, sorry for the delayed reply, I had a few days off 🙂
Mark, that's a great link to some sound software design theory
ST5, My current hardware controllers are based on a state machine as you described. The class holds any data regarding connection addresses and references. I would describe my structure as a queued-state-machine, because other parts of the software can queue actions to the controller to perform an action such as changing a COM port or enabling/disabling reading of an unused channel. Additional states I use include signal conditioning and limit checking.
Ben, I think I really need to find a spare week somewhere and do the LV advanced training course, but in the mean time, I think I can summarise:
In the system I currently implement, data needs to be constantly streaming into the system for safety checking. This data is also used in a number of other locations, namely; safe limit checking, logging, PID controller, displaying to user. Following Felix's suggestion, I am broadcasting the data from a notifier, which is updated at the speed it takes to read, buffer and prepare the data which is only a few milisec. From the looks of it, this is a similar approach to the one outlined in your powerpoint presentation; A liberal use of state machines and the synchronisation palette.
This is holding up for the time being but if I need to run an even more complex rig with a number of RS232/485/USB devices, I am unsure how stable and quick the system would be. The way I would fix this would be to put hardware read/write directly in the most greedy data user, the PID controller(s) and notify the other data sinks, but I would lose the abstraction of the hardware checking. Alternately, the whole hardware state machine could go in the PID loop, but then I might as well be sharing data around as above.
04-22-2010 07:58 AM - edited 04-22-2010 07:59 AM
Here is some stuff from another app
where I was concerned about safety since I had 4 PIDs that if out of control would be bad.
This is a rough interaction diagram to show how the various componenets interact. THe import point in this diagram is that all Output Control (an AE) functions are invoked via the "Safety Limits" ( another AE).
The dotted lines are for error and exception handling and in normal conditions can be ignored. THe following is a cut and paste of my notes to myself that were transfered to the code.
have fun,
Ben
04-22-2010 03:30 PM
Ed,
I published the way I architect my app's in the Event nugget
One thing I consider important is reuse. So I like to have things loosly coupled. This means use of strings instead of type def'ed enums. If a module isn't present (you can load modules using some kind of plug-in architecture or LVOOP, but I havn't done this yet on large scale), it doesn't break the code (very usful if the device is actually missing and you need to start setting up the ATE).
I've a PID on the event architecture (well, at the moment there is a lot of duct tape/glue/gloabl variables involved, but I'm currently rewriting it as a state machine), it runs fine. Some benchmarkings I did showed that I can loose events when I directly call two Generate Event VIs (a 1 ms wait between them did help) AND I did use the filter VI's (but it merly was an academic challenge).
Felix