Variable inside timer callback is lost after first iteration in GUI - undefined function or variable
4 visualizaciones (últimos 30 días)
Mostrar comentarios más antiguos
Daniel Melendrez
el 4 de Dic. de 2019
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 comentarios
Adam
el 4 de Dic. de 2019
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.
Respuesta aceptada
Max Murphy
el 4 de Dic. de 2019
In continuousTemperatureRead, you assign handles the value from guidata(hObject).
However, in controlPanel_OpeningFcn, handles is never assigned to hObject.guidata.
It looks like there is another custom function, MainDataStructure, but my guess is that hObject is out of scope for that function since it is not given as an argument, unless MainDataStructure shares a parent figure object and the association between handles and that parent is made using a call to guidata there.
7 comentarios
Stephen23
el 4 de Dic. de 2019
"to declare and store my variables as part of the handles structure"
Good idea, much cleaner: handles is there, you might as well use it.
Max Murphy
el 4 de Dic. de 2019
Haha, I am not sure that I have ever written "elegant" code!
My suggestion is to define lineTempAcum somewhere else: where you first start the TimerFcn for handles.timer.
Then you can add the handle to that matlab.graphics.primitive.Line object as a field of handles. This way you always have a "pointer" to the original line. Reference that field in continuousTemperatureRead, instead of making a call to line (~line 49). If you don't want it showing a line "early" you can always initialize XData and YData as NaN.
This will avoid re-setting the properties of lineTempAcum each time and you won't have to do the extra flag in the middle of your loop (similar to what you did with handles.liveTemperature, which I assume is an axes somewhere).
The only other stuff I see is timeAccumulation, temperatureAcum, and timeAcumVector; afraid I can't think of an "elegant" way to avoid putting those into handles without defining some other smaller variable that is passed back and forth between your TimerFcn and whatever interacts with those arrays.
Más respuestas (2)
Adam
el 4 de Dic. de 2019
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 comentarios
Adam
el 4 de Dic. de 2019
Editada: Adam
el 4 de Dic. de 2019
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
el 5 de Dic. de 2019
Editada: Daniel Melendrez
el 5 de Dic. de 2019
1 comentario
Adam
el 5 de Dic. de 2019
Editada: Adam
el 5 de Dic. de 2019
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!
Ver también
Categorías
Más información sobre Loops and Conditional Statements en Help Center y File Exchange.
Community Treasure Hunt
Find the treasures in MATLAB Central and discover how the community can help you!
Start Hunting!