package rescuecore2.standard.kernel.comms; import java.util.Collection; import java.util.Collections; import java.util.Map; import java.util.HashMap; import java.util.List; import kernel.AbstractCommunicationModel; import rescuecore2.messages.Command; import rescuecore2.config.Config; import rescuecore2.worldmodel.Entity; import rescuecore2.worldmodel.EntityID; import rescuecore2.worldmodel.WorldModel; import rescuecore2.log.Logger; import rescuecore2.standard.entities.StandardWorldModel; import rescuecore2.standard.entities.StandardEntityURN; import rescuecore2.standard.entities.Civilian; import rescuecore2.standard.entities.FireBrigade; import rescuecore2.standard.entities.FireStation; import rescuecore2.standard.entities.PoliceForce; import rescuecore2.standard.entities.PoliceOffice; import rescuecore2.standard.entities.AmbulanceTeam; import rescuecore2.standard.entities.AmbulanceCentre; import rescuecore2.standard.messages.AKSpeak; import rescuecore2.standard.messages.AKSubscribe; /** The channel-based communication model. */ public class ChannelCommunicationModel extends AbstractCommunicationModel { /** The prefix for channels config options. */ public static final String PREFIX = "comms.channels."; private static final String COUNT_KEY = "comms.channels.count"; private static final String PLATOON_MAX_CHANNELS_KEY = "comms.channels.max.platoon"; private static final String CENTRE_MAX_CHANNELS_KEY = "comms.channels.max.centre"; private static final String TYPE_SUFFIX = ".type"; private static final String NOISE_SUFFIX = ".noise"; private static final String INPUT_SUFFIX = ".input"; private static final String OUTPUT_SUFFIX = ".output"; private static final String TYPE_VOICE = "voice"; private static final String TYPE_RADIO = "radio"; private static final String NOISE_TYPE_DROPOUT = "dropout"; private static final String NOISE_TYPE_STATIC = "static"; private Map<Integer, Channel> channels; private int platoonMax; private int centreMax; private StandardWorldModel world; /** Construct a ChannelCommunicationModel. */ public ChannelCommunicationModel() { channels = new HashMap<Integer, Channel>(); } @Override public String toString() { return "Channel communication model"; } @Override public void initialise(Config config, WorldModel<? extends Entity> model) { super.initialise(config, model); channels.clear(); world = StandardWorldModel.createStandardWorldModel(model); // Read the channel information int count = config.getIntValue(COUNT_KEY); for (int i = 0; i < count; ++i) { String type = config.getValue(PREFIX + i + TYPE_SUFFIX); Channel channel = null; if (TYPE_VOICE.equals(type)) { channel = new VoiceChannel(config, i, world); } else if (TYPE_RADIO.equals(type)) { channel = new RadioChannel(config, i); } else { Logger.error("Unrecognised channel type: " + PREFIX + i + TYPE_SUFFIX + " = '" + type + "'"); } if (channel != null) { String key = PREFIX + i + NOISE_SUFFIX; Noise input = createNoiseObjects(config, key + INPUT_SUFFIX); Noise output = createNoiseObjects(config, key + OUTPUT_SUFFIX); channel.setInputNoise(input); channel.setOutputNoise(output); channels.put(i, channel); Logger.info("Created channel: " + channel); } } platoonMax = config.getIntValue(PLATOON_MAX_CHANNELS_KEY, 1); centreMax = config.getIntValue(CENTRE_MAX_CHANNELS_KEY, 2); } @Override public void process(int time, Collection<? extends Command> agentCommands) { Logger.debug("ChannelCommunicationModel processing commands at time " + time + ": " + agentCommands); super.process(time, agentCommands); // Update all channels for (Channel next : channels.values()) { next.timestep(); } // Look for subscription commands for (Command next : agentCommands) { if (next instanceof AKSubscribe) { processSubscribe((AKSubscribe)next); } } // Now push all speak commands through the right channels for (Command next : agentCommands) { if (next instanceof AKSpeak) { try { AKSpeak speak = (AKSpeak)next; int channelNumber = speak.getChannel(); Channel channel = channels.get(channelNumber); Logger.debug("Processing speak: " + speak); if (channel == null) { throw new InvalidMessageException("Unrecognised channel: " + channelNumber); } else { channel.push(speak); } } catch (InvalidMessageException e) { Logger.warn("Invalid message: " + next + ": " + e.getMessage()); } } } // And find out what each agent can hear for (Entity agent : world.getEntitiesOfType(StandardEntityURN.FIRE_BRIGADE, StandardEntityURN.FIRE_STATION, StandardEntityURN.POLICE_FORCE, StandardEntityURN.POLICE_OFFICE, StandardEntityURN.AMBULANCE_TEAM, StandardEntityURN.AMBULANCE_CENTRE, StandardEntityURN.CIVILIAN)) { for (Channel channel : channels.values()) { addHearing(agent, channel.getMessagesForAgent(agent)); } } } /** Get a view of all registered channels. @return All channels. */ public Collection<Channel> getAllChannels() { return Collections.unmodifiableCollection(channels.values()); } private Noise createNoiseObjects(Config config, String key) { ChainedNoise result = new ChainedNoise(); result.addChild(lookForFailure(config, key)); result.addChild(lookForDropout(config, key)); result.addChild(lookForStatic(config, key)); return result; } private Noise lookForFailure(Config config, String key) { if (config.getBooleanValue(key + ".failure.use", false)) { return new FailureNoise(config.getFloatValue(key + ".failure.p"), config.getRandom()); } return null; } private Noise lookForDropout(Config config, String key) { if (config.getBooleanValue(key + ".dropout.use", false)) { return new DropoutNoise(config.getFloatValue(key + ".dropout.p"), config.getRandom()); } return null; } private Noise lookForStatic(Config config, String key) { if (config.getBooleanValue(key + ".static.use", false)) { return new StaticNoise(config.getFloatValue(key + ".static.p"), config.getRandom()); } return null; } private void processSubscribe(AKSubscribe sub) { Logger.debug("Processing subscribe message : " + sub); List<Integer> requested = sub.getChannels(); EntityID id = sub.getAgentID(); Entity entity = world.getEntity(id); if (entity == null) { Logger.warn("Couldn't find entity " + id); return; } int max; if (entity instanceof FireBrigade || entity instanceof PoliceForce || entity instanceof AmbulanceTeam || entity instanceof Civilian) { max = platoonMax; } else if (entity instanceof FireStation || entity instanceof PoliceOffice || entity instanceof AmbulanceCentre) { max = centreMax; } else { Logger.warn("I don't know how to handle subscriptions for this entity: " + entity); return; } if (requested.size() > max) { Logger.warn("Agent " + id + " tried to subscribe to " + requested.size() + " channels but only " + max + " allowed"); return; } // Unsubscribe from all old channels for (Channel next : channels.values()) { next.removeSubscriber(entity); } // Subscribe to new channels for (int next : requested) { Channel channel = channels.get(next); if (channel == null) { Logger.warn("Agent " + id + " tried to subscribe to non-existant channel " + next); } else { channel.addSubscriber(entity); } } } }