Code development platform for open source projects from the European Union institutions

Skip to content
Snippets Groups Projects
Commit fe149878 authored by Markus Quaritsch's avatar Markus Quaritsch
Browse files

add powerdemand of main components in response object

add ClutchInfo Interface
add Breaks (also to PowertrainBuilder)

Vehicle stopping and drive off again works!
parent 0a7a36f1
No related branches found
No related tags found
No related merge requests found
Showing
with 167 additions and 43 deletions
......@@ -23,5 +23,17 @@ namespace TUGraz.VectoCore.Models.Connector.Ports
Second SimulationInterval { get; set; }
ResponseType ResponseType { get; }
Watt EnginePowerRequest { get; set; }
Watt ClutchPowerRequest { get; set; }
Watt GearboxPowerRequest { get; set; }
Watt AxlegearPowerRequest { get; set; }
Watt WheelsPowerRequest { get; set; }
Watt VehiclePowerRequest { get; set; }
}
}
\ No newline at end of file
......@@ -8,6 +8,18 @@ namespace TUGraz.VectoCore.Models.Connector.Ports.Impl
public Second SimulationInterval { get; set; }
public abstract ResponseType ResponseType { get; }
public Watt EnginePowerRequest { get; set; }
public Watt ClutchPowerRequest { get; set; }
public Watt GearboxPowerRequest { get; set; }
public Watt AxlegearPowerRequest { get; set; }
public Watt WheelsPowerRequest { get; set; }
public Watt VehiclePowerRequest { get; set; }
}
/// <summary>
......@@ -71,8 +83,8 @@ namespace TUGraz.VectoCore.Models.Connector.Ports.Impl
internal class ResponseDryRun : AbstractResponse
{
public Watt DeltaFullLoad { get; set; }
public Watt DeltaDragLoad { get; set; }
public Watt EngineDeltaFullLoad { get; set; }
public Watt EngineDeltaDragLoad { get; set; }
public override ResponseType ResponseType
{
......
using System.Security.Cryptography.X509Certificates;
using TUGraz.VectoCore.Models.SimulationComponent;
namespace TUGraz.VectoCore.Models.Simulation.DataBus
{
public interface IClutchInfo
{
ClutchState ClutchState();
}
}
\ No newline at end of file
......@@ -5,5 +5,6 @@ namespace TUGraz.VectoCore.Models.Simulation.DataBus
/// <summary>
/// Defines interfaces for all different cockpits to access shared data of the powertrain.
/// </summary>
public interface IDataBus : IGearboxInfo, IEngineInfo, IVehicleInfo, IMileageCounter, IBreaks, IRoadLookAhead {}
public interface IDataBus : IGearboxInfo, IEngineInfo, IVehicleInfo, IMileageCounter, IClutchInfo, IBreaks,
IRoadLookAhead {}
}
\ No newline at end of file
......@@ -33,6 +33,10 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
requestDone = true;
break;
case ResponseType.DrivingCycleDistanceExceeded:
var distanceResponse = response as ResponseDrivingCycleDistanceExceeded;
if (distanceResponse != null) {
ds = distanceResponse.MaxDistance;
}
break;
}
} while (!requestDone);
......
......@@ -46,7 +46,8 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
var driver = AddComponent(cycle, new Driver(_container, data.DriverData));
var vehicle = AddComponent(driver, new Vehicle(_container, data.VehicleData));
var wheels = AddComponent(vehicle, new Wheels(_container, data.VehicleData.DynamicTyreRadius));
var tmp = AddComponent(wheels, new AxleGear(_container, data.GearboxData.AxleGearData));
var breaks = AddComponent(wheels, new Breaks(_container));
var tmp = AddComponent(breaks, new AxleGear(_container, data.GearboxData.AxleGearData));
switch (data.VehicleData.Retarder.Type) {
case RetarderData.RetarderType.Primary:
......
......@@ -22,6 +22,8 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
internal IMileageCounter MilageCounter;
internal IClutchInfo Clutch;
internal IRoadLookAhead Road;
internal ISimulationOutPort Cycle;
......@@ -131,6 +133,11 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
if (road != null) {
Road = road;
}
var clutch = component as IClutchInfo;
if (clutch != null) {
Clutch = clutch;
}
}
......@@ -183,5 +190,10 @@ namespace TUGraz.VectoCore.Models.Simulation.Impl
get { return Breaks.BreakPower; }
set { Breaks.BreakPower = value; }
}
public ClutchState ClutchState()
{
return Clutch.ClutchState();
}
}
}
\ No newline at end of file
namespace TUGraz.VectoCore.Models.SimulationComponent
{
public enum ClutchState
{
ClutchClosed,
ClutchOpened,
ClutchSlipping
}
public interface IClutch : IPowerTrainComponent {}
}
\ No newline at end of file
......@@ -35,9 +35,11 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
public IResponse Request(Second absTime, Second dt, NewtonMeter torque, PerSecond angularVelocity, bool dryRun = false)
{
Log.DebugFormat("request: torque: {0}, angularVelocity: {1}", torque, angularVelocity);
return _nextComponent.Request(absTime, dt,
var retVal = _nextComponent.Request(absTime, dt,
_gearData.LossMap.GearboxInTorque(angularVelocity * _gearData.Ratio, torque),
angularVelocity * _gearData.Ratio, dryRun);
retVal.AxlegearPowerRequest = Formulas.TorqueToPower(torque, angularVelocity);
return retVal;
}
public IResponse Initialize(NewtonMeter torque, PerSecond angularVelocity)
......
......@@ -29,7 +29,14 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
public IResponse Request(Second absTime, Second dt, NewtonMeter torque, PerSecond angularVelocity, bool dryRun = false)
{
BreakTorque = Formulas.PowerToTorque(BreakPower, angularVelocity);
if (!BreakPower.IsEqual(0)) {
if (angularVelocity.IsEqual(0)) {
BreakTorque = -torque;
} else {
BreakTorque = Formulas.PowerToTorque(BreakPower, angularVelocity);
}
}
return Next.Request(absTime, dt, torque - BreakTorque, angularVelocity, dryRun);
}
......
......@@ -3,25 +3,20 @@ using TUGraz.VectoCore.Configuration;
using TUGraz.VectoCore.Models.Connector.Ports;
using TUGraz.VectoCore.Models.Simulation;
using TUGraz.VectoCore.Models.Simulation.Data;
using TUGraz.VectoCore.Models.Simulation.DataBus;
using TUGraz.VectoCore.Models.SimulationComponent.Data;
using TUGraz.VectoCore.Utils;
namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
{
public class Clutch : VectoSimulationComponent, IClutch, ITnOutPort, ITnInPort
public class Clutch : VectoSimulationComponent, IClutch, IClutchInfo, ITnOutPort, ITnInPort
{
private readonly PerSecond _idleSpeed;
private readonly PerSecond _ratedSpeed;
private ITnOutPort _nextComponent;
private const double ClutchEff = 1;
private ClutchState _clutchState = ClutchState.ClutchClosed;
private ClutchState _clutchState = SimulationComponent.ClutchState.ClutchOpened;
public enum ClutchState
{
ClutchClosed,
ClutchOpened,
ClutchSlipping
}
public Clutch(IVehicleContainer cockpit, CombustionEngineData engineData)
: base(cockpit)
......@@ -64,7 +59,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
PerSecond engineSpeedIn;
AddClutchLoss(torque, angularVelocity, out torqueIn, out engineSpeedIn);
return _nextComponent.Request(absTime, dt, torqueIn, engineSpeedIn, dryRun);
var retVal = _nextComponent.Request(absTime, dt, torqueIn, engineSpeedIn, dryRun);
retVal.ClutchPowerRequest = Formulas.TorqueToPower(torque, angularVelocity);
return retVal;
}
public IResponse Initialize(NewtonMeter torque, PerSecond angularVelocity)
......@@ -73,7 +70,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
PerSecond engineSpeedIn;
AddClutchLoss(torque, angularVelocity, out torqueIn, out engineSpeedIn);
return _nextComponent.Initialize(torqueIn, engineSpeedIn);
var retVal = _nextComponent.Initialize(torqueIn, engineSpeedIn);
return retVal;
}
public void Connect(ITnOutPort other)
......@@ -90,14 +88,14 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
engineSpeedIn = angularVelocity;
if (DataBus.Gear() == 0) {
_clutchState = ClutchState.ClutchOpened;
_clutchState = SimulationComponent.ClutchState.ClutchOpened;
engineSpeedIn = _idleSpeed;
torqueIn = 0.SI<NewtonMeter>();
} else {
var engineSpeedNorm = (angularVelocity - _idleSpeed) /
(_ratedSpeed - _idleSpeed);
if (engineSpeedNorm < Constants.SimulationSettings.CluchNormSpeed) {
_clutchState = ClutchState.ClutchSlipping;
_clutchState = SimulationComponent.ClutchState.ClutchSlipping;
var engineSpeed0 = VectoMath.Max(_idleSpeed, angularVelocity);
var clutchSpeedNorm = Constants.SimulationSettings.CluchNormSpeed /
......@@ -108,11 +106,16 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
torqueIn = Formulas.PowerToTorque(Formulas.TorqueToPower(torque, angularVelocity) / ClutchEff, engineSpeedIn);
} else {
_clutchState = ClutchState.ClutchClosed;
_clutchState = SimulationComponent.ClutchState.ClutchClosed;
}
}
Log.DebugFormat("to Engine: torque: {0}, angularVelocity: {1}, power {2}", torqueIn, engineSpeedIn,
Formulas.TorqueToPower(torqueIn, engineSpeedIn));
}
public ClutchState ClutchState()
{
return _clutchState;
}
}
}
\ No newline at end of file
......@@ -34,6 +34,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
protected const double ZeroThreshold = 0.0001;
protected const double FullLoadMargin = 0.01;
protected readonly Watt StationaryIdleFullLoadPower;
/// <summary>
/// Current state is computed in request method
/// </summary>
......@@ -51,6 +53,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
_previousState.EnginePower = 0.SI<Watt>();
_previousState.EngineSpeed = _data.IdleSpeed;
_previousState.dt = 1.SI<Second>();
StationaryIdleFullLoadPower = Formulas.TorqueToPower(_data.FullLoadCurve.FullLoadStationaryTorque(_data.IdleSpeed),
_data.IdleSpeed);
}
#region IEngineCockpit
......@@ -93,13 +98,17 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
if (dryRun) {
return new ResponseDryRun() {
DeltaFullLoad = (requestedEnginePower - _currentState.DynamicFullLoadPower),
DeltaDragLoad = (requestedEnginePower - _currentState.FullDragPower)
EngineDeltaFullLoad = (requestedEnginePower - _currentState.DynamicFullLoadPower),
EngineDeltaDragLoad = (requestedEnginePower - _currentState.FullDragPower),
EnginePowerRequest = requestedEnginePower
};
}
if (!_currentState.EnginePower.IsEqual(requestedEnginePower, Constants.SimulationSettings.EngineFLDPowerTolerance)) {
return new ResponseFailOverload() { Delta = (requestedEnginePower - _currentState.EnginePower) };
return new ResponseFailOverload() {
Delta = (requestedEnginePower - _currentState.EnginePower),
EnginePowerRequest = requestedEnginePower
};
}
UpdateEngineState(_currentState.EnginePower);
......@@ -108,7 +117,7 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
_currentState.EngineTorque = Formulas.PowerToTorque(_currentState.EnginePower,
_currentState.EngineSpeed);
return new ResponseSuccess();
return new ResponseSuccess() { EnginePowerRequest = requestedEnginePower };
}
protected void ComputeRequestedEnginePower(Second absTime, Second dt, NewtonMeter torque, PerSecond engineSpeed,
......@@ -272,6 +281,10 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
? dynFullPowerCalculated
: _currentState.StationaryFullLoadPower;
if (_currentState.DynamicFullLoadPower < StationaryIdleFullLoadPower) {
_currentState.DynamicFullLoadPower = StationaryIdleFullLoadPower;
}
_currentState.DynamicFullLoadTorque = Formulas.PowerToTorque(_currentState.DynamicFullLoadPower,
angularVelocity);
}
......
......@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.Cache;
using TUGraz.VectoCore.Configuration;
using TUGraz.VectoCore.Exceptions;
using TUGraz.VectoCore.Models.Connector.Ports;
using TUGraz.VectoCore.Models.Connector.Ports.Impl;
......@@ -118,6 +119,12 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
private IResponse DriveDistance(Second absTime, Meter ds)
{
if (!CurrentState.RequestToNextSamplePointDone &&
(CycleIntervalIterator.RightSample.Distance - PreviousState.Distance) <
Constants.SimulationSettings.DriveOffDistance) {
CurrentState.RequestToNextSamplePointDone = true;
return new ResponseDrivingCycleDistanceExceeded() { MaxDistance = Constants.SimulationSettings.DriveOffDistance };
}
CurrentState.Distance = PreviousState.Distance + ds;
CurrentState.VehicleTargetSpeed = CycleIntervalIterator.LeftSample.VehicleTargetSpeed;
CurrentState.Gradient = ComputeGradient();
......@@ -191,7 +198,10 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
// we needed to stop at the current interval in the cycle and have already waited enough time, move on..
CycleIntervalIterator.MoveNext();
}
if (CurrentState.Distance >= CycleIntervalIterator.RightSample.Distance) {
// separately test for equality and greater than to have tolerance for equality comparison
if (CurrentState.Distance.IsEqual(CycleIntervalIterator.RightSample.Distance) ||
CurrentState.Distance > CycleIntervalIterator.RightSample.Distance) {
// we have reached the end of the current interval in the cycle, move on...
CycleIntervalIterator.MoveNext();
}
......@@ -316,6 +326,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
public Radian Gradient;
public IResponse Response;
public bool RequestToNextSamplePointDone = false;
}
}
}
\ No newline at end of file
......@@ -135,8 +135,12 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
nextDrivingAction.Current.Key, nextDrivingAction.Current.Key - currentDistance, currentDistance);
return new ResponseDrivingCycleDistanceExceeded() { MaxDistance = nextDrivingAction.Current.Key - currentDistance };
}
} else {
if (targetVelocity > DataBus.VehicleSpeed()) {
CurrentState.DrivingAction.Action = DrivingBehavior.Accelerating;
}
}
Log.DebugFormat("DrivingAction: {0}", CurrentState.DrivingAction);
Log.DebugFormat("DrivingAction: {0}", CurrentState.DrivingAction.Action);
//CurrentState.DrivingAction = nextAction;
switch (CurrentState.DrivingAction.Action) {
case DrivingBehavior.Accelerating:
......@@ -167,9 +171,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
throw new VectoSimulationException("Expected DryRunResponse after Dry-Run Request!");
}
if (dryRun.DeltaDragLoad > 0) {
throw new VectoSimulationException("No breaking necessary, still above full drag load!");
}
//if (dryRun.EngineDeltaDragLoad > 0) {
// throw new VectoSimulationException("No breaking necessary, still above full drag load!");
//}
var newDs = ds;
var success = SearchBreakingPower(absTime, ref newDs, gradient, dryRun, true);
......@@ -202,14 +206,18 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
private bool SearchBreakingPower(Second absTime, ref Meter ds, Radian gradient, ResponseDryRun response, bool coasting)
{
var exceeded = new List<Watt>(); // only used while testing
var searchInterval = VectoMath.Abs(response.DeltaDragLoad);
var breakingPower = VectoMath.Abs(response.DeltaDragLoad);
var breakingPower = VectoMath.Abs(response.EngineDeltaDragLoad);
if (DataBus.ClutchState() != ClutchState.ClutchClosed) {
breakingPower = VectoMath.Abs(response.AxlegearPowerRequest);
}
var searchInterval = breakingPower;
var originalDs = ds;
do {
ds = originalDs;
var delta = -response.DeltaDragLoad;
var delta = DataBus.ClutchState() == ClutchState.ClutchClosed
? -response.EngineDeltaDragLoad
: -response.AxlegearPowerRequest;
exceeded.Add(delta);
if (delta.IsEqual(0, Constants.SimulationSettings.EngineFLDPowerTolerance)) {
......@@ -380,7 +388,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
delta = ((ResponseFailOverload)response).Delta;
break;
case ResponseType.DryRun:
delta = coasting ? -((ResponseDryRun)response).DeltaDragLoad : ((ResponseDryRun)response).DeltaFullLoad;
delta = coasting
? -((ResponseDryRun)response).EngineDeltaDragLoad
: ((ResponseDryRun)response).EngineDeltaFullLoad;
break;
default:
throw new VectoSimulationException("Unknown response type");
......@@ -533,6 +543,17 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
}
CurrentState.RetryCount = 0;
CurrentState.Response = null;
if (CurrentState.DrivingAction.NextTargetSpeed != null && DataBus.VehicleSpeed().IsEqual(CurrentState.DrivingAction.NextTargetSpeed))
{
Log.DebugFormat("reached target Speed {0} - set Driving action to {1}", CurrentState.DrivingAction.NextTargetSpeed, DrivingBehavior.Drive);
CurrentState.DrivingAction.Action = DrivingBehavior.Drive;
}
if (DataBus.VehicleSpeed().IsEqual(0))
{
Log.DebugFormat("vehicle stopped {0} - set Driving action to {1}", DataBus.VehicleSpeed(), DrivingBehavior.Stopped);
CurrentState.DrivingAction.Action = DrivingBehavior.Stopped;
}
}
......
......@@ -31,8 +31,8 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
if (dryRun) {
return new ResponseDryRun() {
DeltaFullLoad = (requestedEnginePower - _currentState.DynamicFullLoadPower),
DeltaDragLoad = (requestedEnginePower - _currentState.FullDragPower)
EngineDeltaFullLoad = (requestedEnginePower - _currentState.DynamicFullLoadPower),
EngineDeltaDragLoad = (requestedEnginePower - _currentState.FullDragPower)
};
}
......
......@@ -74,7 +74,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
AirDragResistance() +
SlopeResistance(gradient);
return _nextInstance.Request(absTime, dt, vehicleAccelerationForce, _currentState.Velocity, dryRun);
var retval = _nextInstance.Request(absTime, dt, vehicleAccelerationForce, _currentState.Velocity, dryRun);
//retval.VehiclePowerRequest =
return retval;
}
public IResponse Initialize(MeterPerSecond vehicleSpeed, Radian roadGradient)
......
......@@ -42,7 +42,9 @@ namespace TUGraz.VectoCore.Models.SimulationComponent.Impl
Log.DebugFormat("request: force: {0}, velocity: {1}", force, velocity);
var torque = force * _dynamicWheelRadius;
var angularVelocity = velocity / _dynamicWheelRadius;
return _outPort.Request(absTime, dt, torque, angularVelocity, dryRun);
var retVal = _outPort.Request(absTime, dt, torque, angularVelocity, dryRun);
retVal.WheelsPowerRequest = Formulas.TorqueToPower(torque, angularVelocity);
return retVal;
}
public IResponse Initialize(Newton force, MeterPerSecond velocity)
......
......@@ -162,6 +162,7 @@
<Compile Include="Models\SimulationComponent\Impl\Breaks.cs" />
<Compile Include="Models\SimulationComponent\Impl\EngineOnlyCombustionEngine.cs" />
<Compile Include="Models\SimulationComponent\Impl\MappingAuxiliaryData.cs" />
<Compile Include="Models\Simulation\DataBus\IClutchInfo.cs" />
<Compile Include="Models\Simulation\Data\AuxiliaryDemandType.cs" />
<Compile Include="Models\SimulationComponent\Data\AuxiliaryType.cs" />
<Compile Include="Models\SimulationComponent\Data\GearFullLoadCurve.cs" />
......
......@@ -29,6 +29,7 @@ namespace TUGraz.VectoCore.Tests.Integration.SimulationRuns
public const string AccelerationFile = @"TestData\Components\Coach.vacc";
public const string AccelerationFile2 = @"TestData\Components\Truck.vacc";
public const double Tolerance = 0.001;
......@@ -42,7 +43,7 @@ namespace TUGraz.VectoCore.Tests.Integration.SimulationRuns
var axleGearData = CreateAxleGearData();
var driverData = CreateDriverData();
var driverData = CreateDriverData(AccelerationFile);
var modalWriter = new ModalDataWriter("Coach_MinimalPowertrainOverload.vmod", false); //new TestModalDataWriter();
var sumWriter = new TestSumWriter();
......@@ -91,7 +92,7 @@ namespace TUGraz.VectoCore.Tests.Integration.SimulationRuns
var vehicleData = CreateVehicleData(3300.SI<Kilogram>());
var driverData = CreateDriverData();
var driverData = CreateDriverData(AccelerationFile);
var modalWriter = new ModalDataWriter("Coach_MinimalPowertrain.vmod", false); //new TestModalDataWriter();
var sumWriter = new TestSumWriter();
......@@ -161,7 +162,7 @@ namespace TUGraz.VectoCore.Tests.Integration.SimulationRuns
var vehicleData = CreateVehicleData(3300.SI<Kilogram>());
var driverData = CreateDriverData();
var driverData = CreateDriverData(AccelerationFile2);
var modalWriter = new ModalDataWriter("Coach_MinimalPowertrain_Stop.vmod", false); //new TestModalDataWriter();
var sumWriter = new TestSumWriter();
......@@ -172,6 +173,7 @@ namespace TUGraz.VectoCore.Tests.Integration.SimulationRuns
dynamic tmp = AddComponent(cycle, new Driver(vehicleContainer, driverData));
tmp = AddComponent(tmp, new Vehicle(vehicleContainer, vehicleData));
tmp = AddComponent(tmp, new Wheels(vehicleContainer, vehicleData.DynamicTyreRadius));
tmp = AddComponent(tmp, new Breaks(vehicleContainer));
tmp = AddComponent(tmp, new AxleGear(vehicleContainer, axleGearData));
tmp = AddComponent(tmp, new Clutch(vehicleContainer, engineData));
AddComponent(tmp, new CombustionEngine(vehicleContainer, engineData));
......@@ -189,7 +191,7 @@ namespace TUGraz.VectoCore.Tests.Integration.SimulationRuns
gbx.CurrentGear = 1;
IResponse response;
var ds = Constants.SimulationSettings.DriveOffDistance;
while (vehicleContainer.Distance().Value() < 20) {
while (vehicleContainer.Distance().Value() < 100) {
response = cyclePort.Request(absTime, ds);
switch (response.ResponseType) {
......@@ -263,10 +265,10 @@ namespace TUGraz.VectoCore.Tests.Integration.SimulationRuns
};
}
private static DriverData CreateDriverData()
private static DriverData CreateDriverData(string accelerationFile)
{
return new DriverData() {
AccelerationCurve = AccelerationCurveData.ReadFromFile(AccelerationFile),
AccelerationCurve = AccelerationCurveData.ReadFromFile(accelerationFile),
LookAheadCoasting = new DriverData.LACData() {
Enabled = false,
},
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment