Jump to content

Conversion from (QMH + LV2 Globals) to AF


Recommended Posts

(Cross-posted in the NI Actor Framework community: https://decibel.ni.com/content/groups/actor-framework-2011)

I have a device with a serial interface. The device is always listening for commands. Some commands cause an immediate response, some commands cause a continuous response, and some responses show up without having given a command.

There are several types of responses (streaming and immediate). In my current code, I use several LV2 globals to hold the most recent 5 minutes of each type of data. In a particular test, I can retrieve the most recent value, all values, the next new value, etc.

WIth AF, I can't figure out a good way to replace the LV2s. In the attached zip file, the main code is in "main.vi" and "SerialDevice.lvlib Actors Sim.lvclass Actor Core.vi".

Any advice or analysis is appreciated. Note that some inheritance is there since the protocol can (and does) change often, and I need to maintain backwards compatibility.

2011 Serial Device Project.zip

Link to comment

There are several types of responses (streaming and immediate). In my current code, I use several LV2 globals to hold the most recent 5 minutes of each type of data. In a particular test, I can retrieve the most recent value, all values, the next new value, etc.

WIth AF, I can't figure out a good way to replace the LV2s. In the attached zip file, the main code is in "main.vi" and "SerialDevice.lvlib Actors Sim.lvclass Actor Core.vi".

Any advice or analysis is appreciated. Note that some inheritance is there since the protocol can (and does) change often, and I need to maintain backwards compatibility.

Where are the LV2 globals used in your code? How many of these devices will exist on a system?

My advice would be that LV2 globals do not scale well to a system with "n" instruments. If it would be acceptable to store a fixed number of elements (rather than time-based), I would suggest using lossy queues.

Link to comment

I’m not sure there is anything wrong with using your LV2 globals, assuming they are entirely internal to one actor (i.e. not a by-ref connection between different actors).

The intent definitely is to have the data storage internal to one actor. That keeps the "user API" simple: all interaction with the actor is done via it's Send Queue.

Where are the LV2 globals used in your code?

In the QMH/LV2G "pre" code, the LV2Gs are called in the user code (for initialization and for getting data) and in the slave-loop/QMH for the instrument (primarily for adding data).

How many of these devices will exist on a system?

This particular device has one instance, at the moment. I need to be able to support adding additional copies of the same instrument. I also want to make it very easy for a user to use this instrument in their code. My goal is to have one Send Queue per instrument - as opposed to a driver and five or six LV2Gs.

My advice would be that LV2 globals do not scale well to a system with "n" instruments. If it would be acceptable to store a fixed number of elements (rather than time-based), I would suggest using lossy queues.

I haven't taken the time to replace the client-specific parts of the "pre" code so I can post an apples-to-AF comparison. I do use lossy queues in the working code. What I have not figured out is a good way to make the data stores available to the actor's Send Queue. Should there be one data-store actor per LV2G replacement? Should there be a set of DVRs that get created in the Actor Core override and handled in the parent Actor Core?

Thank you both for looking.

On a side note, I've added a Simulated Byte Stream actor and bundled it into the Simulated transport class. I'm starting to really like actors.

Link to comment

I'm just shooting in the dark here, but what about replacing your LV2 globals with a private DVR? You could drop in a re-entrant VI to replace the LV2 global calls, and then just wire in the DVR reference - if I'm thinking of this right, it wouldn't be much of an overhaul and the block diagram footprint would be only marginally bigger.

This is without looking at your code or even playing with the idea myself, so feel free to yell at me if it's dumb.

Link to comment

Is this a design question or a refactoring question? i.e. Are you rewriting from the ground up or trying to incrementally refactor the current app into actors.

Regarding refactoring, I've had a few projects where I've tried refactoring from by-ref data (usually functional globals/action engines) to by-value data passed via messages. I think success depends a lot on the specific application and how well the original developer separated the functional components. The by-ref code I've seen often is highly coupled making it hard to refactor to a by-value paradigm without making big, sweeping changes. I believe it is possible to do it incrementally, I just haven't been successful doing it yet.

If it's a design question, here's how I would approach it... The Instrument Actor would replicate the instrument as much as possible. In other words, if the instrument doesn't store the most recent 5 minutes of data internally, the Instrument Actor does not either. Once you have that actor fleshed out and working you can decide how you want to implement the data buffer. The buffering functionality would be in a separate class. It could be an Instrument Actor child class, or a class that wraps the Instrument Actor (aggregation,) or a class sitting in between the Instrument Actor and the rest of the application (also known as man-in-the-middle.)

I prefer aggregation or man in the middle over inheritance as I think it's much easier to independently test the buffering code using those techniques. If your buffering is built into the Instrument Actor (or its child class) directly you'll have to have an instrument connected to test it. Many others perfer inheritance. The best decision depends a lot on your circumstances and goals.

Link to comment

Is this a design question or a refactoring question? i.e. Are you rewriting from the ground up or trying to incrementally refactor the current app into actors.

Yes. :) Actually, I'm bringing up the actor-based driver from scratch on the side - copying in the existing personality developed in the slave-loop-type driver.

Regarding refactoring, I've had a few projects where I've tried refactoring from by-ref data (usually functional globals/action engines) to by-value data passed via messages. I think success depends a lot on the specific application and how well the original developer separated the functional components. The by-ref code I've seen often is highly coupled making it hard to refactor to a by-value paradigm without making big, sweeping changes. I believe it is possible to do it incrementally, I just haven't been successful doing it yet.

I have only three or four apps that use this driver, currently. I actually did a fair job keeping most of the code modular/componentized. The main reason I'm attempting to refactor the driver is because some of the inheritance decisions I made didn't allow for new types of device functionality that came along. The other reason is normal "oh, I wish I'd have done this a little different the first time".

If it's a design question, here's how I would approach it... The Instrument Actor would replicate the instrument as much as possible. In other words, if the instrument doesn't store the most recent 5 minutes of data internally, the Instrument Actor does not either. Once you have that actor fleshed out and working you can decide how you want to implement the data buffer. The buffering functionality would be in a separate class. It could be an Instrument Actor child class, or a class that wraps the Instrument Actor (aggregation,) or a class sitting in between the Instrument Actor and the rest of the application (also known as man-in-the-middle.)

I prefer aggregation or man in the middle over inheritance as I think it's much easier to independently test the buffering code using those techniques. If your buffering is built into the Instrument Actor (or its child class) directly you'll have to have an instrument connected to test it. Many others perfer inheritance. The best decision depends a lot on your circumstances and goals.

Ok, good! I like this feedback. I'll try some separate data-engine-type actors that get started by the device actor, and put their Send Queues in the device actor's private data. Data types can change over time, too, so using data-actors allows me some inheritance freedom. One of my main goals is to keep the "test case" VIs very simple - one actor's Send Queue runs along the diagram for all control and data grabbing operations.

As for needing an instrument connected, I made the base "transport" class a simulator that has its own data-actor (child classes of transport can be things like "com port" and "GPIB", for example). While testing, data can be injected into the simulator's buffer to be read by the device driver.

Thank you!

Edited by todd
Link to comment

For a circular buffer like this, you can convert the functional global into an actor. The actor's private data would be the data stored by your functional global (anything in a shift register), and it would have one public method for each operation the global supports (write data, read last, etc). You will provide a message for each of these methods, of course.

To use this buffer, launch the actor and pass its queue to whoever needs to access it.

The net effect will be very much like putting your original functional global into a DVR, with no deadlock/race condition concerns.

Link to comment

Ok, something is slowly becoming more clear for me. I've been trying to write vis that look like the "traditional" left-to-right, open/do stuff/close - and have one by-ref wire running across the diagram into and out of an actor's API. (That way users don't have to know anything about the AF. It makes writing tests easier for me, too.) So far, I've been sending across the Send Queue and creating top-level send messages for each "write this" command, and reply messages for each "read this" command. It sounds instead like I should be sending a queue pair across. Or bundle the Message Queue Pair with the Caller-To-Actor Send Queue, or just bundle the Caller-To-Actor Queue and Actor-To-Caller Queue. That way the actor's API can be all in one virtual folder of one library. Anything I'm missing, here?

Link to comment

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.

×
×
  • Create New...

Important Information

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