A.Rainer
A.Rainer

Reputation: 719

How to use timer in MATLAB to run a function at a fixed interval

I would like to run a function at 30 mins interval. Each time the function run, it will take a different input. Let's say I want to run this 100 times. The function is lookupweather and the input is location1, location2, location3,..., location100

I've tried:

for a = 1:100
    t = timer;          
    t.StartDelay = 30*60*(a-1)       
    t.TimerFcn = @(scr, event) run('lookupweather');
    start(t)
end 

The issue here is that I cannot find away to input the location information. If I tried something lookupweather(location1), the code failed. Of course, without the location input, the lookupweather function fails. Could anyone help?

Edit: I've realized I could do fixed interval

t = timer;
t.Period = 30*60;
t.TasksToExecute = 100;
t.ExecutionMode = 'fixedRate';
t.TimerFcn = @(src, event) run('lookupweather');
start(t)

I still don't know how to input the location information into my function lookupweather, though.

Upvotes: 4

Views: 1797

Answers (2)

Argyll
Argyll

Reputation: 9875

For a general solution, you can create your own Event and Listener classes.

For example, create a new .m file named sampleEvent.m, in which you write

classdef weather < handle
    events
        lookup_timedriven
    end
    methods
        function lookup(self)
             notify(self,'lookup_timedriven')
        end
        %%%% optional
        function self = weather(self)
            t = timer;
            t.StartDelay = 1; % leave some time for event handler to be created
            t.TimerFcn = @()[];  % You can't create a true empty function_handle unless you have a do nothing function in your library.
            start(t)
            for a = 0:99 % the weather calls
              t = timer;          
              t.StartDelay = 30*60*a;
              t.TimerFcn = @self.lookup;
              start(t)
            end 
        end
        %%%%
    end
end

and another .m file named sampleListener.m, in which you write

classdef sampleListener < handle
   methods
      function sampleListener(weather_obj)
         lh = addlistener(weather_obj,'lookup_timedriven',@sampleListener.handleEvnt);
      end
   end
   methods (Static) % static is a must. Because addlistener calls the function without instantiating an object
      function handleEvnt(src,~)
         cellfun(@lookupweather, cellOfLocations, 'UniformOutput', false);
      end
   end
end

where your function lookupweather is assumed to take 1 argument and stores the data properly behind the scenes and cellOfLocations is a cell array of your "locations". You can replace cellfun(@lookupweather, cellOfLocations, 'UniformOutput', false) with whatever you want to do whenever the event is triggered. And Matlab allows you to use event-specific data.

See this mathwork article if you are not familiar with objects in Maltab. Basically, before you start any timing routine, you need to instantiate the event and the handler classes with

weatherEvent = weather;
weatherEventHandler = sampleListener(weather);

Now you just need to trigger the event by calling weather.lookup every 30 minutes or in some other way you want.

Indeed, you can use timer if you wish to run weatherlookup in the background. You already know how timer works and in my example above weather.lookup does not take in any arguments. So the problem you ran into won't happen here.

You can implement your timing code in a separate .m file or directly in the wheather class. If you wish the timer to start automatically, you can define your method in the constructor of weather, which is what I showed in the example.


Some comments:

Your specification of weatherlookup taking 100 variables makes no sense. If it is due to Matlab natively taking unspecified number of "locations", just call weatherlookup with 1 "location" at a time and use cellfun.

I am also not sure if your problem with timer is a real restriction by Matlab. The .TimerFcn field can be any function handle. In other words, you should try replacing @self.lookup in my example with straight up @()notify(self,'lookup_timedriven').

Aside, what you seem to want to do is probably better achieved with Google AppScript + GoogleSheet. Google provides time-driven triggers without anything extra and will run your program for you in the Clouds (as long as you don't do it too frequently; and once very 30 min is fine).

Upvotes: 1

tim
tim

Reputation: 482

You need to declare the timer callback function using a cell array, something like this:

location_index = 1;

t = timer;
t.Period = 1;  %30*60;
t.TasksToExecute = 5;  %100;
t.ExecutionMode = 'fixedRate';
t.TimerFcn = {@timer_callback, location_index};
start(t);

process_locations = true;
while process_locations
    % . . . 
end
stop(t);
delete(t);

function timer_callback(obj, event, location_index)
    fprintf("Location index = %03d\n", location_index);
end

You may also need to use a one-dimensional vector (or array) of locations, something like this:

locations = zeros(1, 100);

t = timer;
t.Period = 1;  %30 * 60;
t.TasksToExecute = 5;  %100;
t.ExecutionMode = 'fixedRate';
%t.TimerFcn = {@timer_callback2};
t.TimerFcn = {@timer_callback3, locations};
start(t);

process_locations = true;
while process_locations
    % . . . 
end
stop(t);
delete(t);

function timer_callback2(obj, event)
    persistent location_index;
    if isempty(location_index)
        location_index = 1;
    end
    fprintf("Location index = %03d\n", location_index);
    location_index = location_index + 1;
end

function timer_callback3(obj, event, locations)
    persistent location_index
    if isempty(location_index)
        location_index = 1;
    end
    locations(location_index) = 12.3;  % Get value from temperature sensor.
    fprintf("locations(%03d) = %f\n", location_index, locations(location_index));
    location_index = location_index + 1;
end

Version 4

This uses a global struct that is modified in the timer callback. Consider encapsulating this in a handler class or nested function to avoid using a global variable.

clear all;
clc;

number_of_iterations = 10;  % 100
number_of_locations = 5;

% Create a global struct for the data.
% Consider encapsulating in a class rather than using a global.
global temperature_data;
temperature_data = struct("IterationIndex", 1, "Processed", false, "Locations", zeros(number_of_iterations, number_of_locations));

t = timer;
t.Period = 1;  %30 * 60;
t.TasksToExecute = number_of_iterations;
t.ExecutionMode = 'fixedRate';
t.TimerFcn = {@TimerCallback4};
start(t);
while temperature_data.Processed == false
        % . . .
        % Yield some processing time.
        time_delay = t.Period * 1000 / 10;
        java.lang.Thread.sleep(time_delay);
end
stop(t);
delete(t);

function TimerCallback4(obj, event)
    global temperature_data;

    % Cycle through locations.
    for location_index = 1:5
        % Get value from temperature sensor.
        temperature_data.Locations(temperature_data.IterationIndex, location_index) = 100 * rand;
        fprintf("temperature_data(%03d, %d) = %5.2f\n", temperature_data.IterationIndex, location_index, temperature_data.Locations(temperature_data.IterationIndex, location_index));
    end

    % Test for completion of processing.
    if temperature_data.IterationIndex >= size(temperature_data.Locations, 1)
        temperature_data.Processed = true;
    else
        temperature_data.IterationIndex = temperature_data.IterationIndex + 1;
    end
end

Version 4 Results

TimerCallback4() 0.058
TimerCallback4() 1.023
TimerCallback4() 2.033
TimerCallback4() 3.042
TimerCallback4() 3.961
TimerCallback4() 4.975
TimerCallback4() 5.982
TimerCallback4() 6.990
TimerCallback4() 8.002
TimerCallback4() 9.008
   10.7889   18.2228    9.9095   48.9764   19.3245
   89.5892    9.9090    4.4166   55.7295   77.2495
   31.1940   17.8982   33.8956   21.0146   51.0153
   90.6364   62.8924   10.1534   39.0855    5.4617
   50.1283   43.1721   99.7560   81.1603   48.5652
   89.4448   13.7547   39.0005   92.7356   91.7494
   71.3574   61.8337   34.3288   93.6027   12.4774
   73.0585   64.6477   83.3152   39.8282   74.9822
   83.5221   32.2460   55.2262   97.9129   54.9309
   33.0424   61.9472   36.0637   75.6510   41.3901

Version 5

This version uses a handle class. It can process either synchronously or asynchronously.

Test.m

    clear all;
    clc;

    % Define the settings.
    number_of_iterations = 10;  % 100
    number_of_locations = 5;
    period = 1;  % 30 * 60  % Seconds.

    % Create the object with required settings.
    temperature_processor = TemperatureProcessor(number_of_iterations, number_of_locations, period);

    % Do the process synchronously.
    temperature_processor.ProcessSync();
    disp(temperature_processor.Locations);

    % Do the process asynchronously.
    temperature_processor.IsProcessed = false;
    temperature_processor.ProcessAsync();
    while temperature_processor.IsProcessed == false
        % Do other stuff.
        % . . .

        % Yield some processing time.
        %pause(0.001);
        java.lang.Thread.sleep(1);  % milliseconds.
    end
    disp(temperature_processor.Locations);

    % Delete the object.
    delete(temperature_processor);

TemperatureProcessor.m

    classdef TemperatureProcessor < handle

        properties
            IsProcessed = false;
            Locations;
        end

        properties (Access = private)
            % Define default values.
            NumberOfIterations = 100;
            NumberOfLocations = 5;
            Period = 30 * 60;  % Seconds.
            AsyncIterationIndex = 1;
            AsyncTimer;
        end

        methods
            % Constructor.
            function obj = TemperatureProcessor(number_of_iterations, number_of_locations, period)
                fprintf("obj.TemperatureProcessor() constructor\n");

                if nargin == 3
                    obj.NumberOfIterations = number_of_iterations;
                    obj.NumberOfLocations = number_of_locations;
                    obj.Period = period;
                end
                obj.Locations = zeros(obj.NumberOfIterations, obj.NumberOfLocations);
            end

            % Destructor.
            function delete(obj)
                fprintf("obj.delete() destructor\n");
                try
                    stop(obj.AsyncTimer);
                    delete(obj.AsyncTimer);
                catch
                end
            end

            function ProcessSync(obj)
                fprintf("obj.ProcessSync()\n");

                iteration_index = 1;
                the_timer = timer;
                the_timer.Period = obj.Period;
                the_timer.TasksToExecute = obj.NumberOfIterations;
                the_timer.ExecutionMode = 'fixedRate';
                the_timer.TimerFcn = {@TimerCallbackSync};
                tic;
                start(the_timer);
                wait(the_timer);
                delete(the_timer);

                function TimerCallbackSync(timer_obj, timer_event)
                    fprintf("obj.Process.TimerCallbackSync() %0.3f\n", toc);

                    % Cycle through locations.
                    for location_index = 1:obj.NumberOfLocations
                        % Get value from temperature sensor.
                        obj.Locations(iteration_index, location_index) = 100 * rand;
                        fprintf("obj.Locations(%03d, %d) = %5.2f\n", iteration_index, location_index, obj.Locations(iteration_index, location_index));
                    end

                    % Test for completion of processing.
                    if iteration_index >= obj.NumberOfIterations
                        obj.IsProcessed = true;
                    else
                        iteration_index = iteration_index + 1;
                    end
                end
            end

            function ProcessAsync(obj)
                fprintf("obj.ProcessAsync()\n");

                try
                    stop(obj.AsyncTimer);
                    delete(obj.AsyncTimer);
                catch
                end
                obj.AsyncIterationIndex = 1;
                obj.AsyncTimer = timer;
                obj.AsyncTimer.Period = obj.Period;
                obj.AsyncTimer.TasksToExecute = obj.NumberOfIterations;
                obj.AsyncTimer.ExecutionMode = 'fixedRate';
                obj.AsyncTimer.TimerFcn = {@obj.TimerCallbackAsync};
                tic;
                start(obj.AsyncTimer);
            end

            function TimerCallbackAsync(obj, timer_obj, timer_event)
                fprintf("obj.Process.TimerCallbackAsync() %0.3f\n", toc);

                % Cycle through locations.
                for location_index = 1:obj.NumberOfLocations
                    % Get value from temperature sensor.
                    obj.Locations(obj.AsyncIterationIndex, location_index) = 100 * rand;
                    fprintf("obj.Locations(%03d, %d) = %5.2f\n", obj.AsyncIterationIndex, location_index, obj.Locations(obj.AsyncIterationIndex, location_index));
                end

                % Test for completion of processing.
                if obj.AsyncIterationIndex >= obj.NumberOfIterations
                    try
                        stop(obj.AsyncTimer);
                        delete(obj.AsyncTimer);
                    catch
                    end
                    obj.IsProcessed = true;
                else
                    obj.AsyncIterationIndex = obj.AsyncIterationIndex + 1;
                end
            end
        end
    end

Version 5 Results

obj.TemperatureProcessor() constructor
obj.ProcessSync()
obj.Process.TimerCallbackSync() 0.051
obj.Process.TimerCallbackSync() 1.029
obj.Process.TimerCallbackSync() 2.026
obj.Process.TimerCallbackSync() 3.025
obj.Process.TimerCallbackSync() 4.034
obj.Process.TimerCallbackSync() 5.024
obj.Process.TimerCallbackSync() 6.023
obj.Process.TimerCallbackSync() 7.023
obj.Process.TimerCallbackSync() 8.023
obj.Process.TimerCallbackSync() 9.023
obj.ProcessAsync()
obj.Process.TimerCallbackAsync() 0.009
obj.Process.TimerCallbackAsync() 1.005
obj.Process.TimerCallbackAsync() 2.004
obj.Process.TimerCallbackAsync() 3.005
obj.Process.TimerCallbackAsync() 4.007
obj.Process.TimerCallbackAsync() 5.005
obj.Process.TimerCallbackAsync() 6.005
obj.Process.TimerCallbackAsync() 7.005
obj.Process.TimerCallbackAsync() 8.005
obj.Process.TimerCallbackAsync() 9.005
obj.delete() destructor

Upvotes: 4

Related Questions