Jump to content

abrink

Members
  • Posts

    16
  • Joined

  • Last visited

  • Days Won

    1

abrink last won the day on October 25 2014

abrink had the most liked content!

LabVIEW Information

  • Version
    LabVIEW 2012
  • Since
    2007

Recent Profile Visitors

1,409 profile views

abrink's Achievements

Newbie

Newbie (1/14)

1

Reputation

  1. 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!
  2. 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.
  3. I've created a class which contains (mainly) a bunch of settings and a bunch of events. To keep things organized, I've put these into a Settings and Events cluster, respectively, in the class private data control. If I want to create neat property nodes for such clusters, I have to follow a rather cumbersome procedure: 1. Create a vi for data member access to the whole cluster via the appropriate dialog 2. Create a vi for data member access each individual element via the same dialog 3. Open the Class properties window and navigate to Item Settings 4. Locate the whole cluster accessor and set its Short Name to (e.g.) Settings, plus the Long Name to (e.g.) Settings:All Elements 5. Locate each individual accessor and set its Short Name to (e.g.) Stngs.Polarity, plus the Long Name to (e.g.) Settings:Polarity. This is very tedious, whereas it seems like a very common scenario in LVOOP development. Are there any solutions to this issue?
  4. Unfortunately, a SEQ doesn't solve anything. Any references within the object (events, etc.) are released when the first caller leaves memory, so you'll end up with a useless object in the SEQ. QueueYueue, I fully agree with your first point, but this construction is impossible as I explained before. A root class cannot create a DVR to a child class. Hence my call for a Dynamic Dispatch DVR terminal type, which I'll file shortly.
  5. Thanks, I'd be happy to take a look if you can provide a LV2012 version. I think the issue could be elegantly solved by implementing Dynamic Dispatch DVR terminals. Any opinions on this?
  6. How does this affect persistence though? If I spawn the Holder thread in the root class and create the DVR in the child class, the ownership of the DVR (and other child-specific init code) lies with the calling vi. LabVIEW will clear those references when the first calling vi leaves memory, whereas other VIs might still need access to the object.
  7. I've created a by-reference framework similar to the Extensible Session Framework (https://decibel.ni.com/content/docs/DOC-12813), with the difference that my object references are self-persistent. This is achieved by creating the object instance in a separate thread, which remains running in the background. This is all working rather smoothly, but I would like to clean up my code a bit. Right now, I have a Persistent Object root class which contains only two properties (Instance Name and Destroy Event) and two methods (Create and Destroy). A child class overrides the Create and Destoy methods, and offers class-specific functionality. Unfortunately, the child class also contains the wizardry code that maintains a list of existing instances and manages the creation / destruction of particular instances. I've included some snippets that demonstrate these methods, as well as a typical top-level vi. Example top-level vi: Require method: Unrequire method: Holder method: Of course, the framework-related functions should preferably be in the root class, not in this child class, to allow me to make changes to the framework without editing all child classes. The problem is that I cannot create the Require/Holder/Unrequire functions in the root class and override them in the child class, as the DVR terminal data types will not match. Does anyone see an elegant solution to this problem? Thank you.
  8. 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.
  9. 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!
  10. 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
  11. 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.
  12. 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: Avoid LVOOP unless clearly called for OOP has it's benefits but it makes code more complicated to grasp for novices. 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. Modular Model-View-Controller (MVC) paradigm This should facilitate our need for extendible and adaptable software 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') 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. 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 Each module is a lvlib containing Typedefs, Public Functions, Private functions, and Panels. The Model is a typedef'd cluster containing Settings, Data, Events, and Resources (queues, refs, etc.) 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. 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. All Controller functions that change the Model generate appropriate User Events when doing so. 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 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). A View Requires its own Controller, and Unrequires it on Panel Close. A controller can Require other modules, which it must Unrequire when it no longer needs them. Hardware interfaces, software components, and measurements are all modules. Practical Example User runs the View (panel) of a Measurement Module, which starts the related Controller. 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). 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. Uses carries out his measurement, calling public functions on his required controllers. All changes are immediately reflected in the open Views through event handling. User stops his measurement and closes the view. All required modules (the DAQ, Camera, and Error Logger) are Unrequired. In turn, these models Unrequire their respective Required modules; the Camera Unrequires the Image Processor, etc. All modules shut down gracefully when there are no modules left requiring them. Concerns 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. 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? 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!
  13. On the Plugin HAL solution: I had looked at the Plugin HAL extensively before opening this topic. If I remember correctly, it uses SEQs to pass around the class instances. I wouldn't mind building a simpler application using the same idea, but the Plugin HAL itself seems daunting and bulky. On including a device identifier in each call to an AE: I had considered this as well. It would be the quickest fix, but feels hacky and doesn't provide OOP candy like inheritance. Two very similar devices, for example, would still require code duplication. On using DVRs only in private methods: This seems like a good idea. It would restrict the more tricky bits of code to the Device level, so that writing new Measurements remains simple. Also, the code blocks inside the DVR In-Place Element Structures would be short and simple, minimizing the chance of deadlocks. I'll write some mock-up code using the latter solution, and see how it works out. Thank you all!
  14. 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.
  15. Thank you! Keeping a 'shadow value' in the display state seems a bit clumsy, but it works well!
×
×
  • Create New...

Important Information

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