Browse Source

Improved battery recharge logic

Added warning if battery capacity is not sufficient
master
Youen Toupin 2 years ago
parent
commit
1b14a62f6e
  1. 4
      simulator/src/app.scss
  2. 5
      simulator/src/simulator-ui.ts
  3. 40
      simulator/src/simulator.ts
  4. 2
      simulator/tools/purify.js

4
simulator/src/app.scss

@ -6,6 +6,10 @@ input[type=number] {
flex-grow: 2.5; flex-grow: 2.5;
} }
.simulation-warning {
color: red;
}
.dropdown.is-fullwidth { .dropdown.is-fullwidth {
display: flex; display: flex;
} }

5
simulator/src/simulator-ui.ts

@ -95,8 +95,9 @@ function initializeSimulator(container: HTMLElement) {
<p>Cela coûtera ${Math.round(totalConsumedGridPower/1000*averageKwhCost*100)/100} sur l'année. Le vhélio sera rechargé à ${solarRechargeRatio}% par le soleil, ${100-solarRechargeRatio}% sur secteur.</p> <p>Cela coûtera ${Math.round(totalConsumedGridPower/1000*averageKwhCost*100)/100} sur l'année. Le vhélio sera rechargé à ${solarRechargeRatio}% par le soleil, ${100-solarRechargeRatio}% sur secteur.</p>
<p><br/></p> <p><br/></p>
<p>Vitesse moyenne : ${Math.round(simulationResult.averageSpeed*10.0)/10.0} km/h (${Math.round(simulationResult.flatTerrainSpeed*10.0)/10.0} km/h sur plat, ${Math.round(simulationResult.uphillSpeed*10.0)/10.0} km/h en côte à ${Math.round((parameters.dailyAscendingElevation/1000.0)/(parameters.dailyDistance*(1.0-parameters.flatTerrainRatio)*0.5)*100.0)}%, ${Math.round(simulationResult.downhillSpeed*10.0)/10.0} km/h en descente)</p> <p>Vitesse moyenne : ${Math.round(simulationResult.averageSpeed*10.0)/10.0} km/h (${Math.round(simulationResult.flatTerrainSpeed*10.0)/10.0} km/h sur plat, ${Math.round(simulationResult.uphillSpeed*10.0)/10.0} km/h en côte à ${Math.round((parameters.dailyAscendingElevation/1000.0)/(parameters.dailyDistance*(1.0-parameters.flatTerrainRatio)*0.5)*100.0)}%, ${Math.round(simulationResult.downhillSpeed*10.0)/10.0} km/h en descente)</p>
<p>Durée de trajet quotidien : ${dailyDurationHours}h ${dailyDurationMinutes}min. Distance annuelle : ${Math.round(simulationResult.cumulatedDistance)} km.</p> <p>Durée de trajet quotidienne : ${dailyDurationHours}h ${dailyDurationMinutes}min. Distance annuelle : ${Math.round(simulationResult.cumulatedDistance)} km.</p>
`; `
+ (simulationResult.outOfBatteryDistance >= 1 ? `<p><br/></p><p class="simulation-warning">/!\\ En raison d'une capacité de batterie insuffisante, il faudra faire ${Math.round(simulationResult.outOfBatteryDistance)}km/an sans assistance électrique</p>` : "");
//<p>${Math.round(100*(simulationResult.cumulatedSolarRechargeEnergy/simulationResult.vehicle.batteryEfficiency) / simulationResult.totalProducedSolarEnergy)}% de l'énergie produite par le panneau photovoltaïque sera utilisée pour recharger le vhélio.</p> //<p>${Math.round(100*(simulationResult.cumulatedSolarRechargeEnergy/simulationResult.vehicle.batteryEfficiency) / simulationResult.totalProducedSolarEnergy)}% de l'énergie produite par le panneau photovoltaïque sera utilisée pour recharger le vhélio.</p>
let batteryChargeGraph = new SvgDrawing.SvgElement(resultsContainer.querySelector('.battery-charge-graph svg')); let batteryChargeGraph = new SvgDrawing.SvgElement(resultsContainer.querySelector('.battery-charge-graph svg'));

40
simulator/src/simulator.ts

@ -9,6 +9,7 @@ namespace Simulator {
batteryCapacity: number; batteryCapacity: number;
batteryEfficiency: number = 0.9; batteryEfficiency: number = 0.9;
gridTransformerEfficiency: number = 0.85; gridTransformerEfficiency: number = 0.85;
rechargeMargin: number = 0.15; // We recharge the battery before an outing if it would otherwise go below this charge ratio
solarPanelEfficiency: number = 0.15; solarPanelEfficiency: number = 0.15;
solarPanelArea: number = 1.0; // in square meters solarPanelArea: number = 1.0; // in square meters
@ -117,6 +118,8 @@ namespace Simulator {
uphillSpeed: number; uphillSpeed: number;
downhillSpeed: number; downhillSpeed: number;
averageSpeed: number; averageSpeed: number;
outOfBatteryDistance: number; // distance, in km, that the driver had to pedal without assistance, because the battery was empty
} }
export function simulate(vehicle: Vehicle, solarIrradiance: number[], planning: OutingPlanning): SimulationResult { export function simulate(vehicle: Vehicle, solarIrradiance: number[], planning: OutingPlanning): SimulationResult {
@ -136,7 +139,9 @@ namespace Simulator {
flatTerrainSpeed: 0, flatTerrainSpeed: 0,
uphillSpeed: 0, uphillSpeed: 0,
downhillSpeed: 0, downhillSpeed: 0,
averageSpeed: 0 averageSpeed: 0,
outOfBatteryDistance: 0
}; };
let remainingBatteryCharge = vehicle.batteryCapacity; let remainingBatteryCharge = vehicle.batteryCapacity;
@ -144,7 +149,7 @@ namespace Simulator {
let outing: Outing = { distance: 0, ascendingElevation: 0 }; let outing: Outing = { distance: 0, ascendingElevation: 0 };
let consumption: ConsumptionData = { motorEnergy: 0, humanEnergy: 0, averageSpeed: 0 }; let consumption: ConsumptionData = { motorEnergy: 0, humanEnergy: 0, averageSpeed: 0 };
let resetConsumption = function(outConsumption: ConsumptionData) { let resetConsumption = function(outConsumption: ConsumptionData) {
consumption.motorEnergy = 0; consumption.humanEnergy = 0; consumption.averageSpeed = 0; outConsumption.motorEnergy = 0; outConsumption.humanEnergy = 0; outConsumption.averageSpeed = 0;
}; };
let flatTerrainRatio = MathUtils.clamp(planning.flatTerrainRatio, 0.0, 1.0); let flatTerrainRatio = MathUtils.clamp(planning.flatTerrainRatio, 0.0, 1.0);
@ -188,27 +193,44 @@ namespace Simulator {
result.totalProducedSolarEnergy += production; result.totalProducedSolarEnergy += production;
let solarCharge = production * vehicle.batteryEfficiency; let solarCharge = production * vehicle.batteryEfficiency;
// TODO: we should keep a margin because real users will recharge before they reach the bare minimum required for an outing let remainingBatteryChargeBeforeOuting = remainingBatteryCharge;
remainingBatteryCharge += solarCharge - consumption.motorEnergy; remainingBatteryCharge += solarCharge - consumption.motorEnergy;
let fullGridRecharge = false; let gridRechargeFrom = -1;
let lowBatteryThreshold = vehicle.rechargeMargin * vehicle.batteryCapacity;
if(remainingBatteryCharge > vehicle.batteryCapacity) { if(remainingBatteryCharge > vehicle.batteryCapacity) {
solarCharge -= remainingBatteryCharge - vehicle.batteryCapacity; solarCharge -= remainingBatteryCharge - vehicle.batteryCapacity;
remainingBatteryCharge = vehicle.batteryCapacity; remainingBatteryCharge = vehicle.batteryCapacity;
} }
else if(remainingBatteryCharge <= 0 || (day==364 && hour==23)) { else if(remainingBatteryCharge <= lowBatteryThreshold || (day==364 && hour==23)) {
// TODO: detect if battery capacity is too low for a single outing, abort simulation and display an explanation for the user gridRechargeFrom = remainingBatteryChargeBeforeOuting;
fullGridRecharge = remainingBatteryCharge <= 0;
let rechargeEnergy = vehicle.batteryCapacity - remainingBatteryCharge; let rechargeEnergy = vehicle.batteryCapacity - remainingBatteryChargeBeforeOuting;
remainingBatteryCharge += rechargeEnergy; remainingBatteryCharge += rechargeEnergy;
result.cumulatedGridRechargeEnergy += rechargeEnergy; result.cumulatedGridRechargeEnergy += rechargeEnergy;
result.gridChargeCount += 1; result.gridChargeCount += 1;
if(remainingBatteryCharge < 0)
{
// battery was exhausted during outing
let missingEnergy = -remainingBatteryCharge;
result.outOfBatteryDistance += outing.distance * missingEnergy / consumption.motorEnergy;
consumption.motorEnergy -= missingEnergy;
consumption.humanEnergy += missingEnergy;
// charge battery again after outing
let secondRechargeEnergy = vehicle.batteryCapacity;
remainingBatteryCharge = vehicle.batteryCapacity;
result.cumulatedGridRechargeEnergy += secondRechargeEnergy;
result.gridChargeCount += 1;
gridRechargeFrom = 0;
}
} }
result.cumulatedMotorConsumption += consumption.motorEnergy; result.cumulatedMotorConsumption += consumption.motorEnergy;
result.cumulatedSolarRechargeEnergy += solarCharge; result.cumulatedSolarRechargeEnergy += solarCharge;
result.batteryLevel[hourIdx] = fullGridRecharge ? 0 : remainingBatteryCharge; result.batteryLevel[hourIdx] = gridRechargeFrom >= 0 ? gridRechargeFrom : remainingBatteryCharge;
} }
} }

2
simulator/tools/purify.js

@ -8,7 +8,7 @@ let css = ['../.intermediate/simulator.css'];
let options = { let options = {
output: '../.intermediate/simulator.css', output: '../.intermediate/simulator.css',
whitelist: ['is-multiple', 'is-loading', 'is-narrow', 'is-active', 'climate-zone', 'grid-recharge', 'is-max-desktop', 'is-max-widescreen', 'line', 'page'], whitelist: ['is-multiple', 'is-loading', 'is-narrow', 'is-active', 'climate-zone', 'grid-recharge', 'is-max-desktop', 'is-max-widescreen', 'line', 'page', 'simulation-warning'],
minify: false, minify: false,
info: false info: false
}; };

Loading…
Cancel
Save