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