Jump to content
Sign in to follow this  
abrink

Simple Modular MVC

Recommended Posts

I've built (and re-built) a medium-sized application over the past months, learning more about LabVIEW in the process. The design is simplistic and reaching its limits by now, so I've been re-thinking the design lately. I'm looking for feedback on my design choices before I start implementing... all criticism and tips are welcome!

 

The goal: flexible software for an ever-changing university laboratory environment, to be used and extended by students/staff with limited LabVIEW skills.

 

Decisions:

  1. Avoid LVOOP unless clearly called for
    OOP has it's benefits but it makes code more complicated to grasp for novices.
  2. Avoid asynchronous commands unless clearly called for
    Many hardware operations are synchronous; these should be carried out synchronously (with time-outs) to minimize complexity. No message queues, definitely no Command architecture. Of course, queues are still OK for inter-thread communication, e.g. a data acquisition thread in a DAQ module.
  3. Modular Model-View-Controller (MVC) paradigm
    This should facilitate our need for extendible and adaptable software
  4. Every module needs to be able to run independently
    A central GUI will usually be running, but it should be possible to start/operate any module separately during run-time. This simplifies debugging and allows for flexibility during measurements ('let's quickly check the signal by hooking up a scope') 
  5. Every module must be able to 'require' and 'unrequire' other modules
    If a measurement module is loaded, any required equipment should be initialized unless it's already running. 
  6. A module should gracefully shut down when there are no modules left requiring it
    This is vital to prevent equipement from melting during the night!

 

What I've come up with (core ideas are underlined):

 

MVC implementation

  1. Each module is a lvlib containing Typedefs, Public Functions, Private functions, and Panels.
  2. The Model is a typedef'd cluster containing Settings, Data, Events, and Resources (queues, refs, etc.)
  3. The Model cluster is stored as a Data Value Reference (DVR) in a Functional Global Variable (FGV). The "First Call?" functionality within the FGV is used to initialize the Model (read Settings from a .ini file, create User Events, spawn background threads, etc.) and create the DVR. I could be re-inventing the wheel here, but I'm pretty enthusiastic about this approach. Note that the Model could alternatively be stored in a Class instance holding the DVR. This is useful in applications where you expect multiple copies of the same module (maybe you have six DAQs attached) but it complicates application design as you need to specify which instance you'd like to access before each Controller function call.
  4. The Controller is a set of synchronous functions operating on the Model. Each function obtains the Model DVR from the FGV, and uses an in-place element structure (IPES) with DVR Read/Write terminals to access and modify the data. Note that this provides some protection against race conditions, and reduces memory usage. Inside this IPES could be a second IPES, with Cluster Unbundle/Bundle terminals, to increase readability and again reduce memory usage.
  5. All Controller functions that change the Model generate appropriate User Events when doing so.
  6. Views are panels that execute public Controller functions to interact with the Model. The View registers for (a subset of) the User Events emitted by the Controller to reflect changes in the Model.

Modularity

  1. Each module has public Require and Unrequire functions. These maintain a list of dependent modules, and call a (private) Stop function if the list is empty. Right now, I'm simply using the Call Chain string to identify the calling module, but there should be a more appropriate solution for this (in light of OOP, where the Call Chain is no longer unique).
  2. A View Requires its own Controller, and Unrequires it on Panel Close.
  3. A controller can Require other modules, which it must Unrequire when it no longer needs them.
  4. Hardware interfaces, software components, and measurements are all modules.

Practical Example

  1. User runs the View (panel) of a Measurement Module, which starts the related Controller.
  2. The measurement controller Requires some hardware (say a DAQ board and a camera) and software (say an Error Logger) and loads certain Views (panels) to them into SubPanels (e.g. it display the Error Log).
  3. The Required modules in turn Require other modules (the camera Requires an Image Processor) or the same modules (the camera also Requires the Error Logger) which are initialized if not already running.
  4. Uses carries out his measurement, calling public functions on his required controllers. All changes are immediately reflected in the open Views through event handling. 
  5. User stops his measurement and closes the view.
  6. All required modules (the DAQ, Camera, and Error Logger) are Unrequired.
  7. In turn, these models Unrequire their respective Required modules; the Camera Unrequires the Image Processor, etc.
  8. All modules shut down gracefully when there are no modules left requiring them.

Concerns

  1. Unexpected errors may break the require/unrequire chain, causing modules to run indefinitely. Similarly, if a users forgets to Unrequire a module, it will live on (possibly without a panel) indefinitely. A sort of application-wide emergency stop system (using events or notifiers) could be used to 'fix' this, but I'd like to refrain from using any central structures of possible.
  2. Controller lifetime. Say two modules Require the same third module. The third module will be 'owned' by the first caller, causing it to abort when the first caller leaves memory. This leaves the second caller module with a problem. I think this can be prevented by having each controller maintain a reference to itself, which it only releases upon graceful shutdown. Will this work, and will it be reliable?
  3. Stop code in a View Panel (notably Unrequiring its controller) might not run. Again, I think this can be circumvented by having the panel maintain a reference to itself, which is only released after calling graceful shutdown code in the Panel Close event case.

 

Wow, that sure turned into a short novel. If you're still with me, I'd love to hear your thoughts!

Share this post


Link to post
Share on other sites

Well. You mention that you don't want to use LVOOP because it makes it difficult to grasp for novices but then advocate a Muddled Verbose Confuser (MVC) architecture which even experts on that design pattern can't agree on what should be in which parts when it comes to real code. :blink:

 

As it needs to be simple for novices, I also suggest you throw rotten tomatoes at anyone that mentions "The Actor Framework".

 

Since there maybe many people who build on the code and many will have limited experience, Have you thought about a service oriented architecture? With this approach you only need to define the interfaces to external code written by "the others". They can write their code anyway they like but it won't affect your core code if they stuff it up. You can then create a plugin architecture that integrates their "modules" that communicate with your core application via the interfaces.

 

The module writers don't need to know any complicated design patterns or architecture or even the details of your core code (however you choose to write it). They will only need to know the API and how to call the API functions..

Edited by ShaunR

Share this post


Link to post
Share on other sites

Thanks for the quick reply. The point is that the end users will have to create components as well as measurements. I'll be gone in two years, and if my successors buy a new piece of equipment I want them to be able to integrate it without hassle. My approach should allow them to copy a template lvlib, replace some code (aided by documentation) and get measuring.

The necessity of simple interfacing is met by writing modular code; the question is, what should a module look like? Subsequently, how should modules interact? I've described a solution above, and I'm hoping for feedback from experienced LabVIEW developers.

Share this post


Link to post
Share on other sites

I would not say that LVOOP should be avoided unless clearly called for, but (like any tool) used appropriately. In the case of the modules, a parent object could define the interface to the larger application and carry in to a child object common data/settings. (See plug-in archetecture.)

 

Since you intend for this to change, grow and mutate without you, as generic a way for modules to communicate would be best. Two ways that has been done before is using a cluster of an enum and variant, or a string. You may want to look at SCPI commands to get some ideas for using strings.

Share this post


Link to post
Share on other sites

By now, I've created my implementation with little difficulty. The main challenge was data persistence, since data tends to leave memory when the original 'requiring' caller aborts. Hence, 'moving' a component from one owning module to the next releases all User Events and other allocated resources. The solution was to asynchronously launch an 'owning' vi on a component's first launch, which initializes the component and then just sits in memory. An added benefit of this approach is that you can implement an event handler for an 'emergency stop' event in this owning vi.

 

As I mentioned, I had a hunch that I was reinventing the wheel here. Indeed, it appears that I have basically recreated the Extensible Session Framework (https://decibel.ni.com/content/docs/DOC-12813). Oh well, I learned a lot  :)

Share this post


Link to post
Share on other sites

By now, I've created my implementation with little difficulty...

Could you explain more?  Your initial description gave the impression of being very complex, a lot more complex that the Extensible Session Framework.

Share this post


Link to post
Share on other sites

Could you explain more?  Your initial description gave the impression of being very complex, a lot more complex that the Extensible Session Framework.

 

I think the OP was just having difficulties figuring out how to do #5 and #6. He'll be back again when he runs into #2 between modules.

Share this post


Link to post
Share on other sites

Nope, everything is running smoothly. I will post a barebone example once I find the time.

It boils down to this: when calling Start on a module, a 'holder' vi is launched asynchronously. This initializes the module data and events and creates a DVR to that data, which it stores in a FGV. The holder then starts listening for a module stop event, which it also stores in a FGV. All functions which operate on the data get the DVR and modify it in an IPE structure, providing protection against race condition. Calling Stop on a module gets the stop event FGV and triggers it, causing the holder vi to run exit code and destroy the DVR.

The require/unrequire functionality is trivial, maintaining a list of depending modules and calling Start/Stop when the list is one element new / empty.

Edit: I should add that I ended up creating a Core module that is required by everything else. This allows me to shut down all modules (gracefully) at once by having each module listen for a Stop All event as well as their own Stop Moduld event. Furthermore, it will contain some settings like a debug mode boolean and a parsed config.ini

Hope this is a bit clear!

Edited by abrink

Share this post


Link to post
Share on other sites

I'd like to leave a small note for anyone reading this topic: this turned out to be a highly unmaintainable solution to the challenge. I've bit the bullet and moved to a LVOOP approach, which is converging towards the ESF solution. The required LV skill for using classes is definitely outweighed by the benefits, mainly inheritance and property nodes on DVR wires of the class.

  • Like 1

Share this post


Link to post
Share on other sites

I'd like to leave a small note for anyone reading this topic: this turned out to be a highly unmaintainable solution to the challenge. I've bit the bullet and moved to a LVOOP approach, which is converging towards the ESF solution. The required LV skill for using classes is definitely outweighed by the benefits, mainly inheritance and property nodes on DVR wires of the class.

 

Remember you said this.... 

 

LVOOP comes with its own set of troubles that i'm sure you will stumble upon in time.  I'm not saying its not the better route, its just not the end all be all :)

Edited by odoylerules

Share this post


Link to post
Share on other sites

Funny you should say that; I've now completed my LVOOP implementation and I hate it. I'm moving back to the solution I've described in this topic; it is more straightforward and looks way cleaner on the block diagram. The added value of inheritance turned out to be limited for our purposes.

Share this post


Link to post
Share on other sites

I must say, reading this whole thread all at once for the first time causes a roller coaster of emotions.

Share this post


Link to post
Share on other sites

Funny you should say that; I've now completed my LVOOP implementation and I hate it. I'm moving back to the solution I've described in this topic; it is more straightforward and looks way cleaner on the block diagram. The added value of inheritance turned out to be limited for our purposes.

 

Indeed. LVOOP is a bit like a religion - a dogmatic ideology that everybody seems to still accept, in spite of all the evidence.

 

Well. That's a bit unfair. It's not just LabVIEW. I've been laughing my gonads off about the implementation of "namespaces" that were introduced in PHP 5.3. Another example of taking a perfectly usable language and making it worse to fit the doctrine.

Share this post


Link to post
Share on other sites

Indeed. LVOOP is a bit like a religion - a dogmatic ideology that everybody seems to still accept, in spite of all the evidence.

 

Well. That's a bit unfair. It's not just LabVIEW. I've been laughing my gonads off about the implementation of "namespaces" that were introduced in PHP 5.3. Another example of taking a perfectly usable language and making it worse to fit the doctrine.

 

It's got its uses, it's definitely helped me simplify certain problems. Just to not be vague, hardware abstraction classes for communicating to various random instruments and hardware, loading those dynamically according to a config file, is one clear slam dunk for LVOOP to me. There are others. 

 

I definitely get very annoyed by the "everything must be a class" philosophy, with vaguely implied benefits of "reusability" or "so we can change it later" (inheritance) being the justification.

 

Using OOP does not necessarily practically imply either benefit IME. If you can already see the whole picture of your hierarchy of classes before implementation, and see how it will benefit you, do it. But just using LVOOP that it will somehow be easier or "better" without a clear picture of what you will accomplish with it will just make things more complicated than needed. 

Share this post


Link to post
Share on other sites

It's got its uses, it's definitely helped me simplify certain problems. Just to not be vague, hardware abstraction classes for communicating to various random instruments and hardware, loading those dynamically according to a config file, is one clear slam dunk for LVOOP to me. There are others. 

 

I definitely get very annoyed by the "everything must be a class" philosophy, with vaguely implied benefits of "reusability" or "so we can change it later" (inheritance) being the justification.

 

Using OOP does not necessarily practically imply either benefit IME. If you can already see the whole picture of your hierarchy of classes before implementation, and see how it will benefit you, do it. But just using LVOOP that it will somehow be easier or "better" without a clear picture of what you will accomplish with it will just make things more complicated than needed. 

 

The thread seems to have run its course, so I will respond here as we are now way off topic and it shouldn't hurt :D

 

Hardware abstraction (at least for devices) was solved years ago by hardware engineers. With the proliferation of boxed solutions, SCPI and robust comms converters, the purpose behind most peoples HALs was obsoleted. It reached critical mass about 6 years ago when to not be SCPI compliant became the exception rather than the rule, there was a large choice in comms-to-comms converters and all PCs came with high speed USB and TCPIP.

 

VI drivers were designed to normalise instrument command interfaces. SCPI changed all that and things have moved on but still people write HALs around these (what I consider legacy) drivers instead of directly interfacing the devices to their messaging systems. If you don't use VI drivers, SCPI devices slot straight in to string based messaging systems and open up scripting and performance benefits. Even NI DAQ, Profibus and Modbus devices can be given a thin SCPI translation wrapper in a pinch if you have no control over device selection and laptops become viable test systems.

 

Once you get that far, you suddenly realise that now the devices don't have to be in the same country, let alone hanging off the same computer, as hardware and device specifics have been completely removed from the software. The emphasis then becomes managing and routing the messages, security and scripting test harnesses - very little thought or indeed programming needs to go into how to talk to the hardware.

 

So. The benefit of OOP for hardware abstraction is moot IMO and wasn't solved by OOP or even software, anyway. There seems to be a lot of trumpeting of LVPOOP for solving complex software problems that either don't exist or where created by using LVPOOP in the first place-singletons are a good example of the latter.However, I know what you mean about "vaguely implied benefits" and the evidence is never proffered. There is however evidence that the benefits are just marketing hype.

 

 

Our results indicate that in a commercial environment there may not be a consistent statistically significant difference in the productivity of object-oriented and procedural software development, at least not for the first couple of generations of an object-oriented product. The reason may be low reuse level, but it could also be the underlying business model.

 

spetep- printable.pdf

Edited by ShaunR

Share this post


Link to post
Share on other sites
VI drivers were designed to normalise instrument command interfaces. SCPI changed all that and things have moved on but still people write HALs around these (what I consider legacy) drivers instead of directly interfacing the devices to their messaging systems. If you don't use VI drivers, SCPI devices slot straight in to string based messaging systems and open up scripting and performance benefits. Even NI DAQ, Profibus and Modbus devices can be given a thin SCPI translation wrapper in a pinch if you have no control over device selection and laptops become viable test systems.

 

Normalizing the communication bus (Ethernet/IP, ModbusTCP, Profibus, CAN, Flexray, UDP, etc.) is where I have found LVOOP to be useful. Could I do this without LVOOP? Certainly. Is it a good use of the LVOOP tool? I believe so.

 

The hardware manufacturers I've been working with don't seem to have heard of SCPI or consider it outdated. The types of devices I've worked with are drives, particle counters, valve manifolds, remote I/O, and weather stations. Most of the hardware is from customers' approved components list.

  • Like 1

Share this post


Link to post
Share on other sites

Normalizing the communication bus (Ethernet/IP, ModbusTCP, Profibus, CAN, Flexray, UDP, etc.) is where I have found LVOOP to be useful. Could I do this without LVOOP? Certainly. Is it a good use of the LVOOP tool? I believe so.

This is what came to mind for me when I read Shaun's post, but I don't know anything at all about SCPI. From what the wiki tells me it defines a generic set of messages to be used kind of like j1939 for instruments. It seems like it has just replaced one type of HAL (instrument centric) with another (network abstraction). You still need to handle communication over ethernet, usb, serial, etc. (ie what VISA does). 

 

It also doesn't seem to help at all on the device side. If I'm making more than one device that talks SCPI, I would want to have some standard network interface which takes commands and passes them to an abstraction layer before returning the response. The abstraction layer would handle the differences between my devices when responding to each command.

 

Anyway, to me lvoop amounts to dynamic cluster with a function pointer and occasional linking issues. For when I need to have a dynamic cluster and function pointer, I use lvoop. When I don't need those things I don't use lvoop. 

Share this post


Link to post
Share on other sites

This is what came to mind for me when I read Shaun's post, but I don't know anything at all about SCPI. From what the wiki tells me it defines a generic set of messages to be used kind of like j1939 for instruments. It seems like it has just replaced one type of HAL (instrument centric) with another (network abstraction). You still need to handle communication over ethernet, usb, serial, etc. (ie what VISA does). 

 

J1939. Wow. That's going back a bit. OBDII has been the standard for at least 10 years now (1998?).

 

In response to both of you though.......... Not really. As I think I mentioned. The hardware guys & gals mitigated hardware interfaces with cables (and hubs). There used to be a few teething issues with things like USB compatibility, but they have pretty much disappeared now. If a device doesn't use TCPIP (most offer it as an option, you just need to spec it) Just use one of the many hubs or cables (Ethernet<->USB/RS422/RS485/SERIAL). It s not unusual to use a 20 port USB or 485 hub dangling off of a Ethernet cable, for instance. You can even connect wirelessly if you like, Combine that with standardised string messages (SCPI) and the worse you need for the OCD programmer who cannot abide odd messages is a lookup table from a home grown SCPI hierarchy for the legacy device to whatever ASCII or byte representations the old instruments use. Just copy and paste from their manuals.

 

And testing? Well. That's a whole other area of ease of use. Just throw all the commands into files and squirt them at the devices in whatever order you want with whatever instruments you want. Just one simple VI that loads the file with commands and expected responses. Send them to one device/service/module to do a full factorial message test or to different devices/services/modules to do system tests. Simples.

 

The hardware guys really have solved it, even if small manufacturers are still doing what they did from 20 years ago. I wouldn't be surprised if over 95% of those on the Instrument Drivers Network were SCPI compliant for instance (haven't counted, just saying).

 

PS.

We don't care about the instrument side, that's the firmware developers problem :D

Edited by ShaunR

Share this post


Link to post
Share on other sites

PS.

We don't care about the instrument side, that's the firmware developers problem :D

Lol well ok then, I guess I can't argue with that :P

 

 

J1939. Wow. That's going back a bit. OBDII has been the standard for at least 10 years now (1998?).

 

Perhaps in some areas, but I can say with unfortunate certainty that people still use that protocol. Very unfortunate certainty :(

Share this post


Link to post
Share on other sites

Lol well ok then, I guess I can't argue with that :P

 

 

That was a little unfair, I know.The fact that firmware engineers did solve it for the rest of us doesn't acknowledge their difficulties in achieving it. Maybe they abstracted so we don't have to? Firmware engineers used to plan for serial and a MAX chip in front did the rest. That is why with so many devices the interface is an option. It depends on which daughter-board they install. So again; it was solved with hardware (did one myself, back in the day).

 

With ARM and modern PICs coming with in-built UARTs, USB  and TCPIP stacks, I don't really know but it may highlight the reason for your question. Once the comms interface has been pushed inside the System On A Chip boundary, how does the firmware switch interfaces? What if the device has an interface that isn't propagated to the outside world?. The solution will still be to get it into a character string and then define the SCPI command set that the firmware can work with but how you get the string may be a pain. Saying that.. The firmware engineers did do it and the major suppliers normalised it and for that, I am thankful because it makes my life so much easier.

Edited by ShaunR

Share this post


Link to post
Share on other sites

Time for another on-topic update! I've been playing around with the concept of 'simple' modular labview code, focusing on flexibility and ease of development. After many revisions, the basic module design has come down to a somewhat surprising layout! Module data is now stored into Global Variables, which are restricted to the Private scope of the module lvlib. A semaphore should be used to protect write operations, if you're worried about race conditions. This approach results in clean block diagrams and quick coding, and I'm really liking it so far.

I'll share some code if there is any interest!

Edited by abrink

Share this post


Link to post
Share on other sites

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.

Sign in to follow this  

  • Similar Content

    • By abrink
      I've recently built a medium-sized application that controls a bunch of devices in a university laboratory. From the start, I've been focussed on writing a modular, extensible program, as people tend to add or remove equipment and write new measurement routines accordingly.
       
      In my architecture, each device is represented by an Action Engine (i.e. Keithley Controller.vi). The UI is comprised of subpanels that contain a simple UI for a single device (i.e. Keithley Panel.vi). Due to the nature of AE's, any vi can send commands to any device; data changes are communicated to the panel through User Events.
       
      This all works really well. Creating new measurements is extremely simple, which is vital as most of my colleagues and students are LabVIEW novices. Adding a device is relatively simple too, as Action Engines are easily understood.
       
      And now, the downsides. Mainly, there is a lot of code duplication in this architecture. Many AE's look very similar, with actions like Set VISA Address popping up in every single device. Moreover, imagine having two identical Keithleys (a very real prospect)... one would have to duplicate each vi to create Keithley A and Keithley B, which seems silly indeed. Also, I don't particularly like the extensive use of Variants to limit the number of inputs on the AE.
       
       
      All this tells me it's time for LVOOP, but so far I haven't found a satisfactory design pattern for my use case. The problem is that classes in LabVIEW are by-data instead of by-reference. Somehow, I need to get class instances of my devices across to different parts of my application (including measurement plug-ins), without having them operate on different copies of the instance. Solutions to this problem seem to come in two flavours: communicate to each instance via a command queue (Actor Framework) or operate on references (DVR, Single Element Queue). I find both approaches unsatisfactory: the former buries the elegance of a class (properties and methods) under a pile of asynchronous mumbo-jumbo I don't need, while the latter can introduce deadlocks and forces my users (students and co-workers who will create measurement routines) to place all calls inside de-referencing blocks (be it an In-Place Element structure or a Checkin-Checkout procedure). Finally, I should mention that I'm aware of the existence of GOOP, which seems to enable by-reference OOP in LabVIEW, but I would like to stick to native solutions if at all possible.
       
      Is there a way of harnessing the power of OOP (inheritance, instancing) in LabVIEW, without moving to asynchronous designs or potential deadlocks? Where do I go from here?
       
       
      PS: my apologies if this turned into something of a rant, but shifting to the OOP paradigm in LabVIEW seems overly complicated compared to other languages.
    • By GeorgeG
      I have some projects on the horizon that have to be more reliable than anything I have done in the past. And this new requirement has me thinking about how I deal with errors in LabVIEW more and more. Simultaneously I have been looking into the Actor Framework. The sample project in LabVIEW 2012 for the Evaporative Cooler does a great job of showing how many of the pieces of a full application fit together. However, as I have studied the example, I have noticed that in many places error handling has simply been ignored.
      An example of this is in the water level control... specifically, I take issue with the word "guarantee". Documentation states: "Water Level is a concrete implementation of a Level Controller. We will use it to guarantee that the water in our evaporative cooler’s reservoir never falls below a minimum safe level."
      What if the building loses water pressure? Or the piping fails? Or the valve doesn't actually respond? Or any of a million scenarios that result in the program being unable to really guarantee anything.
      I'd like to open a discussion of how the Evaporative Cooler example could improve on its error handling so that it was robust enough to safely deal with all the "what ifs" of the world. This question is covering everything from "what if writing to the value property of a UI indicator fails?" to "what if the building water supply fails and the water level doesn't respond when I command the valve?"
      And those seem like two great places to start! In the context of the Evaporative Cooler example project, I found three VIs whose error handling I would like to discuss:
      1. Timed Loop Actor.lvlib:Timed Loop Actor.lvclass:Read Controller Name.vi
      2. Live User Controller.lvclass:Update Current Temperature.vi
      3. Level Controller.lvlib:Level Controller.lvclass:Update Core.vi
      In the case of #1, the VI does not have error in/out terminals, but some of the VI's it calls does. This is a situation I have encountered before: how do you add error handling to a very low level VI that does not (and for the sake of discussion cannot) have error terminals?
      In the case of #2, the VI writes to a property node and ignores the error terminals. In a big application there are soooo many property nodes! What are the implications of ignoring the errors? When is it safe to ignore them? This is probably very common, and has some overlap with #1... In general, in LabVIEW, when can you ignore error terminals?
      #3 is the logic VI that determines whether to open or close a valve based on water level. But it doesn't keep track of time and whether things are "working". And certainly if you put a human operator in place of the VI, the human would after some time decide that opening the valve hadn't helped the water level. Can the VI mimic this?
      For all of this error handling business, I think I am really asking four questions:
      A) What should the software do on its own to respond to the error?
      B) What should the software tell the operator (who might have never heard of LabVIEW or college) about the error?
      C) What should the software tell the operator's supervisor about the error?
      D) What should the software tell the software developer about the error?
      It is pretty easy to write an application when everything works the way it should! But when things might go haywire, how do you write application then?
×
×
  • Create New...

Important Information

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