Commit 0cdce77f authored by Julian Zobel's avatar Julian Zobel
Browse files

Rechargeable Batteries and UAV Landing

parent ccc05c50
...@@ -94,4 +94,5 @@ public interface SimHost extends Host { ...@@ -94,4 +94,5 @@ public interface SimHost extends Host {
* binding with other layers, as these might have been null beforehand. * binding with other layers, as these might have been null beforehand.
*/ */
public void initialize(); public void initialize();
} }
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
package de.tud.kom.p2psim.api.energy; package de.tud.kom.p2psim.api.energy;
import java.nio.channels.UnsupportedAddressTypeException;
/** /**
* This interface implements basic methods used by the EnergyModel to simulate a * This interface implements basic methods used by the EnergyModel to simulate a
...@@ -103,4 +103,6 @@ public interface Battery extends Cloneable { ...@@ -103,4 +103,6 @@ public interface Battery extends Cloneable {
* @return * @return
*/ */
public boolean isFullyCharged(); public boolean isFullyCharged();
public Battery copy(double initialEnergy);
} }
...@@ -46,7 +46,7 @@ public interface EnergyComponent extends EventHandler { ...@@ -46,7 +46,7 @@ public interface EnergyComponent extends EventHandler {
* CommunicationComponent directly. Call goOffline() on the corresponding * CommunicationComponent directly. Call goOffline() on the corresponding
* Mac-Layer instead!</b> * Mac-Layer instead!</b>
*/ */
public void turnOff(); public boolean turnOff();
/** /**
* Generic method to activate a component. <b>DO NOT use this method on a * Generic method to activate a component. <b>DO NOT use this method on a
......
...@@ -54,5 +54,7 @@ public interface ControllableLocationActuator { ...@@ -54,5 +54,7 @@ public interface ControllableLocationActuator {
public void setTargetLocationRoute(LinkedList<PositionVector> route, ReachedLocationCallback cb); public void setTargetLocationRoute(LinkedList<PositionVector> route, ReachedLocationCallback cb);
public void removeAllTargetLocations(); public void removeAllTargetLocations();
public void returnToBase(ReachedLocationCallback cb);
} }
...@@ -231,6 +231,6 @@ public class DefaultHost implements SimHost { ...@@ -231,6 +231,6 @@ public class DefaultHost implements SimHost {
throw new AssertionError( throw new AssertionError(
"This host was configured without any components!"); "This host was configured without any components!");
} }
} }
} }
...@@ -90,16 +90,16 @@ public class EnergyModelFactory implements HostComponentFactory { ...@@ -90,16 +90,16 @@ public class EnergyModelFactory implements HostComponentFactory {
if(useRandomBatteryStartConfiguration) if(useRandomBatteryStartConfiguration)
{ {
// clone the battery from the provided model // clone the battery from the provided model
// FIXME Different Capacities for different nodes.
double percentageOfFullBattery = (1 - random.nextDouble()); double percentageOfFullBattery = (1 - random.nextDouble());
if(percentageOfFullBattery < minimumStartEnergyLevel) if(percentageOfFullBattery < minimumStartEnergyLevel)
percentageOfFullBattery = minimumStartEnergyLevel; percentageOfFullBattery = minimumStartEnergyLevel;
double initialEnergy = batteryModel.getMaximumEnergyLevel() * percentageOfFullBattery; double initialEnergy = batteryModel.getMaximumEnergyLevel() * percentageOfFullBattery;
bat = new SimpleBattery(batteryModel.getMaximumEnergyLevel(), initialEnergy);
bat = batteryModel.copy(initialEnergy);
} }
else { else {
bat = batteryModel.clone(); bat = batteryModel.copy(batteryModel.getMaximumEnergyLevel());
} }
EnergyModel em = new ComponentBasedEnergyModel(host, bat); EnergyModel em = new ComponentBasedEnergyModel(host, bat);
...@@ -115,7 +115,7 @@ public class EnergyModelFactory implements HostComponentFactory { ...@@ -115,7 +115,7 @@ public class EnergyModelFactory implements HostComponentFactory {
* *
* @param energy * @param energy
*/ */
public void setBattery(Battery battery) { public void setBattery(Battery battery) {
this.batteryModel = battery; this.batteryModel = battery;
} }
......
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of PeerfactSim.KOM.
*
* PeerfactSim.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tud.kom.p2psim.impl.energy;
import de.tud.kom.p2psim.api.energy.Battery;
import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor;
/**
* Extension to the basic battery implementation, enables to charge the battery.
*
*
* @author Julian Zobel
* @version 1.0, 21.09.2018
*/
public class RechargeableBattery extends SimpleBattery {
@XMLConfigurableConstructor({ "capacity", "initialEnergy" })
public RechargeableBattery(double capacity, double initialEnergy) {
super(capacity, initialEnergy);
}
/**
* Charge the battery with the given amount in <b>uJ</b>
*
* @param charge
*/
public void chargeBattery(double charge) {
if(currentEnergy + charge >= capacity) {
currentEnergy = capacity;
}
else {
currentEnergy += charge;
}
}
@Override
public void reset() {
currentEnergy = initialEnergy;
}
@Override
public Battery copy(double initialEnergy) {
return new RechargeableBattery(capacity / 1000000, initialEnergy / 1000000);
}
@Override
public String toString() {
return "Rechargeable Battery ["+getCurrentPercentage()+"%] ["+getCurrentEnergyLevel()+"/"+getMaximumEnergyLevel()+"]";
}
}
...@@ -32,13 +32,12 @@ import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor; ...@@ -32,13 +32,12 @@ import de.tudarmstadt.maki.simonstrator.api.util.XMLConfigurableConstructor;
*/ */
public class SimpleBattery implements Battery { public class SimpleBattery implements Battery {
private double capacity; protected final double capacity;
private double initialEnergy; protected final double initialEnergy;
private double currentEnergy; protected double currentEnergy;
private boolean isEmpty = false;
/** /**
* Create a new battery with the given initial energy and total capacity in * Create a new battery with the given initial energy and total capacity in
...@@ -48,15 +47,15 @@ public class SimpleBattery implements Battery { ...@@ -48,15 +47,15 @@ public class SimpleBattery implements Battery {
* @param initalEnergy * @param initalEnergy
*/ */
@XMLConfigurableConstructor({ "capacity", "initialEnergy" }) @XMLConfigurableConstructor({ "capacity", "initialEnergy" })
public SimpleBattery(double capacity, double initalEnergy) { public SimpleBattery(double capacity, double initialEnergy) {
this.capacity = capacity * 1000000; this.capacity = capacity * 1000000;
this.initialEnergy = initalEnergy * 1000000; this.initialEnergy = initialEnergy * 1000000;
this.currentEnergy = initalEnergy * 1000000; this.currentEnergy = initialEnergy * 1000000;
} }
@Override @Override
public double getCurrentPercentage() { public double getCurrentPercentage() {
if (isEmpty) { if (isEmpty()) {
return 0.0; return 0.0;
} }
double percent = 100.0 * currentEnergy / capacity; double percent = 100.0 * currentEnergy / capacity;
...@@ -66,7 +65,7 @@ public class SimpleBattery implements Battery { ...@@ -66,7 +65,7 @@ public class SimpleBattery implements Battery {
@Override @Override
public double getCurrentEnergyLevel() { public double getCurrentEnergyLevel() {
if (isEmpty) { if (isEmpty()) {
return 0.0; return 0.0;
} }
return currentEnergy; return currentEnergy;
...@@ -76,23 +75,16 @@ public class SimpleBattery implements Battery { ...@@ -76,23 +75,16 @@ public class SimpleBattery implements Battery {
public double getConsumedEnergy() { public double getConsumedEnergy() {
return capacity - currentEnergy; return capacity - currentEnergy;
} }
@Override
public void reset() {
currentEnergy = initialEnergy;
isEmpty = false;
}
@Override @Override
public void setToPercentage(double percentage) { public void setToPercentage(double percentage) {
currentEnergy = (capacity / 100.0) * percentage; currentEnergy = (capacity / 100.0) * percentage;
isEmpty = false;
} }
@Override @Override
public void consumeEnergy(double energy) { public void consumeEnergy(double energy) {
if (isEmpty || energy >= currentEnergy) { if ( energy >= currentEnergy) {
isEmpty = true; currentEnergy = 0;
} else { } else {
currentEnergy = currentEnergy - energy; currentEnergy = currentEnergy - energy;
} }
...@@ -100,7 +92,10 @@ public class SimpleBattery implements Battery { ...@@ -100,7 +92,10 @@ public class SimpleBattery implements Battery {
@Override @Override
public boolean isEmpty() { public boolean isEmpty() {
return isEmpty; if(currentEnergy == 0)
return true;
return false;
} }
@Override @Override
...@@ -121,4 +116,9 @@ public class SimpleBattery implements Battery { ...@@ -121,4 +116,9 @@ public class SimpleBattery implements Battery {
return false; return false;
} }
@Override
public Battery copy(double initialEnergy) {
return new SimpleBattery(capacity / 1000000, initialEnergy / 1000000);
}
} }
/*
* Copyright (c) 2005-2010 KOM – Multimedia Communications Lab
*
* This file is part of PeerfactSim.KOM.
*
* PeerfactSim.KOM is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* PeerfactSim.KOM 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. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with PeerfactSim.KOM. If not, see <http://www.gnu.org/licenses/>.
*
*/
package de.tud.kom.p2psim.impl.energy;
import java.util.LinkedList;
import de.tud.kom.p2psim.impl.topology.component.UAVTopologyComponent;
import de.tudarmstadt.maki.simonstrator.api.Host;
import de.tudarmstadt.maki.simonstrator.api.component.HostComponent;
import de.tudarmstadt.maki.simonstrator.api.component.HostComponentFactory;
/**
* Ground-based charger for UAVs
*
*
* @author Julian Zobel
* @version 1.0, 21.09.2018
*/
public class UAVCharger implements HostComponent {
private Host host;
private long replacementDuration;
private int chargerSize;
private LinkedList<UAVTopologyComponent> uavs = new LinkedList<>();
public UAVCharger(Host host, long replacementDuration, int chargerSize) {
this.host = host;
this.replacementDuration = replacementDuration;
this.chargerSize = chargerSize;
}
@Override
public void initialize() {
// TODO Auto-generated method stub
}
@Override
public void shutdown() {
throw new UnsupportedOperationException("Charger shutdown not supported!");
}
@Override
public Host getHost() {
return host;
}
public static class Factory implements HostComponentFactory {
private long replacementDuration;
private int chargerSize;
public void setReplacementDuration(long replacementDuration) {
this.replacementDuration = replacementDuration;
}
public void setChargerSize(int chargerSize) {
this.chargerSize = chargerSize;
}
@Override
public HostComponent createComponent(Host host) {
return new UAVCharger(host, replacementDuration, chargerSize);
}
}
}
...@@ -108,15 +108,16 @@ public class ActuatorEnergyComponent implements EnergyComponent { ...@@ -108,15 +108,16 @@ public class ActuatorEnergyComponent implements EnergyComponent {
} }
@Override @Override
public void turnOff() { public boolean turnOff() {
return; doStateChange(OFF);
return true;
} }
@Override @Override
public boolean turnOn() { public boolean turnOn() {
if (isAvailable()) { if (isAvailable()) {
if (currentState.equals(OFF)) { if (currentState.equals(OFF)) {
currentState = FLY; doStateChange(FLY);
} }
return true; return true;
} }
...@@ -132,7 +133,10 @@ public class ActuatorEnergyComponent implements EnergyComponent { ...@@ -132,7 +133,10 @@ public class ActuatorEnergyComponent implements EnergyComponent {
@Override @Override
public boolean isOn() { public boolean isOn() {
if(!currentState.equals(OFF)) if(!currentState.equals(OFF))
return true; {
doStateChange(FLY);
return true;
}
return false; return false;
} }
......
...@@ -84,8 +84,9 @@ public class OneStateEnergyComponent implements EnergyComponent { ...@@ -84,8 +84,9 @@ public class OneStateEnergyComponent implements EnergyComponent {
} }
@Override @Override
public void turnOff() { public boolean turnOff() {
this.on = false; this.on = false;
return true;
} }
@Override @Override
......
...@@ -89,11 +89,12 @@ EnergyCommunicationComponent { ...@@ -89,11 +89,12 @@ EnergyCommunicationComponent {
} }
@Override @Override
public void turnOff() { public boolean turnOff() {
if (!currentState.equals(IDLE)) { if (!currentState.equals(IDLE)) {
doStateChange(IDLE); doStateChange(IDLE);
} }
tailCounter++; tailCounter++;
return true;
} }
@Override @Override
......
...@@ -89,10 +89,11 @@ public class SmartphoneCommunicationEnergyComponent implements ...@@ -89,10 +89,11 @@ public class SmartphoneCommunicationEnergyComponent implements
} }
@Override @Override
public void turnOff() { public boolean turnOff() {
if (!currentState.equals(OFF)) { if (!currentState.equals(OFF)) {
doStateChange(OFF); doStateChange(OFF);
} }
return true;
} }
@Override @Override
......
...@@ -87,10 +87,11 @@ public class StateEnergyCommunicationComponent implements ...@@ -87,10 +87,11 @@ public class StateEnergyCommunicationComponent implements
} }
@Override @Override
public void turnOff() { public boolean turnOff() {
if (!currentState.equals(OFF)) { if (!currentState.equals(OFF)) {
doStateChange(OFF); doStateChange(OFF);
} }
return true;
} }
@Override @Override
......
...@@ -111,10 +111,11 @@ public class StatelessCommunicationComponent implements ...@@ -111,10 +111,11 @@ public class StatelessCommunicationComponent implements
} }
@Override @Override
public void turnOff() { public boolean turnOff() {
if (!currentState.equals(OFF)) { if (!currentState.equals(OFF)) {
doStateChange(OFF); doStateChange(OFF);
} }
return true;
} }
@Override @Override
......
...@@ -1011,8 +1011,9 @@ public abstract class AbstractMacLayer implements MacLayer { ...@@ -1011,8 +1011,9 @@ public abstract class AbstractMacLayer implements MacLayer {
} }
@Override @Override
public void turnOff() { public boolean turnOff() {
on = false; on = false;
return true;
} }
public boolean turnOn() { public boolean turnOn() {
......
...@@ -90,6 +90,7 @@ public class TopologyFactory implements HostComponentFactory { ...@@ -90,6 +90,7 @@ public class TopologyFactory implements HostComponentFactory {
private boolean alreadyAddedMovement = false; private boolean alreadyAddedMovement = false;
// FIXME this can be inconsistent (both true e.g) and should be refactored
private boolean isUAVComponent = false; private boolean isUAVComponent = false;
private boolean isBaseStationComponent = false; private boolean isBaseStationComponent = false;
......
...@@ -26,16 +26,19 @@ import java.util.Set; ...@@ -26,16 +26,19 @@ import java.util.Set;
import de.tud.kom.p2psim.api.common.SimHost; import de.tud.kom.p2psim.api.common.SimHost;
import de.tud.kom.p2psim.api.energy.ComponentType; import de.tud.kom.p2psim.api.energy.ComponentType;
import de.tud.kom.p2psim.api.energy.EnergyModel; import de.tud.kom.p2psim.api.energy.EnergyModel;
import de.tud.kom.p2psim.api.network.SimNetInterface;
import de.tud.kom.p2psim.api.topology.Topology; import de.tud.kom.p2psim.api.topology.Topology;
import de.tud.kom.p2psim.api.topology.movement.MovementModel; import de.tud.kom.p2psim.api.topology.movement.MovementModel;
import de.tud.kom.p2psim.api.topology.movement.SimUAVLocationActuator; import de.tud.kom.p2psim.api.topology.movement.SimUAVLocationActuator;
import de.tud.kom.p2psim.api.topology.movement.UAVMovementModel; import de.tud.kom.p2psim.api.topology.movement.UAVMovementModel;
import de.tud.kom.p2psim.api.topology.placement.PlacementModel; import de.tud.kom.p2psim.api.topology.placement.PlacementModel;
import de.tud.kom.p2psim.impl.energy.RechargeableBattery;
import de.tud.kom.p2psim.impl.energy.components.ActuatorEnergyComponent; import de.tud.kom.p2psim.impl.energy.components.ActuatorEnergyComponent;
import de.tud.kom.p2psim.impl.energy.models.AbstractEnergyModel;
import de.tud.kom.p2psim.impl.topology.placement.UAVBasePlacement;
import de.tud.kom.p2psim.impl.topology.util.PositionVector; import de.tud.kom.p2psim.impl.topology.util.PositionVector;
import de.tudarmstadt.maki.simonstrator.api.component.ComponentNotAvailableException; import de.tudarmstadt.maki.simonstrator.api.component.ComponentNotAvailableException;
import de.tudarmstadt.maki.simonstrator.api.component.overlay.OverlayComponent; import de.tudarmstadt.maki.simonstrator.api.component.overlay.OverlayComponent;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.battery.BatterySensor;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.AttractionPoint; import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.AttractionPoint;
import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location; import de.tudarmstadt.maki.simonstrator.api.component.sensor.location.Location;
import de.tudarmstadt.maki.simonstrator.api.uavsupport.callbacks.ReachedLocationCallback; import de.tudarmstadt.maki.simonstrator.api.uavsupport.callbacks.ReachedLocationCallback;
...@@ -48,12 +51,17 @@ import de.tudarmstadt.maki.simonstrator.api.uavsupport.callbacks.ReachedLocation ...@@ -48,12 +51,17 @@ import de.tudarmstadt.maki.simonstrator.api.uavsupport.callbacks.ReachedLocation
*/ */
public class UAVTopologyComponent extends AbstractTopologyComponent implements SimUAVLocationActuator { public class UAVTopologyComponent extends AbstractTopologyComponent implements SimUAVLocationActuator {
public enum UAVstate {OFFLINE, ACTION, RETURN, CRASHED}
private UAVMovementModel movement; private UAVMovementModel movement;
private OverlayComponent uavOverlayComponent; private OverlayComponent uavOverlayComponent;
private ActuatorEnergyComponent actuator; private ActuatorEnergyComponent actuator;
public BatterySensor battery; private RechargeableBattery battery;
private UAVstate state = UAVstate.OFFLINE;
private PositionVector baseLocation;
/** /**
* Create a TopologyComponent for the current host. * Create a TopologyComponent for the current host.
...@@ -64,7 +72,7 @@ public class UAVTopologyComponent extends AbstractTopologyComponent implements S ...@@ -64,7 +72,7 @@ public class UAVTopologyComponent extends AbstractTopologyComponent implements S
*/ */
public UAVTopologyComponent(SimHost host, Topology topology, public UAVTopologyComponent(SimHost host, Topology topology,
MovementModel movementModel, PlacementModel placementModel, boolean registerAsInformationProviderInSiS) { MovementModel movementModel, PlacementModel placementModel, boolean registerAsInformationProviderInSiS) {
super(host, topology, movementModel, placementModel, registerAsInformationProviderInSiS); super(host, topology, movementModel, placementModel, registerAsInformationProviderInSiS);
} }
@Override @Override
...@@ -79,11 +87,17 @@ public class UAVTopologyComponent extends AbstractTopologyComponent implements S ...@@ -79,11 +87,17 @@ public class UAVTopologyComponent extends AbstractTopologyComponent implements S
} }
try { try {
battery = getHost().getComponent(BatterySensor.class); battery = (RechargeableBattery) getHost().getComponent(AbstractEnergyModel.class).getBattery();
} catch (ComponentNotAvailableException e) { } catch (ComponentNotAvailableException e) {
System.err.println("No Battery Component was found!"); System.err.println("No Battery Component was found!");
} }
baseLocation = position.clone();
}
public void setState(UAVstate newState) {
this.state = newState;
} }
public void setUAVComponent(OverlayComponent uavOverlayComponent) { public void setUAVComponent(OverlayComponent uavOverlayComponent) {
...@@ -120,14 +134,36 @@ public class UAVTopologyComponent extends AbstractTopologyComponent implements S ...@@ -120,14 +134,36 @@ public class UAVTopologyComponent extends AbstractTopologyComponent implements S
} }
@Override @Override
public boolean activate() { public boolean activate() {
return actuator.turnOn(); if(actuator.turnOn()) {
state = UAVstate.ACTION;
return true;
}
else {
return false;
}
} }
@Override @Override
public boolean deactivate() { public boolean deactivate() {
// TODO Auto-generated method stub actuator.turnOff();
return false;
if(this.position.getAltitude() != 0) {
state = UAVstate.CRASHED;
System.err.println("UAV was destroyed due to actuator deactivation during flight");
uavOverlayComponent.shutdown();
for (SimNetInterface net : getHost().getNetworkComponent()
.getSimNetworkInterfaces()) {
net.goOffline();
}
}
else {
state = UAVstate.OFFLINE;
}
return true;
} }
@Override @Override
...@@ -209,13 +245,33 @@ public class UAVTopologyComponent extends AbstractTopologyComponent implements S ...@@ -209,13 +245,33 @@ public class UAVTopologyComponent extends AbstractTopologyComponent implements S
return movement.getTargetLocations(); return movement.getTargetLocations();
} }
public UAVstate getUAVState() {
return state;
}
@Override
public void returnToBase(ReachedLocationCallback cb) {
this.state = UAVstate.RETURN;
ReachedLocationCallback returnCallback = new ReachedLocationCallback() {
@Override
public void reachedLocation() {
cb.reachedLocation();
deactivate();
connectToBaseCharger();
}
};
movement.setTargetLocation(baseLocation, returnCallback);
}
private void connectToBaseCharger() {
BaseTopologyComponent base = UAVBasePlacement.base;
}
} }
...@@ -82,7 +82,7 @@ public class SimpleMutlicopterMovement implements UAVMovementModel { ...@@ -82,7 +82,7 @@ public class SimpleMutlicopterMovement implements UAVMovementModel {
@Override @Override
public void move(long timeBetweenMovementOperations) { public void move(long timeBetweenMovementOperations) {
if(!route.isEmpty() && topologyComponent.isActive() ) { if(topologyComponent.isActive() && !route.isEmpty()) {
PositionVector currentPosition= topologyComponent.getRealPosition(); PositionVector currentPosition= topologyComponent.getRealPosition();
PositionVector targetPosition = route.getFirst(); PositionVector targetPosition = route.getFirst();
...@@ -90,11 +90,12 @@ public class SimpleMutlicopterMovement implements UAVMovementModel { ...@@ -90,11 +90,12 @@ public class SimpleMutlicopterMovement implements UAVMovementModel {
// If target point is reached within a 1 meter margin, we remove that point from the list // If target point is reached within a 1 meter margin, we remove that point from the list
if(distanceToTargetPosition < 0.1 || distanceToTargetPosition < currentSpeed) if(distanceToTargetPosition < 0.1 || distanceToTargetPosition < currentSpeed)
{ {
locationReached(targetPosition); route.removeFirst();
route.add(route.removeFirst()); topologyComponent.updateCurrentLocation(targetPosition, 0.5); // triggers energy consumption for last distance
topologyComponent.updateCurrentLocation(targetPosition, 0);
currentSpeed = 0; currentSpeed = 0;
topologyComponent.getActuatorEnergyComponent().useActuator(0); // now hover
locationReached(targetPosition);
return; return;
} }
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment