Variable inside timer callback is lost after first iteration in GUI - undefined function or variable
显示 更早的评论
I've spent the past 72 hour (at least) trying to solve this issue. I will try to be as succint as possible.
First, I tried to implement my solution as a nested function that is invoked inside the timer HOWEVER I just read that nested function should NOT be defined inside program control statements (according to this document).
Now that I merged the code into one long and tedious to read code I am facing the next problem: once the first iteration or 'tick' of the timer occurs, Matlab throws an error indicating that one of my variables called "iterations" is undefined.
The flow of my algortihm is the following:
- I declared a timer in the opening function of the GUI whose main purpose is to query data from a serial device (Arduino) and get a temperature data point when the value is ready (there is a conversion process from a thermocouple converter). The format of my function is the following:
function controlPanel_OpeningFcn(hObject, eventdata, handles, varargin)
% Choose default command line output for controlPanel
handles.output = hObject;
%timer creation
handles.timer = timer();
set(handles.timer, 'Period',0.5);
set(handles.timer,'ExecutionMode','fixedRate');
set(handles.timer,'TimerFcn',{@continuousTemperatureRead,hObject, handles}); % Here is where you assign the callback function
drawnow;
movegui('center')
% Add all subfolder from the current path to the environment
handles = MainDataStructure(handles); % Initialize the main group data structure
addpath(genpath(handles.base_directory)); % Add all m-file directories to the search path
% Load initial state of controls and buttons
handles = initialState(handles);
% Update handles structure
guidata(hObject, handles);
2. The callback function basically goes over the following steps: a. ask the device if data is ready, b. if so, get a data point, c. initialise counters and a live plot axes ONCE, d. append timestamp and data values, e. display a data point, f. increment on counters, G. update handles
In the following code I replaced some sections with comments ***LIKE THIS*** for the sake of clarity.
function continuousTemperatureRead(myTimer, eventdata,hObject, handles)
handles = guidata(hObject);
% ********** HERE I ASK THE DEVICE IF THE DATA POINT IS READY
numBytes = handles.serialTempControl.BytesAvailable ;
if (numBytes >= 1)
tempQueryStr = getValues(handles.serialTempControl);
if strcmp(tempQueryStr,'1') % The device responds with a '1' if the data point is ready
% *** IF THE DATA IS READY THEN REQUEST THE TEMPERATURE DATA POINT
pause(0.1) % these are neccessary to avoid 'obfuscation' !!!!
if (handles.serialTempControl.BytesAvailable >= 1) % if theres data available
% **** GET TEMPERATURE POINT ***
% *** INITIALISE COUNTERS AND TIMESTAMP:
if isequal(handles.liveTempPlotInit,0) % handles.livePlotInit is a flag to set initial conditions. It should run ONLY ONCE
iterations = 1;
handles.liveTempPlotInit = 1
tic
disp("Initialisation done!")
end
timeAccumulation = toc/60;
temperatureAcum(iterations) = temperaturePoint;
timeAcumVector(iterations) = timeAccumulation; % for later use
if isequal(handles.flagFigure,0) % This is another flag for setting up the axes where the plot will be drawn. It should run ONLY ONCE
axes(handles.tempPlot);
handles.liveTemperature = gca; % The handle of the live temp axis
% Initial setup for graph display
% *********************************
cla(handles.liveTemperature,'reset')
set(handles.liveTemperature,'FontSize',10);
set(handles.liveTemperature, 'XTickLabelRotation',45);
lineTempAcum = line(timeAccumulation, temperaturePoint,'Parent', handles.liveTemperature);
set(lineTempAcum, 'LineWidth', 1);
handles.liveTemperature.YLabel.String = strcat('Temperature',strcat( {' '}, {char(176)},{'C'})); % ylabel('Temperature')
handles.liveTemperature.XGrid = 'on';
handles.liveTemperature.YGrid = 'on';
handles.liveTemperature.XMinorGrid = 'on';
handles.liveTemperature.YMinorGrid = 'on';
grid on
grid minor
handles.flagFigure = 1;
disp("Figure config done")
end
% Add data points
timeTempAcum = get(lineTempAcum, 'xData');
pointsTempAcum = get(lineTempAcum, 'yData');
timeTempAcum = [timeTempAcum timeAccumulation];
pointsTempAcum = [pointsTempAcum temperaturePoint];
set(lineTempAcum, 'xData', timeTempAcum, 'yData', pointsTempAcum);
datetick('x','keeplimits')
iterations = iterations+1;
refreshdata(lineTempAcum);
refreshdata(handles.liveTemperature);
drawnow limitrate % this belongs to the line
end
drawnow % this belongs to the timer
end
end
guidata(hObject, handles);
end
My apologies if the code seems convoluted however it is based on another DAQ GUI I designed some time ago and it works using a different approach (not inside a timer)
This is the behaviour of the GUI:
- The code runs once, I can see the "checkpoint" messages from the initialisation routines, HOWEVER after running once, Matlab throws an error:
"Error while evaluating TimerFcn for timer 'timer-8'
Undefined function or variable 'iterations' "
2. The axes are NOT being assigned to handles.tempPlot rather a new window appears on top of the GUI.
What am I missing? Why is the variable lost?
Important note: Even the Code Analyzer engine underlines this and other variables and says that the value assigned to 'iterations' might be unused.
Is the scope or workspace from the timer and all the variables inside of it being restored after every cycle?
Thank you in advance
Daniel
7 个评论
Never pass 'handles' directly to a callback. You should always pass the handle of the GUI in, then within the callback you access the handles as
handles = guidata( hGUI ) % hGUI being that handle to your GUI
...
guidata( hGUI, handles ) % This is only necessary if you changed or added anything to handles in the function
handles is just a struct and behaves like any other struct really, it's not a magic variable, though it is unfortunate it's name is so similar to 'handles', which is the base class to iniherit a class from if you want it to have 'pass by reference' properties.
But in GUIDE it is simply a struct and obeys the rules of workspace scope like any other, thus when it goes out of scope it gets deleted. So if you have made changes to it and let it just go out of scope those changes are lost unless you do that guidata step to write handles back to the GUI.
Even doing this there are situations you can get in a big mess if you have a complex tree of function calls, usually if you involve listeners and the like. I've forgotten the exact details, but I have had situations where a callback triggers a listener whose function edits handles, then code flow returns to the callback which still has the version of handles it was given originally, knowing nothing about what the listener callback did, and so it does its thing and then writes its version of handles to the GUI, overwriting those that were written by the listener callback.
In your case you do ignore the handles passed in anyway and do this.
Guillaume
2019-12-4
To add to what Adam is saying:
This:
set(handles.timer,'TimerFcn',{@continuousTemperatureRead,hObject, handles}); % Here is where you assign the callback function
is most likely never going to work as intended, in that the handles variable that the callback will receive every time it is called is going to be a snapshot of the handles variable as it is when the the above line is executed. Subsequent changes to handles would not be reflected.
{xxx, handles}
simply create a cell array whose 2nd element is a copy of the current handles.
So yes, don't pass handles to the callback directly like that.
Stephen23
2019-12-4
"Never pass 'handles' directly to a callback."
GUIDE does this.
The function definitions shown in the question are exactly those that are automatically generated by GUIDE:
GUIDE extracts the latest version of handles when it calls its predefined GUI-component callbacks taking handles though. User-defined callbacks don't include this extra hidden step, which is one of the reasons handles confuse people so much in GUIDE. This aspect makes them look like some kind of special object rather than what it actually is (I assume via getappdata - I don't know exactly what it does behind the scenes), since Matlab ensures that the current version of handles is passed in there, not one that was baked in at the time the callback was setup, which is what this
set(handles.timer,'TimerFcn',{@continuousTemperatureRead,hObject, handles})
is doing.
Max Murphy
2019-12-4
Yes, but every time there is a call to @continuousTemperatureRead, he is assigning handles within that function from handles = guidata(hObject);
So he gets the current version of handles. If it is re-creating handles each time from hObject, and hObject is a "snapshot" from this, then he would not have the issue where iterations is ever undefined, because then by definition his loop would always meet the criterion that handles.liveTempPlotInit == 0 and iterations would be defined. However, that would cause a different problem, in that iterations would never increment.
Therefore, I do not think that this is an issue with passing an old version of handles or hObject.
Adam
2019-12-4
No, that is why in my answer I went in a different direction. I wrote my comment up here first, based on a glance at what was happening and a skim over the fact he was having issues related to handles. I left my comment in because it is still vaguely relevant and sound advice. In the case here passing handles to the callback is just an irrelevance though rather than doing any harm, since they are instantly overwritten.
Daniel Melendrez
2019-12-4
编辑:Daniel Melendrez
2019-12-4
采纳的回答
更多回答(2 个)
Adam
2019-12-4
This seems like the opposite problem to what I originally thought it was as I got distracted by the handles and assumed they were not updating. The problem is they are updating and thus you have a variable iterations which is only ever defined in an if statement.
This is never a good thing as it means any time that code runs and the if statement returns false the variable is not created, so when it is used lower down it does not exist.
Your first run sets
handles.liveTempPlotInit = 1;
Your second run tests that this field is equal to 0. It isn't, so it doesn't create the variable iteration so this line will fail:
temperatureAcum(iterations) = temperaturePoint;
2 个评论
Daniel Melendrez
2019-12-4
Unless it is defined as a persistent variable it will not hold its value from one call of the function to the next. When code execution reaches the end instruction of a function its workspace is lost. Next time you call the function it starts again only with what is passed in. As mentioned in the answer below, put it on the handles structure and then it will be updated since, as we have established, you are getting up to date handles coming through. This is a lot better than using a persistent variable. I only mentioned that at the start because it is another option, though not one to seriously consider here I wouldn't say.
Daniel Melendrez
2019-12-5
编辑:Daniel Melendrez
2019-12-5
1 个评论
Good that you managed to solve the problem. On this though:
'Just to be safe I did it will all of them but the current temperature datum'
whilst it is often tempting to do that and here it won't do any harm, I would recommend only doing it for those variables you do actually need to hold their value for the next call of the function, especially since you already identified that this is the issue.
Understanding the required scope of all your variables is extremely useful for when you need to debug in future or for just generally understanding exactly what your code is doing. Knowing that a given variable only needs local scope vs another than needs wider scope is useful knowledge. So only adding those variables to 'handles' that really need to be added is usually best as the rest just clutter up what is already a sizeable structure, in terms of fields (it has all your GUI components attached to it too).
Like I said, it does no real harm adding them all, it's just neater not to!
类别
在 帮助中心 和 File Exchange 中查找有关 Loops and Conditional Statements 的更多信息
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!
