Variable inside timer callback is lost after first iteration in GUI - undefined function or variable

12 views (last 30 days)
Daniel Melendrez
Daniel Melendrez on 4 Dec 2019
Edited: Adam on 5 Dec 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:
  1. 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:
  1. 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 Comments
Daniel Melendrez
Daniel Melendrez on 4 Dec 2019
Guys. I deeptly appreciate the great feedback that all of you are providing.
Adam: I can confirm that the behavior of the function is the same even after removing handles from the TimeFcn definition. After the first iteration I lose persistent variables as Max mentioned in another comment.
Max: I am in the same 'frequency' with you. I am reading an updated snapshot of handles in the beggining of my callback (based on info that I gathered from other posts). I don't think the problem is on the way I assigned handles in the callback function.
In agreement with both of you I removed handles but I am getting the same result. I now understand that the workspace of this function is refreshed on each iteration and that's why my variables are being lost.
I will reply to your other comments in this thread.
Cheers guys!

Sign in to comment.

Accepted Answer

Max Murphy
Max Murphy on 4 Dec 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 Comments
Max Murphy
Max Murphy on 4 Dec 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.

Sign in to comment.

More Answers (2)

Adam
Adam on 4 Dec 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 Comments
Adam
Adam on 4 Dec 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.

Sign in to comment.


Daniel Melendrez
Daniel Melendrez on 5 Dec 2019
Edited: Daniel Melendrez on 5 Dec 2019
Hello guys
So here's the implementation (so far) for solving the problem I presented to you.
The solution is basically to declare almost every variable as a handles object. I say almost because apparently only those variables whose value will be reused had to be modified, including of course iterations. Just to be safe I did it will all of them but the current temperature datum.
So, my changes are:
  1. The timer function declaration has no handles as an input, as already discussed.
  2. The function continuousTemperatureRead is self-contained and does not call any other functions as I originally intended. guidata is updated at the end of my plotting loop and the conditional that wraps wheter there's serial data or not. handles is invoked at the beginning of this function. I had to implement an extra conditional for the incoming serial data since I was getting some zeros on the plot.
  3. My next BIG ISSUE and therefore why I didn't reply until now, was that the assigment of axes in combination with an animated line resulted on a big mess. At first, a second figure would pop-up, then the axis would show wrong data and so on and so forth.
Finally, I think I have for now what I wanted to achieve with this portion of the code. Below is my final function and the result of this section can be seen in the image ;)
I want to thank you all for your valuable input, specially you Max because you confirmed what I had originally in mind regarding using these variables as part of the handles structure. Having said that, I will accept your answer and THE answer. One thing regarding this:
"Haha, I am not sure that I have ever written "elegant" code!"
Are you saying that you don't code on a tuxedo while drinking dry Martini and wearing a monocle? O_o
Cheers y'all and for sure you will read me around here very soon!
I am working on a massive project for my PostDoc and I still have a very long and winding road ahead to complete this software.
Daniel
function continuousTemperatureRead(myTimer, eventdata,hObject)
handles = guidata(hObject);
% *** ASK SERIAL DEVICE IS DATA IS READY
pause(0.1)
handles.numBytesRead = handles.serialTempControl.BytesAvailable ;
if (handles.numBytesRead >= 1) % If there's data
tempQueryStr = getValues(handles.serialTempControl); % get what the serial buffer has
if strcmp(tempQueryStr,'1') % If the device says "yes, data is ready"
flushinput(handles.serialTempControl); % clean the buffer to read ONLY temperature values
fprintf(handles.serialTempControl, '%s\n', tempRequest); % read the value
pause(0.1) % these are neccessary!!!!
if (handles.serialTempControl.BytesAvailable >= 1)
tempValStr = getValues(handles.serialTempControl);
if ~strcmp(tempValStr,'0.00') % I had to include this to avoid showing zeros that shouldn't be
% here but I will investigate this later on. Perhaps is a matter
% of asyncronism between the Matlab timer and the Arduino controller
temperaturePoint = str2double(tempValStr);
% *** FORMAT THE TEMPERATURE VALUE
% Insert data plotting function here
% The only reason for having this conditional is for the 'tic' function however I might remove it later
% coz I am getting time points in a different way now
if isequal(handles.liveTempPlotInit,0)
handles.iterations = 1;
handles.liveTempPlotInit = 1;
tic % Start counting
end
handles.timeContinuous = toc/60; %only for accumulated data
handles.tempCont(handles.iterations) = temperaturePoint;
handles.timeContinuousVector(handles.iterations) = handles.timeContinuous;
% Initial setup for graph display
% *********************************
if isequal(handles.flagFigure,0)
axes(handles.tempPlot);
% handles.liveTempPlot = gca; % I had to remove this, otherwise it would go crazy
handles.tempPlot.YLabel.String = strcat('Temperature',strcat( {' '}, {char(176)},{'C'})); %ylabel('Temperature')
handles.tempPlot.XLabel.String = "time[min]";
handles.tempPlot.XGrid = 'on';
handles.tempPlot.YGrid = 'on';
handles.tempPlot.XMinorGrid = 'on';
handles.tempPlot.YMinorGrid = 'on';
handles.tempPlot.FontSize = 9;
handles.tempPlot.XTickLabelRotation = 45;
handles.tempPlot.XLabel.String = "time[min]";
handles.animatedLineTemp = animatedline('LineWidth', 1,'Color', 'blue','Parent', handles.tempPlot); %line(handles.timeContinuous, temperaturePoint); % 'Parent', handles.tempPlot
disp("Figure config done")
handles.flagFigure = 1;
end
% Time managmement
t(handles.iterations,1) = datetime('now', 'Format','HH:mm:ss'); % - handles.startTime; %+initialTime;
handles.timePoints(handles.iterations,1) = datenum(t(handles.iterations));
addpoints(handles.animatedLineTemp, handles.timePoints(handles.iterations),temperaturePoint);
handles.tempPlot.XLim = datenum([t(handles.iterations,1)-seconds(30) t(handles.iterations,1)]);
datetick(handles.tempPlot, 'x','keeplimits'); % very important to tell this dude where to show the time
%refreshdata(handles.animatedLineTemp); % same story, refreshingdata causes problems
drawnow limitrate
%refreshdata(handles.tempPlot); % Is this necessary?
handles.iterations = handles.iterations+1;
guidata(hObject, handles); % Yes! only update handles once here
end
end
end
end
guidata(hObject, handles); % in case the conditional jumps here
end
  1 Comment
Adam
Adam on 5 Dec 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!

Sign in to comment.

Products


Release

R2019a

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!