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

Generated by: LCOV version 2.0-1