/* * Created on 30-Sep-2004 by Ryan McNally */ package com.speckled.specksim.imp.specks; import java.util.Iterator; import java.util.Map; import java.util.TreeMap; import com.ryanm.config.imp.ConfigurableType; import com.ryanm.config.imp.Description; import com.ryanm.config.imp.NumberRange; import com.ryanm.config.imp.Variable; import com.ryanm.util.io.DataSink; import com.ryanm.util.io.DataSource; import com.ryanm.util.io.SerialUtils; import com.ryanm.util.io.SerializableObject; import com.speckled.specksim.comm.Message; import com.speckled.specksim.comm.Modem; import com.speckled.specksim.imp.Clock.Timer; import com.speckled.specksim.imp.comm.MessageID; import com.speckled.specksim.imp.parallel.MessageCodec; import com.speckled.specksim.imp.state.NeighbourhoodAware; import com.speckled.specksim.state.SpeckState; /** * This speck implementation simply broadcasts its id periodically, * and keeps a decaying list of all the IDs it has received. Look in * the source for voluminous comments demonstrating how to write a * Speck implementation. * * @author ryanm */ @ConfigurableType( "Neighbourly Speck" ) @Description( "A simple speck type that keeps track of its neighbours" ) public class NeighbourlySpeck extends AbstractSpeck { /** * The name of this speck type */ private static final String NAME = "Neighbourly Speck"; /** * Component manager for this flavour. It's very important to make * the {@link SpeckComponentManager} static, or the changes to the * speck configuration will not be applied to specks in the * simulation */ @Variable public static SpeckComponentManager componentManager = new SpeckComponentManager(); /** * The time delay between broadcasts */ @Variable( "Broadcast Delay" ) @Description( "The delay between each broadcast" ) @NumberRange( { 0, 10 } ) public static float broadcastDelay = 1; /** * The time for which a neighbour record exists */ @Variable( "Record Lifetime" ) @Description( "How long each speck will remember its neighbours" ) @NumberRange( { 0, 20 } ) public static float recordLife = 1.1f; /** * The neighbour list. A mapping of the IDs of the neighbours to * the time when this speck last received a message from that * neighbour */ private Map neighbours = new TreeMap(); /** * A data object that encapsulates the interesting state of this * Speck. This SpeckState object is used in Statistic gathering and * visualisation */ private final NeighbourlyState state = new NeighbourlyState(); /** * The timer allows us to periodically do things */ private Timer timer; // add the Message codec for parallel operation static { MessageCodec.addCodec( new MessageCodec.Codec() { public Class getSupportedMessageType() { return NSMessage.class; } public void encodeMessage( Message m, DataSink sink ) { ( ( NSMessage ) m ).encode( sink ); } public Message parseMessage( DataSource source ) { return new NSMessage( source ); } } ); } /** * Constructs a new {@link NeighbourlySpeck}. Every speck * implementation must have a no-argument constructor */ public NeighbourlySpeck() { super( NAME ); } @Override public void startup() { // lets use a timer to get stuff done timer = clock.getRecurringTimer( broadcastDelay, new Runnable() { public void run() { // initiate a transmit request macProtocol.transmit( new NSMessage( getNextMessageID() ) ); // our broadcast delay may have been changed, so... timer.delay = broadcastDelay; } } ); timer.start(); } @Override public void shutdown() { /* * Called when the speck runs out of power. A powered-down speck is * unlikely to remember its neighbours, so... */ neighbours.clear(); // timers will be stopped automatically } @Override protected SpeckComponentManager getComponentManager() { /* * Simply return the component manager. This is used by the abstract * superclass to initialise the radio, mcu, battery, etc. I cannot * stress enough how important it is that the component manager field is * static */ return componentManager; } @Override public void processMessage( Modem modem, Message message ) { /* * Different speck flavours can coexist in the simulator, so we cannot * make any assumptions about the nature of the received message object */ if( message instanceof NSMessage ) { /* * Add or overwrite an entry in the neighbour list */ neighbours.put( new Integer( message.id.src ), new Float( clock.getTime() ) ); } } /** * The message object that this speck broadcasts simply contains * its id number * * @author ryanm */ public static class NSMessage extends Message implements SerializableObject { /** * @param id */ public NSMessage( MessageID id ) { super( id ); } private NSMessage( DataSource source ) { super( new MessageID( source ) ); } public NSMessage decode( DataSource buffer ) { return new NSMessage( buffer ); } public void encode( DataSink buffer ) { id.encode( buffer ); } } public SpeckState getState() { /* * For performance reasons, we reuse the existing state object Use the * abstract super class to set the boring stuff like ID, battery level, * etc */ setCommonState( state ); // Then do the stuff specific to this speck type state.neighbourArray = getNeighbourArray(); return state; } /** * Trims the out of date entries in the neighbour list out and * returns an int array containing the IDs of the surviving * members. * * @return The IDs of this speck's neighbours */ private int[] getNeighbourArray() { // trim down the list for( Iterator> iter = neighbours.entrySet().iterator(); iter .hasNext(); ) { Map.Entry entry = iter.next(); float birth = entry.getValue().floatValue(); if( birth + recordLife < clock.getTime() ) { // this one's a bit stale... iter.remove(); } } // build the array int[] array = new int[ neighbours.size() ]; int i = 0; for( Integer nid : neighbours.keySet() ) { array[ i++ ] = nid.intValue(); } return array; } /** * This class encapsulates the interesting parts of this speck's * state. * * @author ryanm */ private class NeighbourlyState extends SpeckState implements NeighbourhoodAware, SerializableObject { /** * Array used to hold the neighbour list */ private int[] neighbourArray; private NeighbourlyState() { super( NAME ); } public int[] getNeighbourIDs() { /* * This method is specified by the NeighbourhoodAware interface, so * it allows this state to be used by the neighbourhood statistics * module, state processor, and visualiser module */ return neighbourArray; } /* * The encode and decode methods allow state objects to be saved to a * file or transmitted over a network */ public void encode( DataSink sink ) { encodeCommonData( sink ); SerialUtils.write( neighbourArray, sink ); } public NeighbourlyState decode( DataSource source ) { NeighbourlyState s = new NeighbourlyState(); // again, use the super-class to read the boring stuff common // to all specks s.decodeCommonData( source ); // And then get your own data from the stream // take care to read data from the array in the same order // that you write it s.neighbourArray = SerialUtils.readIntArray( source ); return s; } } }