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 : /****************************************************************************/
|