Callback function w MATLAB
Callback function to bardzo przydatny mechanizm pozwalający wywołać wskazaną przez użytkownika funkcję przy okazji obsługi jakiegoś zdarzenia. Scenariuszy, w których callback funtion może się przydać, jest kilka. Dla przykładu, potrzebne nam do analizy dane mogą być na bieżąco odczytywane ze sprzętu pomiarowego lub serwera. W związku z tym w MATLABie tworzymy odmierzający czas obiekt, z którym to wiążemy szereg procedur umożliwiających cykliczne pobieranie i analizę porcji danych – i tu właśnie jest miejsce na wykorzystanie mechanizmu callback
. Najlepiej będzie zilustrować to przykładem - spróbujmy zaimplementować w MATLABie technikę Pomodoro. W skrócie, Pomodoro to technika motywacyjna umożliwiająca zwiększenie naszej produktywności na tej zasadzie, że czując potrzebę przerwy mówimy sobie „pocisnę jeszcze te 15 minut a potem luz”. Zazwyczaj w technice pomodoro wykorzystuje się kuchenny minutnik, my wykorzystamy MATLABa i obiekt timer
.
Pomidoro
Mechanizm ma działać na tej zasadzie, że w momencie zbliżającego się kryzysu uruchamiamy funkcję pomodoro.m, która - nie blokując nam w żaden sposób pracy - odmierzy 15 minut, a po tym czasie wyświetli w oknie głównym MATLABa komunikat informujący o tym, że możemy sobie zrobić drzemkę czy przydzielić innego głaska.
Przede wszystkim potrzebny nam jest obiekt timer
, dzięki któremu będziemy mogli odmierzać czas
timer1 = timer
Wśród właściwości obiektu interesuje nas StartDelay
określająca, po jakim czasie ma zostać wykonana zdefiniowana przez nas (już za chwilę) funkcja. Docelowo powinno to być 15 * 60 sekund, jednak do celów testowych wystarczy nam wartość 5.
timer1.StartDelay = 5;
Teraz możemy przejść do kluczowej kwestii, czyli do zdefiniowana tego, co ma się stać po zadanym czasie. W tym celu należy określić TimerFnc
, co można zrobić na trzy różne sposoby. Najprostszy jest taki, że pomiędzy znakami apostrofów wpisujemy wprost co chcemy, żeby MATLAB dla nas zrobił
timer1.TimerFnc = 'disp(''Zrób sobie przerwę, zasłużyłeś.'')';
Podwójny znak apostrofu jest konieczny z racji stosowania apostrofów w apostrofach.
To w zasadzie wszystko, by ta podstawowa MATLABowa wersja minutnika (sekundnika) działała. Wystarczy teraz wpisać:
start(timer1)
Po upływie 5 sekund w oknie poleceń powinien pojawić się zdefiniowany przez nas komunikat.
Callback z małpą
Załóżmy jednak, że możemy być mocno zajęci pracą w edytorze i komunikat możemy przegapić. Przydałby się zatem dodatkowy sygnał dźwiękowy, który oprócz wyświetlanego tekstu informowałby o tym, że czas minął. Całą procedurę można zamknąć w jednej funkcji, nazwijmy ją alarm
.
function alarm() disp('Zrób sobie przerwę, zasłużyłeś'); load gong.mat soundsc(y, Fs);
Funkcję alarm należy oczywiście ustawić jako Timerfnc
.
Timer1.Timerfnc = @alarm Start(timer1)
Niedługo po wystartowaniu timera komunikat błędu poinformuje nas o pewnym problemie ...
Error while evaluating TimerFcn for timer 'timer-1'
Too many input arguments.
W każdym języku programowania prędzej czy później okazuje się, że nie da się zrobić wszystkiego w do końca intuicyjny dla użytkownika sposób. W MATLABie tak jest z mechanizmem callback function. Wszystkie funkcje wykorzystywane jako callback muszą przyjmować przynajmniej 2 argumenty wejściowe. Jeśli chcemy, żeby wszystko zadziałało, należy zmienić pierwszą linię funkcji alarm na:
function alarm(src, evt)
W naszym przypadku argumenty src i evt nie są jeszcze do niczego wykorzystywane, ale ponieważ mechanizm callback zakłada ich przekazanie do funkcji, muszą się tu znaleźć. Są oczywiście sytuacje, gdy elementy te mogą się przydać. src to obiekt wywołujący funkcję (w naszym przypadku timer1), evt to struktura zawierająca informacje o zdarzeniu (jest tam i jego nazwa, i dokładny czas).
Jeśli po poczynionej modyfikacji uruchomimy timer1, wszystko powinno zadziałać.
Ostatni krok
Zróbmy kolejny krok. Załóżmy, że dźwięk chcielibyśmy mieć w naszym minutniku opcjonalnie, bo na przykład siedząc przy komputerze późno w nocy nie chcemy hałasować. Funkcja alarm
powinna więc przyjąć dodatkowy argument dzwiek. W przypadku wartości true dźwięk się odtworzy, w innym przypadku nie.
function alarm(src, evt, dzwiek) disp('Zrób sobie przerwę, zasłużyłeś'); if dzwiek load gong.mat soundsc(y, Fs); end
Teraz trzeba odpowiednio zmienić TimerFnc
w obiekcie timer1. I tu znowu nie wygląda to intuicyjnie. Jeżeli funkcja, którą wywołujemy jako callback ma przyjąć dodatkowe argumenty (poza src i evt) należy je podać po przecinku za nazwą funkcji, a całość zamknąć w nawiasach klamrowych.
Timer1.timerFnc = {@alarm, true}
Poniżej gotowa funkcja pomidoro
, przyjmująca opcjonalny argument określający, czy wywołaniu alarmu ma towarzyszyć dźwięk.
Jeśli wszystko jasne, to
Zrób sobie przerwę, zasłużyłeś.:)
function pomidoro(czas, dzwiek) if nargin < 2 dzwiek = true; end if nargin < 1 czas = 15*60; end timer1 = timer('StartDelay', czas); timer1.TimerFcn = {@alarm, dzwiek}; start(timer1); function alarm(src, ~, dzwiek) disp('Zrób sobie przerwę, zasłużyłeś'); if dzwiek load gong.mat soundsc(y, Fs); end stop(src) delete(src)