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

조회 수: 4 (최근 30일)
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
Adam
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
Daniel Melendrez 2019년 12월 4일
편집: Daniel Melendrez 2019년 12월 4일
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!

댓글을 달려면 로그인하십시오.

채택된 답변

Max Murphy
Max Murphy 2019년 12월 4일
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
Stephen23
Stephen23 2019년 12월 4일
"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
Max Murphy 2019년 12월 4일
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.

댓글을 달려면 로그인하십시오.

추가 답변 (2개)

Adam
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
Daniel Melendrez 2019년 12월 4일
Adam:
I am assuming that on each 'tick' of the timer, iteration holds it previous value. I am definiing it once then just increasing it and using it later as an index. Is this not acceptable inside a function?
I have plenty of codes with similar structures and the integrity of variables is kept intact.
Please check that iterations inside the conditional is intended just to define its initial value, to be then only incremented.
You are right. On the second run it SHOULD test zero meaning that it should only be increased as its soley purpose is to work as an index for an array that I will save at a later point (not now)
Cheers
Adam
Adam 2019년 12월 4일
편집: Adam 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
Daniel Melendrez 2019년 12월 5일
편집: Daniel Melendrez 2019년 12월 5일
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
Adam
Adam 2019년 12월 5일
편집: Adam 2019년 12월 5일
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!

댓글을 달려면 로그인하십시오.

카테고리

Help CenterFile Exchange에서 Loops and Conditional Statements에 대해 자세히 알아보기

제품


릴리스

R2019a

Community Treasure Hunt

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

Start Hunting!

Translated by