From 1b14a62f6e568fe7197662fe589dd70dc6ec4e17 Mon Sep 17 00:00:00 2001 From: Youen Toupin Date: Sun, 30 Jan 2022 21:48:45 +0100 Subject: [PATCH] Improved battery recharge logic Added warning if battery capacity is not sufficient --- simulator/src/app.scss | 4 ++++ simulator/src/simulator-ui.ts | 5 +++-- simulator/src/simulator.ts | 40 +++++++++++++++++++++++++++-------- simulator/tools/purify.js | 2 +- 4 files changed, 39 insertions(+), 12 deletions(-) diff --git a/simulator/src/app.scss b/simulator/src/app.scss index 87f24b6..bf36e99 100644 --- a/simulator/src/app.scss +++ b/simulator/src/app.scss @@ -6,6 +6,10 @@ input[type=number] { flex-grow: 2.5; } +.simulation-warning { + color: red; +} + .dropdown.is-fullwidth { display: flex; } diff --git a/simulator/src/simulator-ui.ts b/simulator/src/simulator-ui.ts index e9c265c..0f99360 100644 --- a/simulator/src/simulator-ui.ts +++ b/simulator/src/simulator-ui.ts @@ -95,8 +95,9 @@ function initializeSimulator(container: HTMLElement) {

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.


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)

-

Durée de trajet quotidien : ${dailyDurationHours}h ${dailyDurationMinutes}min. Distance annuelle : ${Math.round(simulationResult.cumulatedDistance)} km.

- `; +

Durée de trajet quotidienne : ${dailyDurationHours}h ${dailyDurationMinutes}min. Distance annuelle : ${Math.round(simulationResult.cumulatedDistance)} km.

+ ` + + (simulationResult.outOfBatteryDistance >= 1 ? `


/!\\ En raison d'une capacité de batterie insuffisante, il faudra faire ${Math.round(simulationResult.outOfBatteryDistance)}km/an sans assistance électrique

` : ""); //

${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.

let batteryChargeGraph = new SvgDrawing.SvgElement(resultsContainer.querySelector('.battery-charge-graph svg')); diff --git a/simulator/src/simulator.ts b/simulator/src/simulator.ts index 1dc16b3..253979f 100644 --- a/simulator/src/simulator.ts +++ b/simulator/src/simulator.ts @@ -9,6 +9,7 @@ namespace Simulator { batteryCapacity: number; batteryEfficiency: number = 0.9; 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; solarPanelArea: number = 1.0; // in square meters @@ -117,6 +118,8 @@ namespace Simulator { uphillSpeed: number; downhillSpeed: 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 { @@ -136,7 +139,9 @@ namespace Simulator { flatTerrainSpeed: 0, uphillSpeed: 0, downhillSpeed: 0, - averageSpeed: 0 + averageSpeed: 0, + + outOfBatteryDistance: 0 }; let remainingBatteryCharge = vehicle.batteryCapacity; @@ -144,7 +149,7 @@ namespace Simulator { let outing: Outing = { distance: 0, ascendingElevation: 0 }; let consumption: ConsumptionData = { motorEnergy: 0, humanEnergy: 0, averageSpeed: 0 }; 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); @@ -188,27 +193,44 @@ namespace Simulator { result.totalProducedSolarEnergy += production; 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; - let fullGridRecharge = false; + let gridRechargeFrom = -1; + let lowBatteryThreshold = vehicle.rechargeMargin * vehicle.batteryCapacity; if(remainingBatteryCharge > vehicle.batteryCapacity) { solarCharge -= remainingBatteryCharge - vehicle.batteryCapacity; remainingBatteryCharge = vehicle.batteryCapacity; } - else if(remainingBatteryCharge <= 0 || (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 - fullGridRecharge = remainingBatteryCharge <= 0; - let rechargeEnergy = vehicle.batteryCapacity - remainingBatteryCharge; + else if(remainingBatteryCharge <= lowBatteryThreshold || (day==364 && hour==23)) { + gridRechargeFrom = remainingBatteryChargeBeforeOuting; + + let rechargeEnergy = vehicle.batteryCapacity - remainingBatteryChargeBeforeOuting; remainingBatteryCharge += rechargeEnergy; result.cumulatedGridRechargeEnergy += rechargeEnergy; 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.cumulatedSolarRechargeEnergy += solarCharge; - result.batteryLevel[hourIdx] = fullGridRecharge ? 0 : remainingBatteryCharge; + result.batteryLevel[hourIdx] = gridRechargeFrom >= 0 ? gridRechargeFrom : remainingBatteryCharge; } } diff --git a/simulator/tools/purify.js b/simulator/tools/purify.js index 114cbdc..a016317 100644 --- a/simulator/tools/purify.js +++ b/simulator/tools/purify.js @@ -8,7 +8,7 @@ let css = ['../.intermediate/simulator.css']; let options = { 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, info: false };