03-28-2013 03:26 PM
I'm having a problem similar to that described here:
Here's my code for handling the start/pause key:
int CVICALLBACK StartButtonCB (int panel, int control, int event, void *callbackData, int eventData1, int eventData2) { int rc; int handle; int controlID; switch (event) { case EVENT_COMMIT: // disable the button from further input until we're finished. if (g_runState == PAUSED) { setRunState(RUNNING); } else if (g_runState == RUNNING) { setRunState(PAUSED); } else exit(1); // error. break; case EVENT_RIGHT_CLICK: break; } return 0; } void setRunState(enum StartButtonState state) { SetCtrlAttribute(g_hmainPanel, PANEL_MAIN_START, ATTR_CTRL_MODE, VAL_INDICATOR); SetCtrlAttribute(g_hmainPanel, PANEL_MAIN_START, ATTR_DIMMED, 1); ProcessDrawEvents(); // the state logic is a little tricky here. Since we want the button to reflect // a command, ie, what will happen on the next button push, the label text and colors // selected here are not the same as the state. if (state == RUNNING) { g_runState = RUNNING; SetCtrlAttribute (g_hmainPanel, PANEL_MAIN_START, ATTR_LABEL_TEXT, "PAUSE"); SetCtrlAttribute (g_hmainPanel, PANEL_MAIN_START, ATTR_CMD_BUTTON_COLOR, VAL_RED); ProcessDrawEvents(); g_serverDataType = PTS; tcpWrite("GET PTS"); } else if (state == PAUSED) { g_runState = PAUSED; SetCtrlAttribute (g_hmainPanel, PANEL_MAIN_START, ATTR_LABEL_TEXT, "START"); SetCtrlAttribute (g_hmainPanel, PANEL_MAIN_START, ATTR_CMD_BUTTON_COLOR, VAL_GREEN); ProcessDrawEvents(); g_serverDataType = NONE; ProcessDrawEvents(); } else exit (1); // serious error; may want to put in error message. // finally, we re-enable the button that was disabled when it was pressed. SetCtrlAttribute(g_hmainPanel, PANEL_MAIN_START, ATTR_CTRL_MODE, VAL_HOT); SetCtrlAttribute(g_hmainPanel, PANEL_MAIN_START, ATTR_DIMMED, 0); ProcessDrawEvents(); }
The tcpWrite command sends a command to the server for data, and I have a callback that processes the data and issues a new command for more data.
I find that if I bang on the button too quickly, my program goes into a state in which it remains in the "STARTED" state forever, and all input is locked out from the user. (The button is dimmed, too.)
I suppose I could put a command at the end of my callback to set the button to "hot" again, but this strikes me as something of a hack. Can anyone see something I'm doing wrong here?
Also: is there a way to clear the user event queue? The GetUserEvent() function doesn't seem to remove an event from the queue (at least I can't see it doing that in my execution.)
Thanks...this one is driving me crazy.
03-29-2013 01:28 AM
Hello mzimmers,
it seems to me that you are taking the "callbacks must be short" paradigm too rigidly
Yes, callbacks must be short indeed, but they can actually do something!
In your case, the callback is simply setting a global flag so that another task is set/paused and this can be handled within the callback itself, without need to deal with enabling/disabling the button. The problem you are facing can rather be in tcp writing, that when the user presses the button too quickly can be issued in a wrong moment (i.e. when extetrnal device is not able to handle properly). This is a separate problem that must be solved another way.
One alternative could be to use a toggle button instead of a simple push button: a toggle button has two stable states, each with its proper lable, so you are not in charge of notifying the operator of the new state. Having said this, I am not comfortable with aesthetic of the toggle buttons, so I normally use two picture button with start and stop icons inside: the start button is enabled and the stop one dimmed; start callback firstly disables the button, next does what it's supposed to and enables the stop button as the last operation. The stop button operates the same way. This has a good visual effect and prevents the operator from pushing too quickly: he has to move the mouse to switch state!
Thinking to your unstable state, one possible problem is the tcp callback handling the global flag in a wrong moment. If I'm not wrong, your app is a simple polling loop: you query the device and handle its answer and so on. If this is true, you could structure the tcp callback so that (pseudo-code):
if data is available
read more data
process data
loop
check global flag to start another cycle or not
I seem to remember that you are quite new to CVI, so I encourge you to keep things simple and to solve your problems one after the other, taking your time to learn paradigms and concepts.
03-29-2013 09:09 AM
Thanks, Roberto. Originally my code looked more like yours, but I changed it.
The global flag is set in a callback. If callbacks queue, then all I should need to do is check this flag at the start of my routine, right? Unless the flag is changed by a CB with higher priority, then its state should be constant throughout the processing of the routine.
I read the help on callback priorities, but it didn't include the TCP event callback.
03-30-2013 06:09 AM
You are facing a relatively slow process: your device takes approx. 20 seconds to respond and during this time is probably unable to process a new request.
In a situation like this in my opinion you should:
The structure I suggested for the tcp callback goes on this line: only after processing the answer is completed I can write a new request. In your structure, instead, the operator is able to issue a request at any time regardless the state of the external device simply pressing Stop and Start rapidly: you should modify the structure so that after the Stop button is pressed, Start one is enabled only after complete processing of the last request.
03-30-2013 09:43 AM
Hi, Roberto -
A couple of points/questions:
1. the 20 seconds was something I just put in to test the read timeout. In actuality, the remote device responds much more quickly (probably less than one second).
2. once the button is pressed, virtually the first thing I do is:
SetCtrlAttribute(g_hmainPanel, PANEL_MAIN_START, ATTR_CTRL_MODE, VAL_INDICATOR);
Does this not disable further presses? Is there a preferred way to do this?
04-01-2013 02:59 AM
Yes, setting the button as indicator turns off getting some events (not all, but commit events for sure) on that control; but you re-enable the button at the end of button callback itself, and it will take very few time to execute! "Rapidly" it's an undefined term, but ideally the operator could be fire events at some hertz, that is possibly faster than the time external device takes to process the request. This is made easier by your choice to use a single button both for starting and stopping the process. In my opinion, once the operator has stopped the process, Start button should be enabled by the callback that handles communication with the device, at the end of response from it. And again, using SetWaitCursor could help the operator understand that its action is taken into account.
Is there any special reason that forces you to use one single "toggle" button for start and stop? Simply using two different controls will slow down operator's activity and possibly solve 90% of your problems.
Finally, is there a specific reason to split the button callback in two separate pieces apart the "callback must be short" idea? Given the code you posted, that callback would be short even if you merge all code in it. Remember that simpler is better in coding, considering code efficiency, code readability and maintenance over time: willl you be able to remember the reason for this choice when you will look at the code again in a couple of months spent on other projects?
04-01-2013 08:50 AM
Hi, Roberto -
I used one button mainly for esthetics and simplicity of the UI. Not necessary, but a nice feature IMO.
I'm now using a true toggle button; none of the above code exists anymore.
I appreciate your comment about coding simplicity, and I'm not trying to add any extraneous stuff to this app. I'm just trying to make sure that all situations are covered. I currently use two global variables that drive the app's behavior:
- g_runState: reflects the state of the toggle button
- g_ready: whether the app is ready to send another request for data to the server. (Basically this means that the program has finished processing the data from the previous request.)
Maybe these should be combined into a single, multi-state variable to better reflect the circumstances of the program at any point in time.
One thing that isn't clear to me: can an event CB ever interrupt another CB?
04-01-2013 09:14 AM
mzimmers ha scritto:
One thing that isn't clear to me: can an event CB ever interrupt another CB?
No, unless a callback call ProcessSystemEvents in some place. But you are in effect dealing with multiple processes: the communications process which runs independent from the user interface process; you need to match them in order to maintain consistency in your application.
04-01-2013 09:17 AM
Yes, I agree. I'm not new to real-time programming, so I'm a little annoyed at myself that I'm having so much trouble with this one. I think perhaps I'll simplify the read algorithm. The cost will be occasionally lost data, but if it eliminates system hangs, that's worth it.