LCOV - code coverage report
Current view: top level - src/microsim/devices - MSDevice_StationFinder.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 73.9 % 498 368
Test Date: 2026-09-05 15:36:08 Functions: 81.1 % 37 30

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2001-2026 German Aerospace Center (DLR) and others.
       4              : // This program and the accompanying materials are made available under the
       5              : // terms of the Eclipse Public License 2.0 which is available at
       6              : // https://www.eclipse.org/legal/epl-2.0/
       7              : // This Source Code may also be made available under the following Secondary
       8              : // Licenses when the conditions for such availability set forth in the Eclipse
       9              : // Public License 2.0 are satisfied: GNU General Public License, version 2
      10              : // or later which is available at
      11              : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
      12              : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
      13              : /****************************************************************************/
      14              : /// @file    MSDevice_StationFinder.cpp
      15              : /// @author  Michael Behrisch
      16              : /// @author  Mirko Barthauer
      17              : /// @date    2023-05-24
      18              : ///
      19              : // A device which triggers rerouting to nearby charging stations
      20              : /****************************************************************************/
      21              : #include <config.h>
      22              : 
      23              : #include <microsim/MSEdge.h>
      24              : #include <microsim/MSEdgeWeightsStorage.h>
      25              : #include <microsim/MSEventControl.h>
      26              : #include <microsim/MSNet.h>
      27              : #include <microsim/MSParkingArea.h>
      28              : #include <microsim/MSStop.h>
      29              : #include <microsim/MSVehicleControl.h>
      30              : #include <microsim/MSVehicleTransfer.h>
      31              : #include <microsim/trigger/MSChargingStation.h>
      32              : #include <microsim/output/MSDetectorControl.h>
      33              : #include <utils/common/ParametrisedWrappingCommand.h>
      34              : #include <utils/options/OptionsCont.h>
      35              : #include <utils/emissions/PollutantsInterface.h>
      36              : #include <utils/emissions/HelpersEnergy.h>
      37              : #include <utils/iodevices/OutputDevice.h>
      38              : #include <utils/xml/SUMOSAXAttributes.h>
      39              : #include "MSRoutingEngine.h"
      40              : #include "MSDevice_Battery.h"
      41              : #include "MSDevice_StationFinder.h"
      42              : 
      43              : //#define DEBUG_STATIONFINDER_RESCUE
      44              : //#define DEBUG_STATIONFINDER_REROUTE
      45              : #define DEBUG_COND (myHolder.isSelected())
      46              : 
      47              : 
      48              : // ===========================================================================
      49              : // static variables
      50              : // ===========================================================================
      51              : 
      52              : 
      53              : // ===========================================================================
      54              : // method definitions
      55              : // ===========================================================================
      56              : // ---------------------------------------------------------------------------
      57              : // static initialisation methods
      58              : // ---------------------------------------------------------------------------
      59              : void
      60        45763 : MSDevice_StationFinder::insertOptions(OptionsCont& oc) {
      61        91526 :     insertDefaultAssignmentOptions("stationfinder", "Battery", oc);
      62        91526 :     oc.doRegister("device.stationfinder.rescueTime", new Option_String("1800", "TIME"));
      63        91526 :     oc.addDescription("device.stationfinder.rescueTime", "Battery", TL("Time to wait for a rescue vehicle on the road side when the battery is empty"));
      64        91526 :     oc.doRegister("device.stationfinder.rescueAction", new Option_String("remove"));
      65        91526 :     oc.addDescription("device.stationfinder.rescueAction", "Battery", TL("How to deal with a vehicle which has to stop due to low battery: [none, remove, tow]"));
      66        45763 :     oc.doRegister("device.stationfinder.reserveFactor", new Option_Float(1.1));
      67        91526 :     oc.addDescription("device.stationfinder.reserveFactor", "Battery", TL("Scale battery need with this factor to account for unexpected traffic situations"));
      68        45763 :     oc.doRegister("device.stationfinder.emptyThreshold", new Option_Float(0.05));
      69        91526 :     oc.addDescription("device.stationfinder.emptyThreshold", "Battery", TL("Battery percentage to go into rescue mode"));
      70        91526 :     oc.doRegister("device.stationfinder.radius", new Option_String("180", "TIME"));
      71        91526 :     oc.addDescription("device.stationfinder.radius", "Battery", TL("Search radius in travel time seconds"));
      72        45763 :     oc.doRegister("device.stationfinder.maxEuclideanDistance", new Option_Float(-1));
      73        91526 :     oc.addDescription("device.stationfinder.maxEuclideanDistance", "Battery", TL("Euclidean search distance in meters (a negative value disables the restriction)"));
      74        91526 :     oc.doRegister("device.stationfinder.repeat", new Option_String("60", "TIME"));
      75        91526 :     oc.addDescription("device.stationfinder.repeat", "Battery", TL("When to trigger a new search if no station has been found"));
      76        91526 :     oc.doRegister("device.stationfinder.chargeType", new Option_String("charging"));
      77        91526 :     oc.addDescription("device.stationfinder.chargeType", "Battery", TL("Type of energy transfer"));
      78        91526 :     oc.doRegister("device.stationfinder.waitForCharge", new Option_String("600", "TIME"));
      79        91526 :     oc.addDescription("device.stationfinder.waitForCharge", "Battery", TL("After this waiting time vehicle searches for a new station when the initial one is blocked"));
      80        91526 :     oc.doRegister("device.stationfinder.minOpportunityDuration", new Option_String("3600", "TIME"));
      81        91526 :     oc.addDescription("device.stationfinder.minOpportunityDuration", "Battery", TL("Only stops with a predicted duration of at least the given threshold are considered for opportunistic charging."));
      82        45763 :     oc.doRegister("device.stationfinder.saturatedChargeLevel", new Option_Float(0.8));
      83        91526 :     oc.addDescription("device.stationfinder.saturatedChargeLevel", "Battery", TL("Target state of charge after which the vehicle stops charging"));
      84        45763 :     oc.doRegister("device.stationfinder.needToChargeLevel", new Option_Float(0.4));
      85        91526 :     oc.addDescription("device.stationfinder.needToChargeLevel", "Battery", TL("State of charge the vehicle begins searching for charging stations"));
      86        45763 :     oc.doRegister("device.stationfinder.opportunisticChargeLevel", new Option_Float(0.));
      87        91526 :     oc.addDescription("device.stationfinder.opportunisticChargeLevel", "Battery", TL("State of charge below which the vehicle may look for charging opportunities along its planned stops"));
      88        45763 :     oc.doRegister("device.stationfinder.replacePlannedStop", new Option_Float(0.));
      89        91526 :     oc.addDescription("device.stationfinder.replacePlannedStop", "Battery", TL("Share of stopping time of the next independently planned stop to use for charging instead"));
      90        45763 :     oc.doRegister("device.stationfinder.maxDistanceToReplacedStop", new Option_Float(300.));
      91        91526 :     oc.addDescription("device.stationfinder.maxDistanceToReplacedStop", "Battery", TL("Maximum distance in meters from the original stop to be replaced by the charging stop"));
      92        91526 :     oc.doRegister("device.stationfinder.chargingStrategy", new Option_String("none"));
      93        91526 :     oc.addDescription("device.stationfinder.chargingStrategy", "Battery", TL("Set a charging strategy to alter time and charging load from the set: [none, balanced, latest]"));
      94        45763 :     oc.doRegister("device.stationfinder.checkEnergyForRoute", new Option_Bool(true));
      95        91526 :     oc.addDescription("device.stationfinder.checkEnergyForRoute", "Battery", TL("Only search for charging stations if the battery charge is not estimated sufficient to complete the current route"));
      96        45763 : }
      97              : 
      98              : 
      99              : 
     100              : void
     101      5440087 : MSDevice_StationFinder::buildVehicleDevices(SUMOVehicle& v, std::vector<MSVehicleDevice*>& into) {
     102      5440087 :     OptionsCont& oc = OptionsCont::getOptions();
     103     10880174 :     if (equippedByDefaultAssignmentOptions(oc, "stationfinder", v, false)) {
     104          109 :         into.push_back(new MSDevice_StationFinder(v));
     105              :     }
     106      5440086 : }
     107              : 
     108              : 
     109              : // ---------------------------------------------------------------------------
     110              : // MSDevice_StationFinder-methods
     111              : // ---------------------------------------------------------------------------
     112          109 : MSDevice_StationFinder::MSDevice_StationFinder(SUMOVehicle& holder)
     113          109 :     : MSVehicleDevice(holder, "stationfinder_" + holder.getID()),
     114              :       MSStoppingPlaceRerouter("device.stationfinder.charging", true, {
     115              :     {"waitingTime", 1.}, {"chargingTime", 1.}
     116              : }, { {"waitingTime", false}, {"chargingTime", false} }),
     117            1 : myVeh(dynamic_cast<MSVehicle&>(holder)),
     118          108 : myBattery(nullptr), myChargingStation(nullptr), myRescueCommand(nullptr), myChargeLimitCommand(nullptr),
     119          108 : myLastChargeCheck(0), myCheckInterval(1000), myArrivalAtChargingStation(-1), myLastSearch(-1),
     120          982 : myLastOpportunisticSearch(-1) {
     121              :     // consider whole path to/from a charging station in the search
     122          108 :     myEvalParams["distanceto"] = 0.;
     123          108 :     myEvalParams["timeto"] = 1.;
     124          108 :     myEvalParams["timefrom"] = 1.;
     125          108 :     myNormParams["chargingTime"] = true;
     126          108 :     myNormParams["waitingTime"] = true;
     127          108 :     myRescueTime = STEPS2TIME(holder.getTimeParam("device.stationfinder.rescueTime"));
     128          216 :     const std::string chargingStrategy = holder.getStringParam("device.stationfinder.chargingStrategy");
     129          108 :     if (chargingStrategy == "balanced") {
     130            4 :         myChargingStrategy = CHARGINGSTRATEGY_BALANCED;
     131          104 :     } else if (chargingStrategy == "latest") {
     132            0 :         myChargingStrategy = CHARGINGSTRATEGY_LATEST;
     133          104 :     } else if (chargingStrategy == "none") {
     134          104 :         myChargingStrategy = CHARGINGSTRATEGY_NONE;
     135              :     } else {
     136            0 :         WRITE_ERRORF(TL("Invalid device.stationfinder.chargingStrategy '%'."), chargingStrategy);
     137              :     }
     138          216 :     const std::string rescueAction = holder.getStringParam("device.stationfinder.rescueAction");
     139          108 :     if (rescueAction == "remove") {
     140           84 :         myRescueAction = RESCUEACTION_REMOVE;
     141           24 :     }  else if (rescueAction == "tow") {
     142           12 :         myRescueAction = RESCUEACTION_TOW;
     143           12 :     } else if (rescueAction == "none") {
     144           12 :         myRescueAction = RESCUEACTION_NONE;
     145              :     } else {
     146            0 :         WRITE_ERRORF(TL("Invalid device.stationfinder.rescueAction '%'."), rescueAction);
     147              :     }
     148          108 :     initRescueCommand();
     149          216 :     myReserveFactor = MAX2(1., holder.getFloatParam("device.stationfinder.reserveFactor"));
     150          216 :     myEmptySoC = MAX2(0., MIN2(holder.getFloatParam("device.stationfinder.emptyThreshold"), 1.));
     151          108 :     myRadius = holder.getTimeParam("device.stationfinder.radius");
     152          108 :     myMaxEuclideanDistance = holder.getFloatParam("device.stationfinder.maxEuclideanDistance");
     153          108 :     myRepeatInterval = holder.getTimeParam("device.stationfinder.repeat");
     154          108 :     myChargeType = CHARGETYPE_CHARGING;
     155              : 
     156          108 :     myWaitForCharge = holder.getTimeParam("device.stationfinder.waitForCharge");
     157          108 :     myMinOpportunisticTime = holder.getTimeParam("device.stationfinder.minOpportunityDuration");
     158          216 :     myTargetSoC = MAX2(0., MIN2(holder.getFloatParam("device.stationfinder.saturatedChargeLevel"), 1.));
     159          216 :     mySearchSoC = MAX2(0., MIN2(holder.getFloatParam("device.stationfinder.needToChargeLevel"), 1.));
     160          108 :     if (mySearchSoC <= myEmptySoC) {
     161            0 :         WRITE_WARNINGF(TL("Vehicle '%' searches for charging stations only in the rescue case due to search threshold % <= rescue threshold %."), myHolder.getID(), mySearchSoC, myEmptySoC);
     162              :     }
     163          216 :     myOpportunitySoC = MAX2(0., MIN2(holder.getFloatParam("device.stationfinder.opportunisticChargeLevel"), 1.));
     164          108 :     if (myOpportunitySoC > 0. && myOpportunitySoC < MIN2(mySearchSoC + NUMERICAL_EPS, 1.)) {
     165            0 :         myOpportunitySoC = 0.;
     166            0 :         WRITE_WARNINGF(TL("Vehicle '%' won't do opportunistic charging as the threshold % is too close to the regular one %."), myHolder.getID(), myOpportunitySoC, mySearchSoC);
     167              :     }
     168          216 :     myReplacePlannedStop = MAX2(0., holder.getFloatParam("device.stationfinder.replacePlannedStop"));
     169          108 :     myDistanceToOriginalStop = holder.getFloatParam("device.stationfinder.maxDistanceToReplacedStop");
     170          108 :     myUpdateSoC = 2; // check once at the beginning
     171          108 :     myCheckEnergyForRoute = holder.getBoolParam("device.stationfinder.checkEnergyForRoute");
     172          109 : }
     173              : 
     174              : 
     175          216 : MSDevice_StationFinder::~MSDevice_StationFinder() {
     176              :     // make the rescue command invalid if there is one
     177          108 :     if (myRescueCommand != nullptr) {
     178              :         myRescueCommand->deschedule();
     179              :     }
     180          108 :     if (myChargeLimitCommand != nullptr) {
     181              :         myChargeLimitCommand->deschedule();
     182              :     }
     183          216 : }
     184              : 
     185              : 
     186              : bool
     187       139314 : MSDevice_StationFinder::notifyMove(SUMOTrafficObject& veh, double /*oldPos*/, double /*newPos*/, double /*newSpeed*/) {
     188       139314 :     if (myBattery->getEnergyCharged() > 0. && myChargingStation != nullptr) {
     189           80 :         myArrivalAtChargingStation = -1;
     190           80 :         myChargingStation = nullptr;
     191           80 :         mySearchState = SEARCHSTATE_CHARGING;
     192           80 :         return true;
     193       139234 :     } else if (mySearchState == SEARCHSTATE_CHARGING) {
     194        94068 :         if (myBattery->getChargingStation() == nullptr) {
     195           80 :             mySearchState = SEARCHSTATE_NONE;
     196              :         } else {
     197              :             return true;
     198              :         }
     199              :     }
     200              :     // check if the vehicle travels at most an edge length to the charging station after jump/teleport
     201        45246 :     if (mySearchState == SEARCHSTATE_BROKEN_DOWN && myVeh.hasStops() && myVeh.getStop(0).chargingStation != nullptr && myVeh.getStop(0).chargingStation->getLane().getEdge().getID() == myVeh.getLane()->getEdge().getID()) {
     202              :         return true;
     203              :     }
     204        45102 :     const SUMOTime now = SIMSTEP;
     205        45102 :     if (myChargingStation != nullptr) {
     206         3307 :         if (myArrivalAtChargingStation > 0 && now - myArrivalAtChargingStation > myWaitForCharge) {
     207              :             // waited for too long, try another charging station
     208            4 :             if (rerouteToChargingStation(true)) {
     209           12 :                 WRITE_MESSAGE(TLF("Rerouted vehicle '%' after waiting too long at the previous charging station at time=%.", veh.getID(), toString(SIMTIME)));
     210              :             }
     211         3303 :         } else if (myArrivalAtChargingStation < 0 && myVeh.willStop() && myVeh.getDistanceToPosition(myChargingStation->getBeginLanePosition(), myVeh.getLane()) < DEFAULT_CHARGINGSTATION_VIEW_DIST) {
     212              :             // remember when the vehicle arrived close to the target charging station
     213           75 :             mySearchState = SEARCHSTATE_WAITING;
     214           75 :             myArrivalAtChargingStation = now;
     215              :         }
     216              :     }
     217        45102 :     const double currentSoC = myBattery->getActualBatteryCapacity() / myBattery->getMaximumBatteryCapacity();
     218        45102 :     if (currentSoC < myOpportunitySoC && currentSoC < myTargetSoC && mySearchState == SEARCHSTATE_NONE) {
     219              :         // battery SoC is low enough to allow opportunistic charging (charging whenever - wherever)
     220         3792 :         planOpportunisticCharging();
     221         3792 :         myLastOpportunisticSearch = now;
     222         3792 :         return true;
     223        41310 :     } else if (currentSoC > mySearchSoC || mySearchState == SEARCHSTATE_BROKEN_DOWN) {
     224              :         // battery SoC is too high to look for charging facilities or the vehicle is already in rescue mode
     225              :         return true;
     226              :     }
     227              :     // only check once per second
     228        19586 :     if (now - myLastChargeCheck < 1000) {
     229              :         return true;
     230        19586 :     } else if (myRescueAction != RESCUEACTION_NONE  && (currentSoC < myEmptySoC || currentSoC < NUMERICAL_EPS)) {
     231              : 
     232              :         // vehicle has to stop at the end of the  because battery SoC is too low
     233           20 :         double brakeGap = myVeh.getCarFollowModel().brakeGap(myVeh.getSpeed());
     234           20 :         std::pair<const MSLane*, double> stopPos = myVeh.getLanePosAfterDist(brakeGap);
     235           20 :         if (stopPos.first != nullptr) {
     236           20 :             const MSLane* stopLane = (stopPos.first->isInternal()) ? stopPos.first->getNormalSuccessorLane() : stopPos.first;
     237           20 :             double endPos = stopPos.second;
     238           20 :             if (stopLane != stopPos.first) {
     239              :                 endPos = MIN2(POSITION_EPS, stopLane->getLength());
     240              :             }
     241              :             // remove possibly scheduled charging stop
     242           20 :             if (myVeh.hasStops() && myVeh.getStop(0).chargingStation != nullptr) {
     243            0 :                 myVeh.abortNextStop();
     244              :             }
     245              : 
     246              :             // schedule the rescue stop
     247           20 :             SUMOVehicleParameter::Stop rescueStop;
     248           20 :             rescueStop.index = 0;
     249              :             rescueStop.edge = stopLane->getEdge().getID();
     250              :             rescueStop.lane = stopLane->getID();
     251           20 :             rescueStop.startPos = MAX2(endPos - 2 * myHolder.getVehicleType().getLength(), 0.);
     252           20 :             rescueStop.endPos = endPos;
     253           20 :             rescueStop.parametersSet |= STOP_START_SET | STOP_END_SET;
     254           80 :             WRITE_MESSAGEF(TL("Vehicle '%' wants to stop on lane % at pos % because of low battery charge % at time=%."), myHolder.getID(), rescueStop.lane, toString(rescueStop.endPos), toString(currentSoC), toString(SIMTIME));
     255              : 
     256           20 :             if (myRescueAction == RESCUEACTION_REMOVE) {
     257              :                 // remove vehicle from network
     258            8 :                 rescueStop.until = SUMOTime_MAX;
     259            8 :                 rescueStop.breakDown = true;
     260            8 :                 std::string errorMsg = "Could not insert the rescue stop.";
     261            8 :                 if (!myVeh.insertStop(0, rescueStop, "stationfinder:rescue", false, errorMsg)) {
     262            0 :                     WRITE_ERROR(errorMsg);
     263              :                 }
     264            8 :                 mySearchState = SEARCHSTATE_BROKEN_DOWN;
     265              :                 return true;
     266           12 :             } else if (myRescueAction == RESCUEACTION_TOW) {
     267              :                 // wait next to the road and get teleported to a charging station
     268           12 :                 SUMOTime rescueTime = TIME2STEPS(myRescueTime);
     269           12 :                 rescueStop.duration = rescueTime;
     270           12 :                 rescueStop.parking = ParkingType::ONROAD;
     271           12 :                 rescueStop.jump = 0;
     272           12 :                 std::string errorMsg = "Could not insert the rescue stop.";
     273           12 :                 if (!myVeh.insertStop(0, rescueStop, "stationfinder:rescue", false, errorMsg)) {
     274            0 :                     WRITE_ERROR(errorMsg);
     275              :                 }
     276           12 :                 initRescueCommand();
     277           12 :                 MSNet::getInstance()->getBeginOfTimestepEvents()->addEvent(myRescueCommand, SIMSTEP + rescueStop.duration - DELTA_T);
     278           12 :                 mySearchState = SEARCHSTATE_BROKEN_DOWN;
     279              :                 return true;
     280              :             }
     281           20 :         }
     282        19566 :     } else if (myChargingStation == nullptr &&
     283        16383 :                (currentSoC < myUpdateSoC || (mySearchState == SEARCHSTATE_UNSUCCESSFUL &&
     284         2696 :                        now - myLastSearch >= myRepeatInterval && !myHolder.isStopped()))) {
     285              :         // check if a charging stop is already planned without the device, otherwise reroute inside this device
     286          937 :         if (!alreadyPlannedCharging() && now > myHolder.getDeparture()) {
     287          937 :             rerouteToChargingStation();
     288              :         }
     289         1025 :         myUpdateSoC = currentSoC - MAX2(0.1 * currentSoC, 0.01);
     290              : #ifdef DEBUG_STATIONFINDER_REROUTE
     291              :         std::cout << SIMTIME << " " << myHolder.getID() << " currentSoC=" << currentSoC << " nextUpdateSoC=" << myUpdateSoC << "\n";
     292              : #endif
     293              :     }
     294        19566 :     myLastChargeCheck = SIMSTEP;
     295        19566 :     return true;
     296              : }
     297              : 
     298              : 
     299              : bool
     300            0 : MSDevice_StationFinder::notifyIdle(SUMOTrafficObject& /*veh*/) {
     301            0 :     return true;
     302              : }
     303              : 
     304              : 
     305              : void
     306            0 : MSDevice_StationFinder::saveState(OutputDevice& out) const {
     307            0 :     out.openTag(SUMO_TAG_DEVICE);
     308            0 :     out.writeAttr(SUMO_ATTR_ID, getID());
     309              :     std::vector<std::string> internals;
     310            0 :     internals.push_back(toString(myLastChargeCheck));
     311            0 :     internals.push_back(toString(myUpdateSoC));
     312            0 :     internals.push_back(toString(mySearchSoC));
     313            0 :     internals.push_back(toString(myTargetSoC));
     314            0 :     internals.push_back(toString(myWaitForCharge));
     315            0 :     internals.push_back(toString(myRepeatInterval));
     316            0 :     internals.push_back(toString(myRadius));
     317            0 :     internals.push_back(toString(myLastSearch));
     318            0 :     internals.push_back(toString(myReserveFactor));
     319            0 :     internals.push_back(toString(mySearchState));
     320            0 :     internals.push_back(toString(myArrivalAtChargingStation));
     321            0 :     internals.push_back((myChargingStation == nullptr) ? "NULL" : myChargingStation->getID());
     322            0 :     internals.push_back(toString(myChargeLimits.size()));
     323            0 :     for (auto chargeLimit : myChargeLimits) {
     324            0 :         internals.push_back(toString(chargeLimit.first));
     325            0 :         internals.push_back(toString(chargeLimit.second));
     326              :     }
     327            0 :     internals.push_back(toString(myOpportunitySoC));
     328            0 :     internals.push_back(toString(myMinOpportunisticTime));
     329            0 :     internals.push_back(toString(myCheckEnergyForRoute));
     330            0 :     out.writeAttr(SUMO_ATTR_STATE, toString(internals));
     331            0 :     out.closeTag();
     332            0 : }
     333              : 
     334              : 
     335              : void
     336            0 : MSDevice_StationFinder::loadState(const SUMOSAXAttributes& attrs) {
     337            0 :     std::istringstream bis(attrs.getString(SUMO_ATTR_STATE));
     338            0 :     bis >> myLastChargeCheck;
     339            0 :     bis >> myUpdateSoC;
     340            0 :     bis >> mySearchSoC;
     341            0 :     bis >> myTargetSoC;
     342            0 :     bis >> myWaitForCharge;
     343            0 :     bis >> myRepeatInterval;
     344            0 :     bis >> myRadius;
     345            0 :     bis >> myLastSearch;
     346            0 :     bis >> myReserveFactor;
     347              :     int searchState;
     348            0 :     bis >> searchState;
     349            0 :     mySearchState = (SearchState)searchState;
     350            0 :     bis >> myArrivalAtChargingStation;
     351              :     std::string csID;
     352            0 :     bis >> csID;
     353            0 :     if (csID != "NULL") {
     354            0 :         myChargingStation = dynamic_cast<MSChargingStation*>(MSNet::getInstance()->getStoppingPlace(csID, SUMO_TAG_CHARGING_STATION));
     355              :     }
     356            0 :     int chargeLimitCount = 0;
     357            0 :     bis >> chargeLimitCount;
     358            0 :     for (int i = 0; i < chargeLimitCount; ++i) {
     359            0 :         SUMOTime t = 0;
     360            0 :         double limit = 0.;
     361              :         bis >> t;
     362              :         bis >> limit;
     363            0 :         myChargeLimits.push_back({ t, limit });
     364              :     }
     365            0 :     bis >> myOpportunitySoC;
     366            0 :     bis >> myMinOpportunisticTime;
     367            0 :     bis >> myCheckEnergyForRoute;
     368            0 : }
     369              : 
     370              : 
     371              : void
     372            0 : MSDevice_StationFinder::notifyMoveInternal(const SUMOTrafficObject& /*veh*/,
     373              :         const double /* frontOnLane */,
     374              :         const double /* timeOnLane */,
     375              :         const double /* meanSpeedFrontOnLane */,
     376              :         const double /* meanSpeedVehicleOnLane */,
     377              :         const double /* travelledDistanceFrontOnLane */,
     378              :         const double /* travelledDistanceVehicleOnLane */,
     379              :         const double /* meanLengthOnLane */) {
     380              : 
     381              :     // called by meso (see MSMeanData_Emissions::MSLaneMeanDataValues::notifyMoveInternal)
     382            0 : }
     383              : 
     384              : 
     385              : MSChargingStation*
     386          127 : MSDevice_StationFinder::findChargingStation(SUMOAbstractRouter<MSEdge, SUMOVehicle>& /*router*/, double expectedConsumption, StoppingPlaceParamMap_t& scores, bool constrainTT, bool skipVisited, bool skipOccupied, bool visible) {
     387              :     MSChargingStation* minStation = nullptr;
     388              :     std::vector<StoppingPlaceVisible> candidates;
     389          127 :     const StoppingPlaceMemory* chargingMemory = myVeh.getChargingMemory();
     390          127 :     if (chargingMemory == nullptr) {
     391              :         skipVisited = false;
     392              :     }
     393          127 :     const SUMOTime stoppingPlaceMemory = TIME2STEPS(getWeight(myHolder, "memory", 600));
     394          806 :     for (const auto& stop : MSNet::getInstance()->getStoppingPlaces(SUMO_TAG_CHARGING_STATION)) {
     395          679 :         MSChargingStation* cs = static_cast<MSChargingStation*>(stop.second);
     396          679 :         if (cs->getEfficency() < NUMERICAL_EPS || cs->getChargingPower(false) < NUMERICAL_EPS) {
     397          175 :             continue;
     398              :         }
     399          504 :         if (cs->getChargeType() != myBattery->getChargeType()) {
     400           16 :             continue;
     401              :         }
     402          488 :         if (cs->getParkingArea() != nullptr && !cs->getParkingArea()->accepts(&myVeh)) {
     403              :             // skip stations where the linked parking area does not grant access to the device holder
     404            0 :             continue;
     405              :         }
     406          488 :         if (skipOccupied && freeSpaceAtChargingStation(cs) < 1.) {
     407            0 :             continue;
     408              :         }
     409          536 :         if (skipVisited && chargingMemory->sawBlockedStoppingPlace(cs, false) > 0 && SIMSTEP - chargingMemory->sawBlockedStoppingPlace(cs, false) < stoppingPlaceMemory) {
     410              :             // skip recently visited
     411            0 :             continue;
     412              :         }
     413          488 :         if (constrainTT && myMaxEuclideanDistance > 0 && stop.second->getLane().geometryPositionAtOffset(stop.second->getBeginLanePosition()).distanceTo2D(myHolder.getPosition()) > myMaxEuclideanDistance) {
     414              :             // skip probably too distant charging stations
     415            0 :             continue;
     416              :         }
     417          488 :         if (visible && myHolder.getEdge()->getID() != stop.second->getLane().getEdge().getID()) {
     418           28 :             continue;
     419              :         }
     420          460 :         candidates.push_back({cs, false});
     421              :     }
     422              :     ConstMSEdgeVector newRoute;
     423          127 :     scores["expectedConsumption"] = expectedConsumption;
     424          127 :     std::vector<double> probs(candidates.size(), 1.);
     425              :     bool newDestination;
     426          127 :     myCheckValidity = constrainTT;
     427          127 :     MSStoppingPlace* bestCandidate = rerouteStoppingPlace(nullptr, candidates, probs, myHolder, newDestination, newRoute, scores);
     428          127 :     myCheckValidity = true;
     429          127 :     minStation = dynamic_cast<MSChargingStation*>(bestCandidate);
     430          127 :     return minStation;
     431          127 : }
     432              : 
     433              : 
     434              : bool
     435          941 : MSDevice_StationFinder::rerouteToChargingStation(bool replace) {
     436          957 :     double expectedConsumption = (myCheckEnergyForRoute) ? MIN2(estimateConsumption() * myReserveFactor, myBattery->getMaximumBatteryCapacity() * myTargetSoC) :
     437           16 :                                  myBattery->getMaximumBatteryCapacity() * MAX2(myTargetSoC - myBattery->getActualBatteryCapacity() / myBattery->getMaximumBatteryCapacity(), 0.);
     438              : #ifdef DEBUG_STATIONFINDER_REROUTE
     439              :     std::cout << SIMTIME << " " << myHolder.getID() << " expectedConsumption=" << expectedConsumption << " reserve=" << (myEmptySoC * myBattery->getMaximumBatteryCapacity()) << " chargeLevel=" << myBattery->getActualBatteryCapacity() << "\n";
     440              : #endif
     441          941 :     if (!myCheckEnergyForRoute || myBattery->getActualBatteryCapacity() < expectedConsumption + myEmptySoC * myBattery->getMaximumBatteryCapacity()) {
     442          111 :         myLastSearch = SIMSTEP;
     443          222 :         MSVehicleRouter& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
     444              :         StoppingPlaceParamMap_t scores = {};
     445          111 :         MSChargingStation* cs = findChargingStation(router, expectedConsumption, scores);
     446          111 :         if (cs != nullptr) {
     447              :             // integrate previously planned stops which do not have charging facilities
     448           76 :             myChargingStation = cs;
     449           76 :             SUMOVehicleParameter::Stop stopPar;
     450              :             stopPar.chargingStation = cs->getID();
     451           76 :             if (cs->getParkingArea() != nullptr) {
     452            4 :                 stopPar.parkingarea = cs->getParkingArea()->getID();
     453            8 :                 stopPar.parking = (cs->getParkingArea()->parkOnRoad()) ? ParkingType::ONROAD : ParkingType::OFFROAD;
     454              :             }
     455           76 :             stopPar.edge = cs->getLane().getEdge().getID();
     456           76 :             stopPar.lane = cs->getLane().getID();
     457           76 :             stopPar.duration = TIME2STEPS(expectedConsumption / (cs->getChargingPower(false) * cs->getEfficency()));
     458           76 :             stopPar.parametersSet = STOP_DURATION_SET;
     459           76 :             if (myReplacePlannedStop > 0) {
     460              :                 // "reuse" a previously planned stop (stop at charging station instead of a different stop)
     461              :                 // what if the charging station is skipped due to long waiting time?
     462           12 :                 if (myReplacePlannedStop > 0. && myHolder.hasStops() && myHolder.getNextStopParameter()->chargingStation.empty()) {
     463              :                     // compare the distance to the original target
     464           12 :                     if (scores["distfrom"] < myDistanceToOriginalStop /*actualDist < myDistanceToOriginalStop*/) {
     465              :                         // compute the arrival time at the original stop
     466           12 :                         const SUMOTime timeToOriginalStop = TIME2STEPS(scores["timefrom"]);
     467           12 :                         const SUMOTime originalUntil = myHolder.getNextStopParameter()->until;
     468           12 :                         if (timeToOriginalStop + myLastSearch < originalUntil) {
     469           12 :                             const SUMOTime delta = originalUntil - (timeToOriginalStop + myLastSearch);
     470           12 :                             stopPar.until = timeToOriginalStop + myLastSearch + (SUMOTime)((double)delta * MIN2(myReplacePlannedStop, 1.));
     471           12 :                             stopPar.parametersSet |= STOP_UNTIL_SET;
     472           12 :                             if (myReplacePlannedStop > 1.) {
     473            8 :                                 myHolder.abortNextStop();
     474              :                             }
     475              :                             // optionally implement a charging strategy by adjusting the accepted charging rates
     476           12 :                             if (myChargingStrategy != CHARGINGSTRATEGY_NONE) {
     477              :                                 // the charging strategy should actually only be computed at the arrival at the charging station
     478            8 :                                 implementChargingStrategy(myLastSearch + TIME2STEPS(scores["timeto"]), stopPar.until, expectedConsumption, cs);
     479              :                             }
     480              :                         }
     481              :                     }
     482              :                 }
     483              :             }
     484           76 :             stopPar.startPos = cs->getBeginLanePosition();
     485           76 :             stopPar.endPos = cs->getEndLanePosition();
     486              :             std::string errorMsg;
     487              : #ifdef DEBUG_STATIONFINDER_REROUTE
     488              :             std::ostringstream os;
     489              :             const ConstMSEdgeVector edgesBefore = myVeh.getRoute().getEdges();
     490              :             for (auto edge : edgesBefore) {
     491              :                 os << edge->getID() << " ";
     492              :             }
     493              :             std::cout << "MSDevice_StationFinder::rerouteToChargingStation: \n\tRoute before scheduling the charging station: " << os.str() << "\n";
     494              : #endif
     495          152 :             if ((replace && !myVeh.replaceStop(0, stopPar, "stationfinder:search", false, errorMsg)) || (!replace && !myVeh.insertStop(0, stopPar, "stationfinder:search", false, errorMsg))) {
     496            0 :                 WRITE_MESSAGE(TLF("Problem with inserting the charging station stop for vehicle %.", myHolder.getID()));
     497            0 :                 WRITE_ERROR(errorMsg);
     498              :             }
     499              : 
     500              : #ifdef DEBUG_STATIONFINDER_REROUTE
     501              :             std::ostringstream os2;
     502              :             const ConstMSEdgeVector edgesAfter = myVeh.getRoute().getEdges();
     503              :             for (auto edge : edgesAfter) {
     504              :                 os2 << edge->getID() << " ";
     505              :             }
     506              :             std::cout << "\tRoute after scheduling the charging station: " << os2.str() << "\n";
     507              : #endif
     508           76 :             myArrivalAtChargingStation = -1;
     509           76 :             mySearchState = SEARCHSTATE_SUCCESSFUL;
     510              : #ifdef DEBUG_STATIONFINDER_REROUTE
     511              :             std::cout << "\tVehicle " << myHolder.getID() << " gets rerouted to charging station " << cs->getID()  << " on edge " << stopPar.edge  << " at time " << SIMTIME << "\n";
     512              : #endif
     513              :             return true;
     514           76 :         }
     515           35 :         mySearchState = SEARCHSTATE_UNSUCCESSFUL;
     516          105 :         WRITE_MESSAGEF(TL("Vehicle '%' wants to charge at time=% but does not find any charging station nearby."), myHolder.getID(), toString(SIMTIME));
     517              :     }
     518              :     return false;
     519              : }
     520              : 
     521              : 
     522              : bool
     523         3792 : MSDevice_StationFinder::planOpportunisticCharging() {
     524              :     // check next stop
     525         3792 :     double capacityDelta = MAX2(0., myTargetSoC * myBattery->getMaximumBatteryCapacity() - myBattery->getActualBatteryCapacity());
     526         3792 :     if (myHolder.hasStops() && capacityDelta > 0.) {
     527          396 :         const MSStop& nextStop = myHolder.getNextStop();
     528          396 :         if (myHolder.isStopped() || nextStop.chargingStation != nullptr || myHolder.getCurrentRouteEdge() != nextStop.edge ||
     529            4 :                 nextStop.getMinDuration(SIMSTEP) < myMinOpportunisticTime) {
     530          396 :             return false;
     531              :         }
     532              :         // find charging station on same edge
     533            8 :         MSVehicleRouter& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
     534              :         StoppingPlaceParamMap_t scores = {};
     535            4 :         MSChargingStation* cs = findChargingStation(router, 0., scores, true, true, true, true);
     536            4 :         if (cs != nullptr) {
     537              :             // replace next stop by charging stop
     538            4 :             myChargingStation = cs;
     539            4 :             SUMOVehicleParameter::Stop stopPar;
     540              :             stopPar.chargingStation = cs->getID();
     541            4 :             if (cs->getParkingArea() != nullptr) {
     542            0 :                 stopPar.parkingarea = cs->getParkingArea()->getID();
     543            0 :                 stopPar.parking = (cs->getParkingArea()->parkOnRoad()) ? ParkingType::ONROAD : ParkingType::OFFROAD;
     544              :             }
     545            4 :             stopPar.edge = cs->getLane().getEdge().getID();
     546            4 :             stopPar.lane = cs->getLane().getID();
     547            4 :             stopPar.startPos = cs->getBeginLanePosition();
     548            4 :             stopPar.endPos = cs->getEndLanePosition();
     549              :             // copy over depart time from previously planned stop
     550            4 :             SUMOTime oldUntil = nextStop.getUntil();
     551            4 :             if (oldUntil > 0) {
     552            0 :                 stopPar.until = oldUntil;
     553            0 :                 stopPar.duration = 0;
     554              :             } else { // set a duration needed to charge to the target SoC
     555            4 :                 stopPar.duration = nextStop.duration;
     556              :             }
     557              :             std::string errorMsg;
     558            4 :             if (!myVeh.replaceStop(0, stopPar, "stationfinder:opportunisticSearch", false, errorMsg)) {
     559            0 :                 WRITE_ERROR(errorMsg);
     560              :             }
     561              :             return true;
     562            4 :         }
     563              :     }
     564              :     return false;
     565              : }
     566              : 
     567              : 
     568              : SUMOTime
     569           12 : MSDevice_StationFinder::teleportToChargingStation(const SUMOTime /*currentTime*/) {
     570              :     // find closest charging station
     571           12 :     MSVehicleRouter& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
     572           12 :     double expectedConsumption = MIN2(estimateConsumption(nullptr, true, STEPS2TIME(myVeh.getStops().front().pars.duration)) * myReserveFactor, myBattery->getMaximumBatteryCapacity() * myTargetSoC);
     573              :     StoppingPlaceParamMap_t scores = {};
     574           12 :     MSChargingStation* cs = findChargingStation(router, expectedConsumption, scores, false, false, true);
     575           12 :     if (cs == nullptr) {
     576              :         // continue waiting if all charging stations are occupied
     577              : #ifdef DEBUG_STATIONFINDER_RESCUE
     578              :         std::cout << "MSDevice_StationFinder::teleportToChargingStation: No charging station available to teleport the broken-down vehicle " << myHolder.getID() << " to at time " << SIMTIME << ".\n.";
     579              : #endif
     580              :         // remove the vehicle if teleport to a charging station fails
     581            4 :         if (myHolder.isStopped()) {
     582            4 :             MSStop& currentStop = myHolder.getNextStopMutable();
     583            4 :             currentStop.duration += DELTA_T;
     584              :             SUMOVehicleParameter::Stop& stopPar = const_cast<SUMOVehicleParameter::Stop&>(currentStop.pars);
     585            4 :             stopPar.jump = -1;
     586            4 :             stopPar.breakDown = true;
     587            4 :             mySearchState = SEARCHSTATE_BROKEN_DOWN;
     588           12 :             WRITE_WARNINGF(TL("There is no charging station available to teleport the vehicle '%' to at time=%. Thus the vehicle will be removed."), myHolder.getID(), toString(SIMTIME));
     589              :         }
     590              : #ifdef DEBUG_STATIONFINDER_RESCUE
     591              :         else {
     592              : #ifdef DEBUG_STATIONFINDER_RESCUE
     593              :             std::cout << "MSDevice_StationFinder::teleportToChargingStation: Rescue stop of " << myHolder.getID() << " ended prematurely before regular end at " << SIMTIME << ".\n.";
     594              : #endif
     595              :         }
     596              : #endif
     597            4 :         return myRepeatInterval;
     598              :     }
     599              : 
     600              :     // teleport to the charging station, stop there for charging
     601            8 :     myChargingStation = cs;
     602            8 :     SUMOVehicleParameter::Stop stopPar;
     603              :     stopPar.chargingStation = cs->getID();
     604            8 :     if (cs->getParkingArea() != nullptr) {
     605            0 :         stopPar.parkingarea = cs->getParkingArea()->getID();
     606            0 :         stopPar.parking = (cs->getParkingArea()->parkOnRoad()) ? ParkingType::ONROAD : ParkingType::OFFROAD;
     607              :     }
     608            8 :     stopPar.edge = cs->getLane().getEdge().getID();
     609            8 :     stopPar.lane = cs->getLane().getID();
     610            8 :     stopPar.startPos = cs->getBeginLanePosition();
     611            8 :     stopPar.endPos = cs->getEndLanePosition();
     612            8 :     stopPar.duration = TIME2STEPS(expectedConsumption / (cs->getChargingPower(false) * cs->getEfficency()));
     613              :     std::string errorMsg;
     614            8 :     if (!myVeh.insertStop(1, stopPar, "stationfinder:search", true, errorMsg)) {
     615            0 :         WRITE_ERROR(errorMsg);
     616              :     }
     617            8 :     myRescueCommand->deschedule();
     618            8 :     myRescueCommand = nullptr;
     619              :     return 0;
     620            8 : }
     621              : 
     622              : 
     623              : double
     624          997 : MSDevice_StationFinder::estimateConsumption(const MSEdge* target, const bool includeEmptySoC, const double stopDiscount) const {
     625          997 :     const SUMOTime now = SIMSTEP;
     626          997 :     MSVehicleRouter& router = MSRoutingEngine::getRouterTT(myHolder.getRNGIndex(), myHolder.getVClass());
     627          997 :     const ConstMSEdgeVector& route = myHolder.getRoute().getEdges();
     628          997 :     ConstMSEdgeVector::const_iterator targetIt = (target == nullptr) ? route.end() : std::find(route.begin(), route.end(), target) + 1;
     629          997 :     const ConstMSEdgeVector remainingRoute(route.begin() + myHolder.getRoutePosition(), targetIt);
     630          997 :     const double remainingTime = router.recomputeCosts(remainingRoute, &myHolder, now);
     631          997 :     if (now > myHolder.getDeparture()) {
     632          997 :         const double totalConsumption = myBattery->getTotalConsumption();
     633              :         double expectedConsumption = 0.;
     634          997 :         double passedTime = STEPS2TIME(now - myHolder.getDeparture());
     635          997 :         if (totalConsumption > 0. && passedTime - stopDiscount > DEFAULT_CONSUMPTION_ESTIMATE_HISTORY) {
     636          889 :             expectedConsumption = totalConsumption / (passedTime - stopDiscount) * remainingTime;
     637              :         } else {
     638              :             // fallback consumption rate for vehicles starting with low battery
     639          108 :             if (!myHolder.getVehicleType().getParameter().wasSet(VTYPEPARS_EMISSIONCLASS_SET)) {
     640            0 :                 WRITE_ERRORF("The stationfinder device needs emission parameters for range estimation but no emission class has been set for the vehicle '%'", myHolder.getID());
     641              :             }
     642          108 :             const double speed = MIN2(myHolder.getMaxSpeed(), myHolder.getLane()->getSpeedLimit());
     643          108 :             EnergyParams* const params = myHolder.getEmissionParameters();
     644          108 :             PollutantsInterface::EmissionType emType = myBattery->tracksFuel() ? PollutantsInterface::FUEL : PollutantsInterface::ELEC;
     645          108 :             expectedConsumption = PollutantsInterface::compute(myVeh.getVehicleType().getEmissionClass(), emType,
     646          108 :                                   speed * 0.8, 0., 0., params) * (remainingTime - passedTime);
     647              :         }
     648          997 :         if (includeEmptySoC) {
     649         1089 :             expectedConsumption += MAX2(0., myEmptySoC * myBattery->getMaximumBatteryCapacity() - myBattery->getActualBatteryCapacity());
     650              :         }
     651          997 :         expectedConsumption /= myHolder.getEmissionParameters()->getDoubleOptional(SUMO_ATTR_PROPULSIONEFFICIENCY, 1.);
     652          997 :         return expectedConsumption;
     653              :     }
     654              :     return 0.;
     655          997 : }
     656              : 
     657              : 
     658              : double
     659          612 : MSDevice_StationFinder::freeSpaceAtChargingStation(MSChargingStation* cs) const {
     660          612 :     return (cs->getParkingArea() != nullptr) ? cs->getParkingArea()->getCapacity() - cs->getParkingArea()->getOccupancy() : (cs->getLastFreePos() - cs->getBeginLanePosition()) / myHolder.getVehicleType().getParameter().length;
     661              : }
     662              : 
     663              : 
     664              : bool
     665          937 : MSDevice_StationFinder::alreadyPlannedCharging() {
     666          937 :     if (myChargingStation == nullptr) {
     667          937 :         auto stops = myHolder.getStops();
     668          949 :         for (auto stop : stops) {
     669           12 :             if (stop.chargingStation != nullptr) {
     670              :                 // compare whether we'll make it there without intermediate charging
     671            0 :                 double expectedConsumption = estimateConsumption(*stop.edge);
     672            0 :                 if (myBattery->getActualBatteryCapacity() < expectedConsumption) {
     673            0 :                     myChargingStation = stop.chargingStation;
     674              :                     return true;
     675              :                 }
     676              :             }
     677              :         }
     678              :     }
     679              :     return false;
     680              : }
     681              : 
     682              : 
     683              : void
     684          120 : MSDevice_StationFinder::initRescueCommand() {
     685          120 :     if (myRescueAction == RESCUEACTION_TOW && myRescueCommand == nullptr) {
     686           12 :         myRescueCommand = new WrappingCommand<MSDevice_StationFinder>(this, &MSDevice_StationFinder::teleportToChargingStation);
     687              :     }
     688          120 : }
     689              : 
     690              : 
     691              : void
     692            4 : MSDevice_StationFinder::initChargeLimitCommand() {
     693            4 :     if (myChargingStrategy != CHARGINGSTRATEGY_NONE && myChargeLimitCommand == nullptr) {
     694            4 :         myChargeLimitCommand = new WrappingCommand<MSDevice_StationFinder>(this, &MSDevice_StationFinder::updateChargeLimit);
     695              :     }
     696            4 : }
     697              : 
     698              : 
     699              : SUMOTime
     700            8 : MSDevice_StationFinder::updateChargeLimit(const SUMOTime currentTime) {
     701            8 :     if (myChargeLimits.size() > 0 && myChargeLimits.begin()->first < currentTime - DELTA_T) {
     702              :         myChargeLimits.clear();
     703              :     }
     704            8 :     if (myChargeLimits.size() > 0) {
     705            8 :         double chargeLimit = myChargeLimits.begin()->second;
     706            8 :         myBattery->setChargeLimit(chargeLimit);
     707            8 :         if (chargeLimit < 0) {
     708            8 :             WRITE_MESSAGEF(TL("The charging rate limit of vehicle '%' is lifted at time=%"), myHolder.getID(), STEPS2TIME(SIMSTEP));
     709              :         } else {
     710            8 :             WRITE_MESSAGEF(TL("The charging rate of vehicle '%' is limited to % at time=%"), myHolder.getID(), chargeLimit, STEPS2TIME(SIMSTEP));
     711              :         }
     712              :         myChargeLimits.erase(myChargeLimits.begin());
     713              :     }
     714            8 :     if (myChargeLimits.size() == 0) {
     715            4 :         myChargeLimitCommand->deschedule();
     716            4 :         myChargeLimitCommand = nullptr;
     717            4 :         return 0;
     718              :     } else {
     719            4 :         return myChargeLimits.begin()->first - currentTime;
     720              :     }
     721              : }
     722              : 
     723              : 
     724              : void
     725            4 : MSDevice_StationFinder::implementChargingStrategy(SUMOTime begin, SUMOTime end, const double plannedCharge, const MSChargingStation* cs) {
     726              :     myChargeLimits.clear();
     727            4 :     if (myChargingStrategy == CHARGINGSTRATEGY_BALANCED) {
     728            4 :         const double balancedChargeRate = plannedCharge / STEPS2TIME(end - begin) * 3600.;
     729            4 :         myChargeLimits.push_back({ begin, balancedChargeRate });
     730            4 :         myChargeLimits.push_back({ end, -1});
     731              :     } else { // CHARGINGSTRATEGY_LATEST
     732            0 :         SUMOTime expectedDuration = myBattery->estimateChargingDuration(plannedCharge, cs->getChargingPower(false) * cs->getEfficency());
     733            0 :         if (end - expectedDuration > begin) {
     734            0 :             myChargeLimits.push_back({ begin, 0 });
     735            0 :             myChargeLimits.push_back({ end - expectedDuration, -1 });
     736              :         }
     737              :     }
     738            4 :     if (myChargeLimits.size() > 0) {
     739            4 :         initChargeLimitCommand();
     740            4 :         MSNet::getInstance()->getBeginOfTimestepEvents()->addEvent(myChargeLimitCommand, begin);
     741              :     }
     742            4 : }
     743              : 
     744              : 
     745              : void
     746          104 : MSDevice_StationFinder::generateOutput(OutputDevice* tripinfoOut) const {
     747          104 :     if (tripinfoOut != nullptr && myChargingStation != nullptr) {
     748            0 :         tripinfoOut->openTag("stationfinder");
     749            0 :         tripinfoOut->writeAttr("chargingStation", myChargingStation->getID());
     750            0 :         tripinfoOut->closeTag();
     751              :     }
     752          104 : }
     753              : 
     754              : 
     755              : std::string
     756          120 : MSDevice_StationFinder::getParameter(const std::string& key) const {
     757          120 :     if (key == "chargingStation") { // eventually abstract with enum
     758          116 :         return (myChargingStation == nullptr) ? "" : myChargingStation->getID();
     759           60 :     } else if (key == "batteryNeed") {
     760           60 :         return toString(estimateConsumption() * myReserveFactor);
     761            0 :     } else if (key == "needToChargeLevel") {
     762            0 :         return toString(mySearchSoC);
     763            0 :     } else if (key == "saturatedChargeLevel") {
     764            0 :         return toString(myTargetSoC);
     765            0 :     } else if (key == "waitForCharge") {
     766            0 :         return toString(myWaitForCharge);
     767            0 :     } else if (key == "repeat") {
     768            0 :         return toString(myRepeatInterval);
     769            0 :     } else if (key == "radius") {
     770            0 :         return toString(myRadius);
     771            0 :     } else if (key == "reserveFactor") {
     772            0 :         return toString(myReserveFactor);
     773              :     }
     774            0 :     throw InvalidArgument(TLF("Parameter '%' is not supported for device of type '%'", key, deviceName()));
     775              : }
     776              : 
     777              : 
     778              : void
     779            0 : MSDevice_StationFinder::setParameter(const std::string& key, const std::string& value) {
     780              :     double doubleValue;
     781              :     try {
     782            0 :         doubleValue = StringUtils::toDouble(value);
     783            0 :     } catch (NumberFormatException&) {
     784            0 :         throw InvalidArgument(TLF("Setting parameter '%' requires a number for device of type '%'", key, deviceName()));
     785            0 :     }
     786            0 :     if (key == "needToChargeLevel") {
     787            0 :         mySearchSoC = MAX2(0., MIN2(1., doubleValue));
     788            0 :     } else if (key == "saturatedChargeLevel") {
     789            0 :         myTargetSoC = MAX2(0., MIN2(1., doubleValue));
     790            0 :     } else if (key == "waitForCharge") {
     791            0 :         myWaitForCharge = TIME2STEPS(MAX2(0., doubleValue));
     792            0 :     } else if (key == "repeat") {
     793            0 :         myRepeatInterval = TIME2STEPS(MAX2(0., doubleValue));
     794            0 :     } else if (key == "radius") {
     795            0 :         myRadius = TIME2STEPS(MAX2(0., doubleValue));
     796            0 :     } else if (key == "reserveFactor") {
     797            0 :         myReserveFactor = MAX2(1., doubleValue);
     798              :     } else {
     799            0 :         throw InvalidArgument(TLF("Setting parameter '%' is not supported for device of type '%'", key, deviceName()));
     800              :     }
     801            0 : }
     802              : 
     803              : 
     804              : bool
     805          544 : MSDevice_StationFinder::evaluateCustomComponents(SUMOVehicle& /* veh */, double /* brakeGap */, bool /* newDestination */,
     806              :         MSStoppingPlace* alternative, double /* occupancy */, double /* prob */,
     807              :         SUMOAbstractRouter<MSEdge, SUMOVehicle>& /* router */,
     808              :         StoppingPlaceParamMap_t& stoppingPlaceValues,
     809              :         ConstMSEdgeVector& /* newRoute */, ConstMSEdgeVector& /* stoppingPlaceApproach */,
     810              :         StoppingPlaceParamMap_t& /* maxValues */, StoppingPlaceParamMap_t& addInput) {
     811              :     // estimated waiting time and charging time
     812          544 :     MSChargingStation* cs = dynamic_cast<MSChargingStation*>(alternative);
     813          544 :     double parkingCapacity = (cs->getParkingArea() != nullptr) ? cs->getParkingArea()->getCapacity() : (cs->getEndLanePosition() - cs->getBeginLanePosition()) / myHolder.getVehicleType().getParameter().length;
     814          544 :     double freeParkingCapacity = freeSpaceAtChargingStation(cs);
     815          544 :     stoppingPlaceValues["waitingTime"] = (freeParkingCapacity < 1.) ? DEFAULT_AVG_WAITING_TIME / parkingCapacity : 0.;
     816          544 :     stoppingPlaceValues["chargingTime"] = STEPS2TIME(cs->getChargeDelay()) + addInput["expectedConsumption"] / cs->getChargingPower(false);
     817          544 :     return true;
     818              : }
     819              : 
     820              : 
     821              : bool
     822          508 : MSDevice_StationFinder::validComponentValues(StoppingPlaceParamMap_t& stoppingPlaceValues) {
     823          508 :     if (stoppingPlaceValues["timeto"] > STEPS2TIME(myRadius)) {
     824          188 :         return false;
     825              :     }
     826              :     return true;
     827              : }
     828              : 
     829              : 
     830              : bool
     831          460 : MSDevice_StationFinder::useStoppingPlace(MSStoppingPlace* /* stoppingPlace */) {
     832          460 :     return true;
     833              : }
     834              : 
     835              : 
     836          254 : SUMOAbstractRouter<MSEdge, SUMOVehicle>& MSDevice_StationFinder::getRouter(SUMOVehicle& veh, const Prohibitions& prohibited) {
     837          254 :     return MSRoutingEngine::getRouterTT(veh.getRNGIndex(), veh.getVClass(), prohibited);
     838              : }
     839              : 
     840              : 
     841              : double
     842          460 : MSDevice_StationFinder::getStoppingPlaceOccupancy(MSStoppingPlace* stoppingPlace, const SUMOVehicle*) {
     843          460 :     MSChargingStation* cs = dynamic_cast<MSChargingStation*>(stoppingPlace);
     844          460 :     if (cs->getParkingArea() != nullptr) {
     845            4 :         return cs->getParkingArea()->getOccupancy();
     846              :     }
     847          456 :     return (cs->getEndLanePosition() - cs->getLastFreePos()) / (myHolder.getLength() + myHolder.getVehicleType().getMinGap());
     848              : }
     849              : 
     850              : 
     851              : double
     852            0 : MSDevice_StationFinder::getLastStepStoppingPlaceOccupancy(MSStoppingPlace* stoppingPlace, const SUMOVehicle*) {
     853            0 :     MSChargingStation* cs = dynamic_cast<MSChargingStation*>(stoppingPlace);
     854            0 :     if (cs->getParkingArea() != nullptr) {
     855            0 :         return cs->getParkingArea()->getLastStepOccupancy();
     856              :     }
     857            0 :     return (cs->getEndLanePosition() - cs->getLastFreePos()) / (myHolder.getLength() + myHolder.getVehicleType().getMinGap());
     858              : }
     859              : 
     860              : 
     861              : double
     862         1464 : MSDevice_StationFinder::getStoppingPlaceCapacity(MSStoppingPlace* stoppingPlace) {
     863         1464 :     MSChargingStation* cs = dynamic_cast<MSChargingStation*>(stoppingPlace);
     864         1464 :     if (cs->getParkingArea() != nullptr) {
     865           12 :         return cs->getParkingArea()->getCapacity();
     866              :     }
     867         1452 :     return (cs->getEndLanePosition() - cs->getBeginLanePosition()) / (myHolder.getLength() + myHolder.getVehicleType().getMinGap());
     868              : }
     869              : 
     870              : 
     871              : void
     872            0 : MSDevice_StationFinder::rememberBlockedStoppingPlace(SUMOVehicle& veh, const MSStoppingPlace* stoppingPlace, bool blocked) {
     873            0 :     veh.rememberBlockedChargingStation(stoppingPlace, blocked);
     874            0 : }
     875              : 
     876              : 
     877              : void
     878          360 : MSDevice_StationFinder::rememberStoppingPlaceScore(SUMOVehicle& veh, MSStoppingPlace* place, const std::string& score) {
     879          360 :     veh.rememberChargingStationScore(place, score);
     880          360 : }
     881              : 
     882              : 
     883              : void
     884          127 : MSDevice_StationFinder::resetStoppingPlaceScores(SUMOVehicle& veh) {
     885          127 :     veh.resetChargingStationScores();
     886          127 : }
     887              : 
     888              : 
     889              : SUMOTime
     890          548 : MSDevice_StationFinder::sawBlockedStoppingPlace(SUMOVehicle& veh, MSStoppingPlace* place, bool local) {
     891          548 :     return veh.sawBlockedChargingStation(place, local);
     892              : }
     893              : 
     894              : 
     895              : int
     896          587 : MSDevice_StationFinder::getNumberStoppingPlaceReroutes(SUMOVehicle& /* veh */) {
     897          587 :     return 0;
     898              : }
     899              : 
     900              : 
     901              : void
     902          127 : MSDevice_StationFinder::setNumberStoppingPlaceReroutes(SUMOVehicle& /* veh */, int /* value */) {
     903          127 : }
     904              : 
     905              : 
     906              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1