Abstract
This paper proposes a novel distributed time synchronization scheme for wireless sensor networks, which uses max consensus to compensate for clock drift and average consensus to compensate for clock offset. The main idea is to achieve a global synchronization just using local information. The proposed protocol has the advantage of being totally distributed, asynchronous, and robust to packet drop and sensor node failure. Finally, the protocol has been implemented in MATLAB. Through several simulations, we can see that this protocol can reduce clock error to ±10 ticks, adapt to dynamic topology, and be suitable to large-scale applications.
1. Introduction
As in all distributed systems, time synchronization is very important in wireless sensor networks (WSNs) since the design of many protocols and implementation of applications require precise time, for example, forming an energy-efficient radio schedule, conducting in-network processing (data fusion, data suppression, data reduction, etc.), distributing an acoustic beamforming array, performing acoustic ranging (i.e., measuring the time of flight of sound), logging causal events during system debugging, and querying a distributed database.
Time synchronization is a research area with a very long history. Various mechanisms and algorithms have been proposed and extensively used over the past few decades. However, several unique characteristics of WSNs often preclude the use of the existing synchronization techniques in this domain. First, since the amount of energy available to battery-powered sensors is quite limited, time synchronization must be implemented in an energy-efficient way. Second, some messages need to be exchanged for achieving synchronization while limited bandwidth of wireless communication discourages frequent message exchanges among sensor nodes. Third, the small size of a sensor node imposes restrictions on computational power and storage space. Therefore, traditional synchronization schemes such as network time protocol (NTP) and global positioning system (GPS) are not suitable for WSNs because of complexity and energy issues, cost efficiency, limited size, and so on.
In the context of WSNs, time synchronization refers to the problem of synchronizing clocks across a set of sensor nodes that are connected to one another over a single-hop or multihop wireless networks. To achieve time synchronization in wireless sensor networks, we have to face the following four challenges.
1.1. Nondeterministic Delays
There are many sources of message delivery delays. Kopetz and Ochsenreiter [1] describe the components of message latency, which they call the Reading Error, as being comprised of 4 distinct components plus the local granularity of the nodes clocks. Their work was later expanded by [2] to include transmission and reception time. The most nondeterministic delay is called Access Time, which is incurred in the MAC layer waiting for access to the transmit channel, its orders of magnitude is larger than the synchronization precision required by the network.
1.2. Clock Drift
Manufacturers of crystal oscillators specify a tolerance value in parts per million (PPM) relative the nominal frequency at 25°C, which determines the maximum amount that the skew rate will deviate from 1. For the nodes used in WSNs, the tolerance value is typically in the order of 5 to 20 PPM. If no drift compensation applied, two synchronized nodes will be out of step soon.
1.3. Robustness
Since sensor networks are often left unattended for long periods of time in possibly hostile environments, synchronization schemes should be robust against link and node failures. Mobile nodes can also disrupt routing schemes, and network partitioning may occur.
1.4. Convergence Speed
Nodes in wireless sensor networks always distribute in large scales, one node may get in touch with another by many hops. This increases the difficulty in reducing the convergence speed in time synchronization algorithm design.
Up to now, many protocols have been designed to address this problem. These protocols all have some basic features in common: a simple connectionless messaging protocol, exchange of clock information among nodes, mitigating the effect of nondeterministic factors in message delivery, and processing utilizing different schemes and algorithms, respectively. They can be classified into two types: centralized synchronization and distributed synchronization.
Centralized synchronization protocol, such as RBS [4], TPSN [2], and FTSP [3], usually has fast convergence speed and little synchronization error. This kind of protocol needs a physical node acting as the whole network’s reference clock, so it has to divide the nodes into different roles, for example, client node and beacon node in RBS. If the node with the special role, such as beacon node in RBS, is out of work, the protocol will suffer from big damage. To deal with the WSNs’ dynamic topology, centralized synchronization protocol is often designed with complexity logic. Another disadvantage of centralized synchronization protocol is that synchronization error grows with the increase of network hops.
Distributed synchronization protocol, such as TDP [5]/GCS [6]/RFA [7]/ATS [8]/CCS [9], can use local information to achieve the whole network synchronization. This kind of protocol can easily adapt to WSNs’ dynamic topology property with lite computation. Currently, the disadvantage of distributed synchronization protocol is that the convergence speed may be a bit slow, relating to the network topology.
This paper describes a new distributed protocol for time synchronization in wireless sensor networks called time synchronization using max and average consensus protocol (TSMA). We adapt a number of techniques to take up the challenges time synchronization has in WSNs. To eliminate the nondeterministic delays, we make use of MAC layer timestamp technique. To compensate for the clock drift, we adapt max consensus protocol, and we use average consensus protocol to compensate for the clock offset. This protocol has the advantages of being computationally light, scalable, asynchronous, robust to node and link failure, and it does not require a master or controlling node.
The rest of the paper is organized as follows. Section 2 summarizes the related work. Section 3 introduces some mathematical tools and definitions that will be instrumental for the proof of convergence of the proposed TSMA algorithm. Section 4 introduces a model for the clock dynamics and formally defines the synchronization objectives, while Section 5 presents the TSMA algorithm in details. This is followed by MATLAB simulations in Section 6. Finally, Section 7 briefly summarizes the results obtained and proposes potential research directions.
matlab code
#######################################
function varargout=prowler(command, varargin)
% prowler - PROBABILISTIC WIRELESS NETWORK SIMULATOR - Main simulation program
%
% Command line options:
% initialize: prowler('Init')
% simulate: prowler('StartSimulation')
%
% A graphical user interface can be invoked by typing prowler.
%
% See also: radio_channel, sim_params, demo_application, simstats, demo_opt
% ***
% *** Copyright 2002, Vanderbilt University. All rights reserved.
% ***
% *** This program is distributed in the hope that it will be useful,
% *** but WITHOUT ANY WARRANTY; without even the implied warranty of
% *** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
% ***
% ***
% Written by Gyula Simon, gyula.simon@vanderbilt.edu
% Last modified: Jan 28, 2004 by GYS
persistent event_Q event_Q_ix topology mote_IDs topology_update_stamp
persistent radio app_name sim_t real_sim_T
global global_event_Q
if nargin<1
command='OpenGui';
end
if strcmpi(command, 'Init')
sim_t=0;
prowler('RefreshApplicationAndRadioInfo');
SetApplicationParams(app_name); % if exists _params file, set the default parameters
prowler('RefreshTopologyInfo', 'init');
prowler('show_animation');
prowler('show_events');
prowparams('select_active_params') % if parameters window is open, enable/disable radio specific UICs
event_Q=[]; global_event_Q=[];
prowler('InsertEvents2Q', make_event(0, 'Init_Radio', -999));
for mote_ID=mote_IDs
prowler('InsertEvents2Q', make_event(0, 'Init_Application', mote_ID));
end
event_Q_ix=1;
print_event(['Application ''' app_name ''' initialized...'])
plot_event('init')
AdjustTipButton(app_name);
prowler('show_LEDs')
real_sim_T=0; % measure simulation time
elseif strcmpi(command, 'StartSimulation')
sim_t=0;
% housekeeping A1: remove those events from global_event_Q which were added when simulation was suspended (by stop button)
% see housekeeping A2
NUM_EVENTS_STOP=1000; % max number of events shown when stopped
sim_params('set', 'SIMULATION_RUNNING',1);
if length(global_event_Q)>0
ix_end=length(global_event_Q)-length(event_Q)+event_Q_ix-1;
print_event(global_event_Q(max(1,end-NUM_EVENTS_STOP):ix_end))
global_event_Q=global_event_Q(1:end-length(event_Q));
end
% simulation
% disable buttons and pulldown menus
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
if ~isempty(h_fig)
a=guihandles(h_fig);
set([a.Application_def, a.Radio_def, a.Simulation_start, a.Simulation_continue], 'enable', 'off')
end
tic; last_draw=clock; last_print=clock;
while sim_t<sim_params('get', 'STOP_SIM_TIME') & event_Q_ix <= length(event_Q) & sim_params('get','SIMULATION_RUNNING')
event=event_Q(event_Q_ix);
print_event(event)
plot_event(event)
last_sim_t=sim_t;
[sim_t, event_name, ID, data]=get_event(event);
if last_sim_t~=sim_t % new time instant, perhaps screen update necessary
upd=sim_params('get', 'ANIMATE');
if upd==1
drawnow, last_draw=clock;
else % slow update or no update
if etime(clock, last_draw)>1
drawnow, last_draw=clock;
end
end
end
if etime(clock, last_print)>1, % this is to prevent gui from freezing when no animation is done
last_print=clock;
if ~sim_params('get', 'PRINT_EVENTS'), print_event(make_event(sim_t, 'Simulation running...',0), 0, 0), end
end
% decide to whom the event should be sent
switch event_name
case {'Init_Radio', 'Channel_Request', 'Channel_Idle_Check', ...
'Packet_Receive_Start', 'Packet_Receive_End', ...
'Packet_Transmit_Start', 'Packet_Transmit_End'}
% event to radio layer
feval(radio, event);
case {'Init_Application', 'Packet_Sent', 'Packet_Received', ...
'Collided_Packet_Received', 'Clock_Tick'}
% event to application layer
feval([app_name, '_application'], event);
otherwise
error(['Unknown event: ' event_name])
end
event_Q_ix=event_Q_ix+1;
end % while
try % try provided for compatibility reasons, older applications cannot handle the following events
if sim_params('get','SIMULATION_RUNNING') % event queue empty
for mote_ID=mote_IDs
feval([app_name, '_application'], make_event(0, 'Application_Finished', mote_ID));
end
else
for mote_ID=mote_IDs
feval([app_name, '_application'], make_event(0, 'Application_Stopped', mote_ID));
end
end
catch
end
sim_params('set', 'SIMULATION_RUNNING',0);
real_sim_T=real_sim_T+toc;
% housekeeping A2: add events to global_event_Q which were not purged from event_Q by the time
% simulation was stopped
% see housekeeping A1
global_event_Q=[global_event_Q, event_Q];
highlight_offset=length(event_Q)-event_Q_ix; % event monitor highlights the next event to be executed
print_event(global_event_Q(max(1,end-NUM_EVENTS_STOP):end)) % update event list with a longer list
print_event(['Stopped. (SimTime=' sprintf('%1.1f', real_sim_T) 's)'], highlight_offset+1)
drawnow;
% enable pulldown menus
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
if ~isempty(h_fig)
a=guihandles(h_fig);
set([a.Application_def, a.Radio_def, a.Simulation_start, a.Simulation_continue], 'enable', 'on')
end
elseif strcmpi(command, 'StopSimulation')
sim_params('set','SIMULATION_RUNNING',0);
% enable pulldown menus
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
if ~isempty(h_fig)
a=guihandles(h_fig);
set([a.Application_def, a.Radio_def, a.Simulation_start, a.Simulation_continue], 'enable', 'on')
end
elseif strcmpi(command, 'InsertEvents2Q')
events=varargin{1};
[event_Q, event_Q_ix]=insert_events(event_Q,event_Q_ix,events,sim_t);
elseif strcmpi(command, 'GetRadioName')
varargout={radio};
elseif strcmpi(command, 'GetAnimationName')
varargout={[app_name '_animation']};
elseif strcmpi(command, 'GetTopologyInfo')
varargout={topology, mote_IDs, topology_update_stamp};
elseif strcmpi(command, 'RefreshApplicationAndRadioInfo')
app_name=sim_params('get', 'APP_NAME');
radio=sim_params('get', 'RADIO_NAME');
application_name=[app_name '_application']; % application is implemented in this m-file
topology_name =[app_name '_topology']; % topology and ID info for the application
animation_name =[app_name '_animation']; % topology and ID info for the application
% check names
if ~exist(application_name, 'file'), error(['Application file '' ' application_name '''.m is missing']); end
if ~exist(topology_name, 'file'), error(['Topology file '' ' topology_name '''.m is missing']); end
if ~exist(animation_name, 'file'), error(['Animation file '' ' animation_name '''.m is missing']); end
if ~exist(radio, 'file'), error(['Radio definition file '' ' radio '''.m is missing']); end
elseif strcmpi(command, 'RefreshTopologyInfo')
topology_name=[app_name '_topology']; % topology and ID info for the application
if nargin >1; % init
topology_update_stamp=0;
[topology, mote_IDs]=feval(topology_name, 'init');
else
[topology, mote_IDs]=feval(topology_name);
end
topology_update_stamp=topology_update_stamp+1;
if topology_update_stamp>1 % not init
radio=sim_params('get', 'RADIO_NAME');
feval(radio, 'Prowler!RefreshTopology'); % notify the radio channel, it should update its internal info
end
elseif strcmpi(command, 'TextMessage')
plot_event('TextMessage', varargin{1}, varargin{2})
elseif strcmpi(command, 'LED')
plot_event(command, varargin{1}, varargin{2})
elseif findstr(command, 'Draw')
if findstr(command, 'Line')
plot_line('Line', varargin{:})
elseif findstr(command, 'Arrow')
plot_line('Arrow', varargin{:})
elseif findstr(command, 'Delete')
plot_line('Delete', varargin{:})
end
elseif strcmpi(command, 'Redraw')
plot_event(command)
elseif strcmpi(command, 'Gui_Mouse_Axes_Click') % message from gui; can be used to update topology
position=varargin{1}; position=position(1,1:2);
try
feval([app_name '_topology'], 'Refresh', position);
prowler('RefreshTopologyInfo');
prowler('Redraw')
end
elseif strcmpi(command, 'GuiMouseMoteClick')
h_clicked_mote=varargin{1};
clicked_mote_ID=get(h_clicked_mote, 'userdata');
feval([app_name, '_application'], make_event(sim_t, 'GuiInfoRequest', clicked_mote_ID));
elseif strcmpi(command, 'show_LEDs')
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
ch=allchild(h_fig);
h_cb=findobj(ch, 'flat', 'tag', 'showLEDs');
show_LEDs=get(h_cb, 'value');
% find all LED related staff on plot
%h_ax=findobj(ch, 'flat', 'tag', 'simulation_plot_ax');
h_ax=findall(0, 'tag', 'simulation_plot_ax');
% h_line=findobj(allchild(h_ax), 'flat', 'type', 'line');
% h_LEDs=[]; h_frames=[]; i=1; found=1;
% while ~isempty(found)
% found=[ findobj(h_line, 'flat', 'tag', ['rLED' num2str(i)]); ...
% findobj(h_line, 'flat', 'tag', ['gLED' num2str(i)]); ...
% findobj(h_line, 'flat', 'tag', ['yLED' num2str(i)]); ...
% findobj(h_line, 'flat', 'tag', ['LED_frame' num2str(i)])];
% h_LEDs=[h_LEDs; found]; i=i+1;
% end
% h_LEDs =[findobj(h_ax, 'tag', 'rLED'); findobj(h_ax, 'tag', 'gLED'); findobj(h_ax, 'tag', 'yLED')];
% h_frames=findobj(h_ax, 'tag', 'LED_frame');
h_LEDs=findobj(allchild(h_ax), 'flat', 'buttondownfcn', '3.1415926;');
if show_LEDs
set([h_LEDs], 'visible', 'on')
else
set([h_LEDs], 'visible', 'off')
end
elseif strcmpi(command, 'show_distances')
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
ch=allchild(h_fig);
% h_ax=findobj(ch, 'flat', 'tag', 'simulation_plot_ax');
h_ax=findall(0, 'tag', 'simulation_plot_ax');
h_cb=findobj(ch, 'flat', 'tag', 'show_distances');
if nargin > 1
show=varargin{1};
set(h_cb, 'value', show);
else
show=get(h_cb, 'value');
end
if show
set(h_ax, 'xtickmode', 'auto')
set(h_ax, 'ytickmode', 'auto')
grid(h_ax, 'on')
else
set(h_ax, 'xtickmode', 'manual')
set(h_ax, 'ytickmode', 'manual')
set(h_ax, 'xtick', [])
set(h_ax, 'ytick', [])
grid(h_ax, 'off')
end
elseif strcmpi(command, 'show_animation')
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
ch=allchild(h_fig);
h=findobj(ch, 'flat', 'tag', 'show_animation');
if nargin >1 % value provided
anim=varargin{1};
if anim==0; anim=3; end
set(h,'value', anim);
sim_params('set_from_gui', 'ANIMATE', mod(anim,3));
else
anim=get(h,'value');
sim_params('set_from_gui', 'ANIMATE', mod(anim,3));
end
elseif strcmpi(command, 'show_events')
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
ch=allchild(h_fig);
h=findobj(ch, 'flat', 'tag', 'show_events');
if nargin >1 % value provided
shw=varargin{1};
set(h,'value', shw);
sim_params('set_from_gui', 'PRINT_EVENTS', shw);
else
sim_params('set_from_gui', 'PRINT_EVENTS', get(h,'value'));
end
elseif strcmpi(command, 'ShowApplicationInfo')
infofile=AdjustTipButton(app_name);
if ~isempty(infofile)
feval(infofile);
end
elseif strcmpi(command, 'ShowApplicationParams')
appparamw('init', app_name)
elseif strcmpi(command, 'OpenGUI')
simgui;
elseif strcmpi(command, 'CloseGUI')
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
delete(h_fig)
h_fig=findobj(allchild(0), 'flat', 'tag', 'paramgui_fig');
close(h_fig)
h_fig=findobj(allchild(0), 'flat', 'tag', 'Prowler_External_Display_fig');
delete(h_fig)
elseif strcmpi(command, 'SwitchDisplay')
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
h_cb=findobj(allchild(h_fig), 'flat', 'tag', 'external_display');
if nargin>1
mode=varargin{1};
if strcmp('mode', 'out')
set(h_cb, 'value', 1)
else
set(h_cb, 'value', 0)
end
end
if get(h_cb, 'value')
mode='out';
else
mode='in';
end
SwitchDisplay(mode);
elseif strcmpi(command, 'GetDisplayHandle')
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
h_ax=findall(h_fig, 'tag', 'simulation_plot_ax');
if isempty(h_ax)
h_fig=findobj(allchild(0), 'flat', 'tag', 'Prowler_External_Display_fig');
h_ax=findall(h_fig, 'tag', 'simulation_plot_ax');
end
varargout={h_ax};
% 'PrintEvent' command is added by LK and YZ
elseif strcmpi(command, 'PrintEvent')
print_event(varargin{1});
elseif strcmpi(command, 'version')
% CURRENT VERSION NUMBER
varargout={'1.25'};
else
error(['Unknown command: ' command])
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [new_Q, new_Q_ix]=insert_events(old_Q,old_Q_ix,events,last_t);
global global_event_Q
PURGE_LIMIT=50;
new_Q=[old_Q, events];
L=length(new_Q);
t=zeros(1,L);
for i=1:L
t(i)=new_Q(i).time;
end
[tmp,ix]=sort(t);
ix1=find(t(ix)>=last_t);
purge_Num=length(ix)-length(ix1);
if purge_Num>PURGE_LIMIT; PURGE=1; else PURGE=0; end
if PURGE % purge old events from Q
global_event_Q=[global_event_Q, old_Q(1:purge_Num)];
new_Q=new_Q(ix(ix1));
new_Q_ix=old_Q_ix-purge_Num;
else
new_Q=new_Q(ix);
new_Q_ix=old_Q_ix;
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function print_event(e, offset,force)
persistent h_list ct str
if (~sim_params('get', 'PRINT_EVENTS'))&sim_params('get', 'SIMULATION_RUNNING')&nargin<3, return, end
if nargin<2, offset=0; end
if nargin>2, ct=100; end % force event update
MAX_LINES=100;
if ischar(e) % init or finish
if findstr(e, 'init')
clear=1; ct=100;
h_list=findall(0, 'tag', 'message_list');
disp_str=e; list_str={e};
%plot_event('init')
elseif findstr(e, 'Running')
clear=1; ct=100;
disp_str=e; list_str={e};
else % finish
clear=0; MAX_LINES=inf; % prevent list truncation
disp_str=e; list_str={e}; ct=100; % ensure to update listbox
end
else
len_e=length(e);
if len_e>1,
clear=1;
list_str=[];
else
clear=0;
list_str=[];
end
for ev_ix=1:len_e
[sim_t, event, ID, data]=get_event(e(ev_ix));
sim_t_sec=sim_t*sim_params('get', 'BIT_TIME');
list_stri=sprintf('%7d %6.2f %-26s %5d', floor(sim_t), sim_t_sec, event, ID);
list_str=[list_str; {list_stri}];
end
if len_e >1
disp_str=[]; ct=100;
else
disp_str=sprintf('t:%7d (%6.2fs), %-27s ID:%3d', floor(sim_t), sim_t_sec, [event ','], ID);
%plot_event(e)
end
end
if ~isempty(h_list)
%str=get(h_list, 'string');
if clear
str=list_str;
else
if length(str)>MAX_LINES-1;
str=[str(end-MAX_LINES+1:end); list_str];
else
str=[str; list_str];
end
end
if ct>10 % do not update list too frequently; it's too slow
set(h_list, 'string', str, 'listboxtop', max(1, length(str)-1), 'value', length(str)-offset)
ct=0;
end
ct=ct+1;
else
% disp(disp_str)
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function plot_event(varargin)
persistent h hr hg hy ht hf ax updatetime
upd=sim_params('get', 'ANIMATE');
if ischar(varargin{1}) % command
cmd=varargin{1};
if ~upd&~strcmp(cmd, 'init')
return
end
else
if ~upd
return
end
cmd='update';
event_struct=varargin{1};
[t, event, ID, data]=get_event(event_struct);
end
[topology, mote_IDs]=prowler('GetTopologyInfo');
drw=0; % init draw update flag
switch cmd
case 'Refresh' % refresh handles which could be changed when display changed
ax=findall(0, 'tag', 'simulation_plot_ax');
ch=allchild(ax);
lines=findobj(ch, 'flat', 'type', 'line');
texts=findobj(ch, 'flat', 'type', 'text');
for i=1:length(h)
h(i) =findobj(lines, 'flat', 'tag', ['DisplayObjMote' num2str(i)]);
hr(i)=findobj(lines, 'flat', 'tag', ['rLED' num2str(i)]);
hg(i)=findobj(lines, 'flat', 'tag', ['gLED' num2str(i)]);
hy(i)=findobj(lines, 'flat', 'tag', ['yLED' num2str(i)]);
ht(i)=findobj(texts, 'flat', 'tag', ['DisplayObjTxt' num2str(i)]);
hf(i)=findobj(lines, 'flat', 'tag', ['LED_frame' num2str(i)]);
end
plot_line('refresh')
case {'init', 'Redraw'}
ax=findall(0, 'tag', 'simulation_plot_ax');
if ~isempty(ax)
Mx=max(topology(:,1)); mx=min(topology(:,1));
My=max(topology(:,2)); my=min(topology(:,2));
if Mx-mx<1, Mx=Mx+.5; mx=mx-.5; end
if My-my<1, My=My+.5; my=my-.5; end
%sc=0.001; mx=mx-sc*abs(mx); my=my-sc*abs(my); Mx=Mx+sc*abs(Mx); My=My+sc*abs(My);
deltaX=Mx-mx;
deltaY=My-my;
DX=deltaX/50; DY=-deltaY/50;
if strcmp(cmd, 'init')
updatetime=clock;
delete(allchild(ax));
set(ax, 'nextplot', 'add')
axis(ax, [mx-1*DX Mx+6*DX my+4*DY My-3*DY])
h=[]; hr=[]; hg=[]; hy=[]; ht=[]; hf=[];
for i=1:length(mote_IDs)
PX=topology(i, 1); PY=topology(i, 2);
h(i)=plot(PX,PY,'.', 'parent', ax, ...
'userdata', mote_IDs(i), 'buttondownfcn', 'prowler(''GuiMouseMoteClick'', gcbo)', 'tag', ['DisplayObjMote' num2str(i)]);
hr(i)=plot(PX+1.5*DX,PY+DY,'.r', 'parent', ax, 'tag', ['rLED' num2str(i)], 'userdata', [1 0 0]);
hg(i)=plot(PX+2.5*DX,PY+DY,'.g', 'parent', ax, 'tag', ['gLED' num2str(i)], 'userdata', [0 1 0]);
hy(i)=plot(PX+3.5*DX,PY+DY,'.y', 'parent', ax, 'tag', ['yLED' num2str(i)], 'userdata', [1 0.6 0]);
hf(i)=line(PX+[.7*DX, .7*DX 4*DX 4*DX .7*DX],...
PY+[DY/2 DY*3/2 DY*3/2 DY/2 DY/2], 'parent', ax, 'tag', ['LED_frame' num2str(i)]);
ht(i)=text(PX+.5*DX,PY-DY,' ', 'FontSize', 8, 'clipping', 'on', 'parent', ax, 'tag', ['DisplayObjTxt' num2str(i)]);
end
set([hr hg hy], 'markersize', 6, 'color', [1 1 1])
set([hr hg hy hf], 'buttondownfcn', '3.1415926;', 'hittest', 'off'); % search purposes
plot_line('init')
drawnow
else
for i=1:length(mote_IDs)
PX=topology(i, 1); PY=topology(i, 2);
set(h(i), 'xdata', PX, 'ydata', PY);
set(hr(i), 'xdata', PX+1.5*DX, 'ydata', PY+DY);
set(hg(i), 'xdata', PX+2.5*DX, 'ydata', PY+DY);
set(hy(i), 'xdata', PX+3.5*DX, 'ydata', PY+DY);
set(hf(i), 'xdata', PX+[.7*DX, .7*DX 4*DX 4*DX .7*DX], ...
'ydata', PY+[DY/2 DY*3/2 DY*3/2 DY/2 DY/2]);
set(ht(i), 'position', [PX+.5*DX,PY-DY]);
end
plot_line('redraw')
end
drw=1;
end
case 'update'
if ~isempty(ax)
ix=find(mote_IDs==ID);
a=feval(prowler('GetAnimationName'));
for i=1:length(a)
if strcmpi(a(i).event, event)
if a(i).animated
switch a(i).animated
case 1 % the mote
if ~isempty(a(i).color), set(h(ix), 'color', a(i).color); end
if ~isempty(a(i).size), set(h(ix), 'markersize', a(i).size); end
case {2,3,4} % LEDs
if a(i).color(1)
mode='on';
elseif a(i).color(2)
mode='off';
else
mode='toggle';
end
switch a(i).animated
case 2 % red LED
h_LED=hr(ix);
case 3 % green LED
h_LED=hg(ix);
case 4 % yellow LED
h_LED=hy(ix);
end
cur_col=get(h_LED, 'color');
on_col =get(h_LED, 'userdata');
if strcmp('toggle', mode)
if cur_col==on_col
mode='off';
else
mode='on';
end
end
if strcmp('on', mode)
set(h_LED, 'color', on_col);
else
set(h_LED, 'color', [1 1 1]);
end
end
drw=1;
end
break
end
end
end
case 'TextMessage'
if ~isempty(ax)
ID=varargin{2};
txt=varargin{3};
ix=find(mote_IDs==ID);
set(ht(ix), 'string', txt)
drw=1;
end
case 'LED'
if ~isempty(ax)
ID=varargin{2};
msg=varargin{3};
ix=find(mote_IDs==ID);
if findstr(lower(msg), 'red'); h_LED=hr(ix);
elseif findstr(lower(msg), 'green'); h_LED=hg(ix);
elseif findstr(lower(msg), 'yellow'); h_LED=hy(ix);
else error(['Bad LED color in command ' msg]);
end
cur_col=get(h_LED, 'color');
on_col =get(h_LED, 'userdata');
if findstr(lower(msg), 'on'); mode='on';
elseif findstr(lower(msg), 'off'); mode='off';
elseif findstr(lower(msg), 'toggle');
if cur_col==on_col; mode='off'; else mode='on'; end
else error(['Bad LED state in command ' msg]);
end
if strcmp('on', mode)
set(h_LED, 'color', on_col);
else
set(h_LED, 'color', [1 1 1]);
end
drw=1;
end
end
if drw*0 % drawnow's are managed in the main loop
if upd==1 % fast update
% drawnow necessary
drwnow=1;
else % slow update
% check update time
if etime(clock, updatetime)>5
drwnow=1;
else
drwnow=0;
end
end
if drwnow
drawnow;
updatetime=clock;
end
end
function plot_line(command, ID1, ID2, varargin)
persistent table
% table contains current lines (or arrows) in the following format:
% each line (arrow) has a row in the table: {ID1_i, ID2_i, handle_i, command, varargin}
command=lower(command);
if nargin>3
style=varargin;
else
style=[];
end
switch command
case {'line', 'arrow', 'delete'}
if ~sim_params('get', 'ANIMATE');
return
end
[topology, mote_IDs]=prowler('GetTopologyInfo');
if strcmp(command, 'delete') & isinf(ID1+ID2)
ct=1; % special delete all syntax, only ID1 is needed
else
ct=0; % two ID's required
end
for ix=1:length(mote_IDs)
if mote_IDs(ix) == ID1
ix1=ix; ct=ct+1;
elseif mote_IDs(ix) == ID2
ix2=ix; ct=ct+1;
end
if ct>1
break
end
end
if ct<2 % ID not found
error(sprintf('Bad ID for line draw: %d, %d', ID1, ID2))
else
ax=findall(0, 'tag', 'simulation_plot_ax');
if ~isempty(ax)
% search for line in the table; if exists, remove
len_t=size(table,1);
for t_ix=1:len_t
if table{t_ix, 1}==ID1 & table{t_ix, 2}==ID2
h=table{t_ix, 3};
table(t_ix,:)=[];
delete(h);
break
end
end
if strcmp(command, 'delete')
if isinf(ID1+ID2) % delete all
if isinf(ID1)
ID_ix=2; ID_val=ID2;
else
ID_ix=1; ID_val=ID1;
end
del_h=[]; del_ix=[];
for t_ix=1:len_t
if table{t_ix, ID_ix}==ID_val
del_h=[del_h, table{t_ix, 3}];
del_ix=[del_ix, t_ix];
end
end
table(del_ix,:)=[];
delete(del_h);
end
return
end
x1=topology(ix1,1); y1=topology(ix1,2);
x2=topology(ix2,1); y2=topology(ix2,2);
if strcmp(command, 'arrow')
phi=pi/10; % arrow angle
L=0.03; % arrow size constant
xa = get(ax,'xlim');
ya = get(ax,'ylim');
set(ax, 'unit', 'points');
pos= get(ax,'position');
xp=pos(3); yp=pos(4); % axis size in figure
xd = xa(2)-xa(1); % axis limits
yd = ya(2)-ya(1);
scalex = L*xd/xp*yp; % compensate aspect ratio
scaley = L*yd;
dx = x1 - x2;
dy = y1 - y2;
alphac=atan2(dy/yd*yp, dx/xd*xp); % angle of line on screen
xx = [x1, x2, x2+scalex*cos(alphac+phi), NaN, ...
x2, x2+scalex*cos(alphac-phi)]';
yy = [y1, y2, y2+scaley*sin(alphac+phi), NaN, ...
y2, y2+scaley*sin(alphac-phi)]';
hl=line(xx,yy, 'parent', ax, 'hittest', 'off', 'tag', 'DisplayArrow', 'userdata', [ID1, ID2]);
set(ax, 'unit', 'normalized'); % necessary for resizable external plot
else % line
hl=line([x1,x2], [y1,y2], 'parent', ax, 'hittest', 'off', 'tag', 'DisplayArrow', 'userdata', [ID1, ID2]);
end
if ~isempty(style)
set(hl, style{:})
end
table=[table; {ID1, ID2, hl, command, varargin}];
end
end
case 'init'
table={};
case 'refresh'
ax=findall(0, 'tag', 'simulation_plot_ax');
h_arr=findobj(allchild(ax), 'flat', 'tag', 'DisplayArrow');
new_table=table;
for i=1:length(h_arr)
ix=get(h_arr(i),'userdata');
for j=1:length(h_arr)
if table{j,1}==ix(1) & table{j,2}==ix(2)
new_table{j,3}=h_arr(i);
break
end
end
end
table=new_table;
case 'redraw'
[topology, mote_IDs]=prowler('GetTopologyInfo');
len_t=size(table,1);
table_old=table;
for t_ix=1:len_t
ID1=table_old{t_ix,1};
ID2=table_old{t_ix,2};
command=table_old{t_ix,4};
xtra=table_old{t_ix,5};
plot_line(command, ID1, ID2, xtra{:})
end
end
function out=AdjustTipButton(app_name)
% Enables Application Info pushbutton if _info file exists for the application,
% disables otherwise. Returns the info file name.
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
h_tip=findobj(allchild(h_fig), 'flat', 'tag', 'Application_tips');
infofile=[app_name, '_info'];
if exist([infofile '.m'], 'file')
set(h_tip, 'enable', 'on')
out=infofile;
else
set(h_tip, 'enable', 'off')
out=[];
end
function out=SetApplicationParams(app_name)
% Checks if application parameters are defined.
% If not, set the default.
h_fig=findobj(allchild(0), 'flat', 'tag', 'Simulation_Fig');
h_par=findobj(allchild(h_fig), 'flat', 'tag', 'Application_params');
paramfile=[app_name, '_params'];
if exist([paramfile '.m'], 'file')
set(h_par, 'enable', 'on')
p=feval(paramfile);
for i=1:length(p)
if isempty(sim_params('get_app', p(i).name))
if iscell(p(i).default) % popupmenu, the first element is the default
sim_params('set_app', p(i).name, p(i).default{1});
else
sim_params('set_app', p(i).name, p(i).default);
end
end
end
out=paramfile;
else
set(h_par, 'enable', 'off')
out=[];
end
function SwitchDisplay(mode)
% switch between internal and external display modes
switch mode
case 'out'
h_sim_fig=findall(0, 'tag', 'Simulation_Fig');
h_ax=findall(h_sim_fig, 'tag', 'simulation_plot_ax');
if ~isempty(h_ax)
set(h_ax, 'tag', 'inactive_simulation_plot_ax')
h=findall(0, 'tag', 'simulation_plot_ax'); delete(h); % just in case...
h_fig=figure(...
'name', 'Prowler - Display',...
'numbertitle', 'off', ...
'integerhandle', 'off', ...
'closerequestfcn', 'prowler(''SwitchDisplay'', ''in'')', ...
'handlevisibility', 'off', ...
'units', 'pixels', ...
'tag', 'Prowler_External_Display_fig');
h_ax_new=copyobj(h_ax, h_fig);
delete(allchild(h_ax))
set(h_ax, 'buttondownfcn', 'figure(findall(0,''tag'', ''Prowler_External_Display_fig''))');
set(h_ax_new, ...
'unit', 'normalized',...
'position', [0 0 1 1], ...
'tag', 'simulation_plot_ax');
xx=get(h_ax, 'Xlim'); yy=get(h_ax, 'Ylim');
plot([xx(2) xx(1) nan xx(1) xx(2)], [yy(1) yy(2) nan yy(1) yy(2)], 'parent', h_ax)
end
case 'in'
h_sim_fig=findall(0, 'tag', 'Simulation_Fig');
h_ext_fig=findall(0, 'tag', 'Prowler_External_Display_fig');
h_ax=findall(h_ext_fig, 'tag', 'simulation_plot_ax');
if ~isempty(h_ax)
set(h_ax, 'tag', 'external_simulation_plot_ax');
h=findall(0, 'tag', 'simulation_plot_ax'); delete(h); % just in case...
h_ax_old=findall(h_sim_fig, 'tag', 'inactive_simulation_plot_ax');
h_ax_new=copyobj(h_ax, h_sim_fig);
set(h_ax_new, ...
'unit', get(h_ax_old, 'unit'), ...
'position', get(h_ax_old, 'position'), ...
'tag', 'simulation_plot_ax');
delete(h_ax_old)
delete(h_ext_fig)
end
end
plot_event('Refresh'); % update handles