LCOV - code coverage report
Current view: top level - src/microsim/trigger - MSOverheadWire.cpp (source / functions) Coverage Total Hit
Test: lcov.info Lines: 80.1 % 336 269
Test Date: 2025-12-06 15:35:27 Functions: 75.0 % 40 30

            Line data    Source code
       1              : /****************************************************************************/
       2              : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
       3              : // Copyright (C) 2002-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    MSOverheadWire.cpp
      15              : /// @author  Jakub Sevcik (RICE)
      16              : /// @author  Jan Prikryl (RICE)
      17              : /// @date    2019-12-15
      18              : ///
      19              : // Member method definitions for MSOverheadWire and MSTractionSubstation.
      20              : /****************************************************************************/
      21              : #include <config.h>
      22              : 
      23              : #include <cassert>
      24              : #include <tuple>
      25              : #include <mutex>
      26              : #include <string.h>
      27              : 
      28              : #include <utils/vehicle/SUMOVehicle.h>
      29              : #include <utils/common/ToString.h>
      30              : #include <microsim/MSVehicleType.h>
      31              : #include <microsim/MSStoppingPlace.h>
      32              : #include <microsim/MSJunction.h>
      33              : #include <microsim/MSLane.h>
      34              : #include <microsim/MSLink.h>
      35              : #include <microsim/MSNet.h>
      36              : #include <microsim/devices/MSDevice_ElecHybrid.h>
      37              : 
      38              : // due to gOverheadWireSolver
      39              : #include <microsim/MSGlobals.h>
      40              : 
      41              : // due to solving circuit as endEndOfTimestepEvents
      42              : #include <utils/common/StaticCommand.h>
      43              : #include <utils/common/WrappingCommand.h>
      44              : #include <microsim/MSEventControl.h>
      45              : 
      46              : #include <utils/traction_wire/Node.h>
      47              : #include "MSOverheadWire.h"
      48              : 
      49              : 
      50              : Command* MSTractionSubstation::myCommandForSolvingCircuit = nullptr;
      51              : static std::mutex ow_mutex;
      52              : 
      53              : // ===========================================================================
      54              : //                                                              MSOverheadWire
      55              : // ===========================================================================
      56              : 
      57           62 : MSOverheadWire::MSOverheadWire(const std::string& overheadWireSegmentID, MSLane& lane, double startPos, double endPos, bool voltageSource) :
      58           62 :     MSStoppingPlace(overheadWireSegmentID, SUMO_TAG_OVERHEAD_WIRE_SEGMENT, std::vector<std::string>(), lane, startPos, endPos),
      59           62 :     myVoltage(0),
      60           62 :     myChargingVehicle(false),
      61           62 :     myTotalCharge(0),
      62           62 :     myChargingVehicles({}),
      63              :                    // RICE_TODO: think about some better structure storing circuit pointers below
      64           62 :                    myTractionSubstation(nullptr),
      65           62 :                    myVoltageSource(voltageSource),
      66           62 :                    myCircuitElementPos(nullptr),
      67           62 :                    myCircuitStartNodePos(nullptr),
      68          124 : myCircuitEndNodePos(nullptr) {
      69           62 :     if (getBeginLanePosition() > getEndLanePosition()) {
      70            0 :         WRITE_WARNING(toString(SUMO_TAG_OVERHEAD_WIRE_SEGMENT) + " with ID = " + getID() + " doesn't have a valid range (" + toString(getBeginLanePosition()) + " < " + toString(getEndLanePosition()) + ").");
      71              :     }
      72           62 : }
      73              : 
      74          112 : MSOverheadWire::~MSOverheadWire() {
      75           62 :     if (myTractionSubstation != nullptr) {
      76              :         Circuit* circuit = myTractionSubstation->getCircuit();
      77           42 :         if (circuit != nullptr && myCircuitElementPos != nullptr && myCircuitElementPos->getPosNode() == myCircuitStartNodePos && myCircuitElementPos->getNegNode() == myCircuitEndNodePos) {
      78           42 :             circuit->eraseElement(myCircuitElementPos);
      79           84 :             delete myCircuitElementPos;
      80           42 :             if (myCircuitEndNodePos->getElements()->size() == 0) {
      81           16 :                 circuit->eraseNode(myCircuitEndNodePos);
      82           32 :                 delete myCircuitEndNodePos;
      83              :             }
      84           42 :             if (myCircuitStartNodePos->getElements()->size() == 0) {
      85           12 :                 circuit->eraseNode(myCircuitStartNodePos);
      86           24 :                 delete myCircuitStartNodePos;
      87              :             }
      88              :         }
      89              : 
      90           42 :         if (myTractionSubstation->numberOfOverheadSegments() <= 1) {
      91            8 :             myTractionSubstation->eraseOverheadWireSegmentFromCircuit(this);
      92              :             //RICE_TODO We should "delete myTractionSubstation;" here ...
      93              :         } else {
      94           34 :             myTractionSubstation->eraseOverheadWireSegmentFromCircuit(this);
      95              :         }
      96              :     }
      97          174 : }
      98              : 
      99              : 
     100              : void
     101           42 : MSOverheadWire::addVehicle(SUMOVehicle& veh) {
     102              :     std::lock_guard<std::mutex> guard(ow_mutex);
     103           42 :     setChargingVehicle(true);
     104           42 :     myChargingVehicles.push_back(&veh);
     105           42 :     sort(myChargingVehicles.begin(), myChargingVehicles.end(), vehicle_position_sorter());
     106           42 : }
     107              : 
     108              : void
     109           27 : MSOverheadWire::eraseVehicle(SUMOVehicle& veh) {
     110              :     std::lock_guard<std::mutex> guard(ow_mutex);
     111           27 :     myChargingVehicles.erase(std::remove(myChargingVehicles.begin(), myChargingVehicles.end(), &veh), myChargingVehicles.end());
     112           27 :     if (myChargingVehicles.size() == 0) {
     113           22 :         setChargingVehicle(false);
     114              :     }
     115              :     //sort(myChargingVehicles.begin(), myChargingVehicles.end(), vehicle_position_sorter());
     116           27 : }
     117              : 
     118              : void
     119           34 : MSOverheadWire::lock() const {
     120           34 :     ow_mutex.lock();
     121           34 : }
     122              : 
     123              : void
     124           34 : MSOverheadWire::unlock() const {
     125              :     ow_mutex.unlock();
     126           34 : }
     127              : 
     128              : void
     129           22 : MSTractionSubstation::addVehicle(MSDevice_ElecHybrid* elecHybrid) {
     130           22 :     myElecHybrid.push_back(elecHybrid);
     131           22 : }
     132              : 
     133              : void
     134           22 : MSTractionSubstation::eraseVehicle(MSDevice_ElecHybrid* veh) {
     135           22 :     myElecHybrid.erase(std::remove(myElecHybrid.begin(), myElecHybrid.end(), veh), myElecHybrid.end());
     136           22 : }
     137              : 
     138              : void
     139            0 : MSTractionSubstation::writeOut() {
     140            0 :     std::cout << "substation " << getID() << " constrols segments: \n";
     141            0 :     for (std::vector<MSOverheadWire*>::iterator it = myOverheadWireSegments.begin(); it != myOverheadWireSegments.end(); ++it) {
     142            0 :         std::cout << "        " << (*it)->getOverheadWireSegmentName() << "\n";
     143              :     }
     144            0 : }
     145              : 
     146              : 
     147            0 : std::string MSOverheadWire::getOverheadWireSegmentName() {
     148            0 :     return toString(getID());
     149              : }
     150              : 
     151            0 : MSTractionSubstation::~MSTractionSubstation() {
     152            0 : }
     153              : 
     154              : Circuit*
     155         1381 : MSOverheadWire::getCircuit() const {
     156         1381 :     if (getTractionSubstation() != nullptr) {
     157         1363 :         return getTractionSubstation()->getCircuit();
     158              :     }
     159              :     return nullptr;
     160              : }
     161              : 
     162              : double
     163            0 : MSOverheadWire::getVoltage() const {
     164            0 :     return myVoltage;
     165              : }
     166              : 
     167              : void
     168            0 : MSOverheadWire::setVoltage(double voltage) {
     169            0 :     if (voltage < 0) {
     170            0 :         WRITE_WARNING("New " + toString(SUMO_ATTR_VOLTAGE) + " for " + toString(SUMO_TAG_OVERHEAD_WIRE_SEGMENT) + " with ID = " + getID() + " isn't valid (" + toString(voltage) + ").")
     171              :     } else {
     172            0 :         myVoltage = voltage;
     173              :     }
     174            0 : }
     175              : 
     176              : void
     177           64 : MSOverheadWire::setChargingVehicle(bool value) {
     178           64 :     myChargingVehicle = value;
     179           64 : }
     180              : 
     181              : 
     182              : void
     183          360 : MSTractionSubstation::setChargingVehicle(bool value) {
     184          360 :     myChargingVehicle = value;
     185          360 : }
     186              : 
     187              : bool
     188            0 : MSOverheadWire::vehicleIsInside(const double position) const {
     189            0 :     if ((position >= getBeginLanePosition()) && (position <= getEndLanePosition())) {
     190              :         return true;
     191              :     } else {
     192            0 :         return false;
     193              :     }
     194              : }
     195              : 
     196              : 
     197              : bool
     198            0 : MSOverheadWire::isCharging() const {
     199            0 :     return myChargingVehicle;
     200              : }
     201              : 
     202              : 
     203              : void
     204          710 : MSOverheadWire::addChargeValueForOutput(double WCharged, MSDevice_ElecHybrid* elecHybrid, bool ischarging) {
     205          710 :     std::string status = "charging";
     206          710 :     if (!ischarging) {
     207              :         status = "not-charging";
     208              :     }
     209              : 
     210              :     // update total charge
     211          710 :     myTotalCharge += WCharged;
     212              :     // create charge row and insert it in myChargeValues
     213              :     const std::string vehID = elecHybrid->getHolder().getID();
     214              :     if (myChargeValues.count(vehID) == 0) {
     215           42 :         myChargedVehicles.push_back(vehID);
     216              :     }
     217          710 :     Charge C(MSNet::getInstance()->getCurrentTimeStep(), elecHybrid->getHolder().getID(), elecHybrid->getHolder().getVehicleType().getID(),
     218              :              status, WCharged, elecHybrid->getActualBatteryCapacity(), elecHybrid->getMaximumBatteryCapacity(),
     219         2130 :              elecHybrid->getVoltageOfOverheadWire(), myTotalCharge);
     220          710 :     myChargeValues[vehID].push_back(C);
     221         1420 : }
     222              : 
     223              : 
     224              : void
     225           48 : MSOverheadWire::writeOverheadWireSegmentOutput(OutputDevice& output) {
     226           48 :     int chargingSteps = 0;
     227              :     std::vector<SUMOTime> chargingSteps_list;
     228           70 :     for (const auto& item : myChargeValues) {
     229          202 :         for (auto it : item.second) {
     230          180 :             if (std::find(chargingSteps_list.begin(), chargingSteps_list.end(), it.timeStep) == chargingSteps_list.end()) {
     231          180 :                 chargingSteps_list.push_back(it.timeStep);
     232              :             }
     233          180 :         }
     234              :     }
     235           48 :     chargingSteps = (int) chargingSteps_list.size();
     236           48 :     output.openTag(SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
     237           48 :     output.writeAttr(SUMO_ATTR_ID, myID);
     238           48 :     if (getTractionSubstation() != nullptr) {
     239           42 :         output.writeAttr(SUMO_ATTR_TRACTIONSUBSTATIONID, getTractionSubstation()->getID());
     240              :     } else {
     241            6 :         output.writeAttr(SUMO_ATTR_TRACTIONSUBSTATIONID, "");
     242              :     }
     243           48 :     output.writeAttr(SUMO_ATTR_TOTALENERGYCHARGED, myTotalCharge);
     244              : 
     245              :     // RICE_TODO QUESTION myChargeValues.size() vs. chargingSteps
     246              :     // myChargeValues.size() is the number of vehicles charging sometimes from this overheadwire segment during simulation
     247              :     // chargingSteps is now the sum of chargingSteps of each vehicle, but takes also into account that at the given
     248              :     // step more than one vehicle may be charged from this segment
     249           48 :     output.writeAttr(SUMO_ATTR_CHARGINGSTEPS, chargingSteps);
     250              :     // output.writeAttr(SUMO_ATTR_EDGE, getLane().getEdge());
     251           48 :     output.writeAttr(SUMO_ATTR_LANE, getLane().getID());
     252              : 
     253              :     // Start writing
     254           48 :     if (myChargeValues.size() > 0) {
     255           44 :         for (const std::string& vehID : myChargedVehicles) {
     256              :             int iStart = 0;
     257           22 :             const auto& chargeSteps = myChargeValues[vehID];
     258           44 :             while (iStart < (int)chargeSteps.size()) {
     259           22 :                 int iEnd = iStart + 1;
     260           22 :                 double charged = chargeSteps[iStart].WCharged;
     261          180 :                 while (iEnd < (int)chargeSteps.size() && chargeSteps[iEnd].timeStep == chargeSteps[iEnd - 1].timeStep + DELTA_T) {
     262          158 :                     charged += chargeSteps[iEnd].WCharged;
     263          158 :                     iEnd++;
     264              :                 }
     265           22 :                 writeVehicle(output, chargeSteps, iStart, iEnd, charged);
     266              :                 iStart = iEnd;
     267              :             }
     268              :         }
     269              :     }
     270              :     // close charging station tag
     271           48 :     output.closeTag();
     272           48 : }
     273              : 
     274              : 
     275              : void
     276           22 : MSOverheadWire::writeVehicle(OutputDevice& out, const std::vector<Charge>& chargeSteps, int iStart, int iEnd, double charged) {
     277           22 :     const Charge& first = chargeSteps[iStart];
     278           22 :     out.openTag(SUMO_TAG_VEHICLE);
     279           22 :     out.writeAttr(SUMO_ATTR_ID, first.vehicleID);
     280           22 :     out.writeAttr(SUMO_ATTR_TYPE, first.vehicleType);
     281           22 :     out.writeAttr(SUMO_ATTR_TOTALENERGYCHARGED_VEHICLE, charged);
     282           22 :     out.writeAttr(SUMO_ATTR_CHARGINGBEGIN, time2string(first.timeStep));
     283           22 :     out.writeAttr(SUMO_ATTR_CHARGINGEND, time2string(chargeSteps[iEnd - 1].timeStep));
     284           22 :     out.writeAttr(SUMO_ATTR_MAXIMUMBATTERYCAPACITY, first.maxBatteryCapacity);
     285          202 :     for (int i = iStart; i < iEnd; i++) {
     286          180 :         const Charge& c = chargeSteps[i];
     287          180 :         out.openTag(SUMO_TAG_STEP);
     288          180 :         out.writeAttr(SUMO_ATTR_TIME, time2string(c.timeStep));
     289              :         // charge values
     290          180 :         out.writeAttr(SUMO_ATTR_CHARGING_STATUS, c.status);
     291          180 :         out.writeAttr(SUMO_ATTR_ENERGYCHARGED, c.WCharged);
     292          180 :         out.writeAttr(SUMO_ATTR_PARTIALCHARGE, c.totalEnergyCharged);
     293              :         // charging values of charging station in this timestep
     294          180 :         out.writeAttr(SUMO_ATTR_VOLTAGE, c.voltage);
     295              :         // battery status of vehicle
     296          180 :         out.writeAttr(SUMO_ATTR_ACTUALBATTERYCAPACITY, c.actualBatteryCapacity);
     297              :         // close tag timestep
     298          360 :         out.closeTag();
     299              :     }
     300           22 :     out.closeTag();
     301           22 : }
     302              : 
     303              : 
     304              : // ===========================================================================
     305              : //                                                        MSTractionSubstation
     306              : // ===========================================================================
     307              : // RICE_TODO Split MSTractionSubstation and MSOverheadWire?
     308              : // Probably no as the traction substation cannot stand alone and is always
     309              : // used together with the overhead wire. It is a bit disorganised, though.
     310              : 
     311           17 : MSTractionSubstation::MSTractionSubstation(const std::string& substationId, double voltage, double currentLimit) :
     312              :     Named(substationId),
     313           17 :     myChargingVehicle(false),
     314           17 :     myElecHybridCount(0),
     315           17 :     mySubstationVoltage(voltage),
     316           17 :     myCircuit(new Circuit(currentLimit)),
     317           17 :     myTotalEnergy(0)
     318           17 : {}
     319              : 
     320              : 
     321              : 
     322              : void
     323           38 : MSTractionSubstation::addOverheadWireSegmentToCircuit(MSOverheadWire* newOverheadWireSegment) {
     324           38 :     MSLane& lane = const_cast<MSLane&>(newOverheadWireSegment->getLane());
     325           38 :     if (lane.isInternal()) {
     326            0 :         return;
     327              :     }
     328              : 
     329              :     // RICE_TODO: consider the possibility of having more segments that belong to one lane.
     330              : 
     331           38 :     myOverheadWireSegments.push_back(newOverheadWireSegment);
     332              :     newOverheadWireSegment->setTractionSubstation(this);
     333              : 
     334           38 :     if (MSGlobals::gOverheadWireSolver) {
     335              : #ifdef HAVE_EIGEN
     336           38 :         Circuit* circuit = newOverheadWireSegment->getCircuit();
     337              :         const std::string segmentID = newOverheadWireSegment->getID();
     338              : 
     339           76 :         if (circuit->getNode("negNode_ground") == nullptr) {
     340           16 :             circuit->addNode("negNode_ground");
     341              :         }
     342              : 
     343              :         // convention: pNode is at the beginning of the wire segment, nNode is at the end of the wire segment
     344           38 :         newOverheadWireSegment->setCircuitStartNodePos(circuit->addNode("pNode_pos_" + segmentID));
     345           76 :         newOverheadWireSegment->setCircuitEndNodePos(circuit->addNode("nNode_pos_" + segmentID));
     346              :         // RICE_TODO: to use startPos and endPos of ovhdsegment: set the length of wire here properly
     347           38 :         newOverheadWireSegment->setCircuitElementPos(
     348           76 :             circuit->addElement("pos_" + segmentID,
     349           38 :                                 (newOverheadWireSegment->getLane().getLength()) * WIRE_RESISTIVITY,
     350              :                                 newOverheadWireSegment->getCircuitStartNodePos(),
     351              :                                 newOverheadWireSegment->getCircuitEndNodePos(),
     352              :                                 Element::ElementType::RESISTOR_traction_wire));
     353              : #else
     354              :         WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
     355              : #endif
     356              :     }
     357              : 
     358              :     const MSLane* connection = nullptr;
     359           38 :     std::string ovrhdSegmentID = ""; //ID of outgoing or incoming overhead wire segment
     360              :     MSOverheadWire* ovrhdSegment = nullptr; //pointer to outgoing or incoming overhead wire segment
     361              : 
     362              :     // RICE_TODO: simplify the code, two similar code-blocks below
     363              :     // RICE_TODO: to use startPos and endPos of ovhdsegment: if endPos+EPS > newOverheadWireSegment->getLane().getLength(),
     364              :     //            and the outgoing lanes will be skipped as there is no wire at the end of the lane
     365              : 
     366              :     /* in version before SUMO 1.0.1 the function getOutgoingLanes() returning MSLane* exists,
     367              :        in new version of SUMO the funciton getOutgoingViaLanes() returning MSLane* and MSEdge* pair exists */
     368              :     // std::vector<const MSLane*> outgoing = lane.getOutgoingLanes();
     369           38 :     const std::vector<std::pair<const MSLane*, const MSEdge*> > outgoingLanesAndEdges = lane.getOutgoingViaLanes();
     370              :     std::vector<const MSLane*> neigboringInnerLanes;
     371           38 :     neigboringInnerLanes.reserve(outgoingLanesAndEdges.size());
     372           89 :     for (size_t it = 0; it < outgoingLanesAndEdges.size(); ++it) {
     373           51 :         neigboringInnerLanes.push_back(outgoingLanesAndEdges[it].first);
     374              :     }
     375              : 
     376              :     // Check if there is an overhead wire segment on the outgoing lane. If not, do nothing, otherwise find connnecting internal lanes and
     377              :     // add all lanes (this and inner) to circuit
     378           89 :     for (std::vector<const MSLane*>::iterator it = neigboringInnerLanes.begin(); it != neigboringInnerLanes.end(); ++it) {
     379          102 :         ovrhdSegmentID = MSNet::getInstance()->getStoppingPlaceID(*it, NUMERICAL_EPS, SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
     380              :         // If the overhead wire segment is over the outgoing (not internal) lane
     381           51 :         if (ovrhdSegmentID != "" && !(*it)->isInternal()) {
     382           25 :             ovrhdSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace(ovrhdSegmentID, SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     383              :             // If the outgoing overhead wire segment belongs to the same substation as newOverheadWireSegment
     384              :             // RICE_TODO: define what happens if the traction stations are different (overhead wire should continue over inner segments but it is unclear to which traction substation or even circuit it should be connected)
     385           25 :             if (ovrhdSegment->getTractionSubstation() == newOverheadWireSegment->getTractionSubstation()) {
     386            2 :                 connection = lane.getInternalFollowingLane(*it);
     387            2 :                 if (connection != nullptr) {
     388              :                     //is connection a forbidden lane?
     389            6 :                     if (!(ovrhdSegment->getTractionSubstation()->isForbidden(connection) ||
     390            2 :                             ovrhdSegment->getTractionSubstation()->isForbidden(lane.getInternalFollowingLane(connection)) ||
     391            2 :                             ovrhdSegment->getTractionSubstation()->isForbidden(connection->getInternalFollowingLane(*it)))) {
     392            2 :                         addOverheadWireInnerSegmentToCircuit(newOverheadWireSegment, ovrhdSegment, connection, lane.getInternalFollowingLane(connection), connection->getInternalFollowingLane(*it));
     393              :                     }
     394              : 
     395              :                 } else {
     396            0 :                     if (MSGlobals::gOverheadWireSolver) {
     397              : #ifdef HAVE_EIGEN
     398              :                         Node* const unusedNode = newOverheadWireSegment->getCircuitEndNodePos();
     399            0 :                         for (MSOverheadWire* const ows : myOverheadWireSegments) {
     400            0 :                             if (ows->getCircuitStartNodePos() == unusedNode) {
     401              :                                 ows->setCircuitStartNodePos(ovrhdSegment->getCircuitStartNodePos());
     402              :                             }
     403            0 :                             if (ows->getCircuitEndNodePos() == unusedNode) {
     404              :                                 ows->setCircuitEndNodePos(ovrhdSegment->getCircuitStartNodePos());
     405              :                             }
     406              :                         }
     407            0 :                         newOverheadWireSegment->getCircuit()->replaceAndDeleteNode(unusedNode, ovrhdSegment->getCircuitStartNodePos());
     408              : #else
     409              :                         WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
     410              : #endif
     411              :                     }
     412              :                 }
     413              :             }
     414              :         }
     415              :     }
     416              : 
     417              :     // RICE_TODO: to use startPos and endPos of ovhdsegment: if startPos-EPS < 0,
     418              :     //            and the incoming lanes will be skipped as there is no wire at the beginning of the lane
     419              : 
     420              :     // This is the same as above, only this time checking the wires on some incoming lanes. If some of them
     421              :     // has an overhead wire segment, find the connnecting internal lanes and add all lanes (the internal
     422              :     // and this) to the circuit, otherwise do nothing.
     423           38 :     neigboringInnerLanes = lane.getNormalIncomingLanes();
     424          111 :     for (std::vector<const MSLane*>::iterator it = neigboringInnerLanes.begin(); it != neigboringInnerLanes.end(); ++it) {
     425          146 :         ovrhdSegmentID = MSNet::getInstance()->getStoppingPlaceID(*it, (*it)->getLength() - NUMERICAL_EPS, SUMO_TAG_OVERHEAD_WIRE_SEGMENT);
     426              :         // If the overhead wire segment is over the incoming (not internal) lane
     427           73 :         if (ovrhdSegmentID != "" && !(*it)->isInternal()) {
     428           19 :             ovrhdSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace(ovrhdSegmentID, SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     429              :             // If the incoming overhead wire segment belongs to the same substation as newOverheadWireSegment
     430              :             // RICE_TODO: define what happens if the traction stations are different (overhead wire should continue over inner segments but it is unclear to which traction substation or even circuit it should be connected)
     431           19 :             if (ovrhdSegment->getTractionSubstation() == newOverheadWireSegment->getTractionSubstation()) {
     432           17 :                 connection = (*it)->getInternalFollowingLane(&lane);
     433           17 :                 if (connection != nullptr) {
     434              :                     //is connection a forbidden lane?
     435            6 :                     if (!(ovrhdSegment->getTractionSubstation()->isForbidden(connection) ||
     436            2 :                             ovrhdSegment->getTractionSubstation()->isForbidden((*it)->getInternalFollowingLane(connection)) ||
     437            2 :                             ovrhdSegment->getTractionSubstation()->isForbidden(connection->getInternalFollowingLane(&lane)))) {
     438            2 :                         addOverheadWireInnerSegmentToCircuit(ovrhdSegment, newOverheadWireSegment, connection, (*it)->getInternalFollowingLane(connection), connection->getInternalFollowingLane(&lane));
     439              :                     }
     440              :                 } else {
     441           15 :                     if (MSGlobals::gOverheadWireSolver) {
     442              : #ifdef HAVE_EIGEN
     443              :                         Node* const unusedNode = newOverheadWireSegment->getCircuitStartNodePos();
     444           60 :                         for (MSOverheadWire* const ows : myOverheadWireSegments) {
     445           45 :                             if (ows->getCircuitStartNodePos() == unusedNode) {
     446              :                                 ows->setCircuitStartNodePos(ovrhdSegment->getCircuitEndNodePos());
     447              :                             }
     448           45 :                             if (ows->getCircuitEndNodePos() == unusedNode) {
     449              :                                 ows->setCircuitEndNodePos(ovrhdSegment->getCircuitEndNodePos());
     450              :                             }
     451              :                         }
     452           15 :                         newOverheadWireSegment->getCircuit()->replaceAndDeleteNode(unusedNode, ovrhdSegment->getCircuitEndNodePos());
     453              : #else
     454              :                         WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
     455              : #endif
     456              :                     }
     457              :                 }
     458              :             }
     459              :         }
     460              :     }
     461              : 
     462           38 :     if (MSGlobals::gOverheadWireSolver && newOverheadWireSegment->isThereVoltageSource()) {
     463              : #ifdef HAVE_EIGEN
     464           22 :         newOverheadWireSegment->getCircuit()->addElement(
     465           22 :             "voltage_source_" + newOverheadWireSegment->getID(),
     466              :             mySubstationVoltage,
     467              :             newOverheadWireSegment->getCircuitStartNodePos(),
     468              :             newOverheadWireSegment->getCircuit()->getNode("negNode_ground"),
     469              :             Element::ElementType::VOLTAGE_SOURCE_traction_wire);
     470              : #else
     471              :         WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
     472              : #endif
     473              :     }
     474           38 : }
     475              : 
     476              : 
     477              : void
     478            4 : MSTractionSubstation::addOverheadWireInnerSegmentToCircuit(MSOverheadWire* incomingSegment, MSOverheadWire* outgoingSegment, const MSLane* connection, const MSLane* frontConnection, const MSLane* behindConnection) {
     479            4 :     if (frontConnection == nullptr && behindConnection == nullptr) {
     480              :         // addOverheadWire from nNode of newOverheadWireSegment to pNode
     481            4 :         MSOverheadWire* innerSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + connection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     482            4 :         myOverheadWireSegments.push_back(innerSegment);
     483              :         innerSegment->setTractionSubstation(incomingSegment->getTractionSubstation());
     484            4 :         if (MSGlobals::gOverheadWireSolver) {
     485              : #ifdef HAVE_EIGEN
     486            8 :             Element* elem = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + connection->getID(), (connection->getLength()) * WIRE_RESISTIVITY, incomingSegment->getCircuitEndNodePos(), outgoingSegment->getCircuitStartNodePos(), Element::ElementType::RESISTOR_traction_wire);
     487              :             innerSegment->setCircuitElementPos(elem);
     488              :             innerSegment->setCircuitStartNodePos(incomingSegment->getCircuitEndNodePos());
     489              :             innerSegment->setCircuitEndNodePos(outgoingSegment->getCircuitStartNodePos());
     490              : #else
     491              :             UNUSED_PARAMETER(outgoingSegment);
     492              :             WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
     493              : #endif
     494              :         }
     495            0 :     } else if (frontConnection != nullptr && behindConnection == nullptr) {
     496            0 :         MSOverheadWire* innerSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + frontConnection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     497            0 :         MSOverheadWire* innerSegment2 = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + connection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     498              : 
     499              :         innerSegment->setTractionSubstation(incomingSegment->getTractionSubstation());
     500            0 :         myOverheadWireSegments.push_back(innerSegment);
     501              :         innerSegment2->setTractionSubstation(incomingSegment->getTractionSubstation());
     502            0 :         myOverheadWireSegments.push_back(innerSegment2);
     503              : 
     504            0 :         if (MSGlobals::gOverheadWireSolver) {
     505              : #ifdef HAVE_EIGEN
     506            0 :             Node* betweenFrontNode_pos = incomingSegment->getCircuit()->addNode("betweenFrontNode_pos_" + connection->getID());
     507            0 :             Element* elem = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + frontConnection->getID(), (frontConnection->getLength()) * WIRE_RESISTIVITY, incomingSegment->getCircuitEndNodePos(), betweenFrontNode_pos, Element::ElementType::RESISTOR_traction_wire);
     508            0 :             Element* elem2 = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + connection->getID(), (connection->getLength()) * WIRE_RESISTIVITY, betweenFrontNode_pos, outgoingSegment->getCircuitStartNodePos(), Element::ElementType::RESISTOR_traction_wire);
     509              : 
     510              :             innerSegment->setCircuitElementPos(elem);
     511              :             innerSegment->setCircuitStartNodePos(incomingSegment->getCircuitEndNodePos());
     512              :             innerSegment->setCircuitEndNodePos(betweenFrontNode_pos);
     513              : 
     514              :             innerSegment2->setCircuitElementPos(elem2);
     515              :             innerSegment2->setCircuitStartNodePos(betweenFrontNode_pos);
     516              :             innerSegment2->setCircuitEndNodePos(outgoingSegment->getCircuitStartNodePos());
     517              : #else
     518              :             WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
     519              : #endif
     520              :         }
     521            0 :     } else if (frontConnection == nullptr && behindConnection != nullptr) {
     522            0 :         MSOverheadWire* innerSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + connection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     523            0 :         MSOverheadWire* innerSegment2 = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + behindConnection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     524              : 
     525              :         innerSegment->setTractionSubstation(incomingSegment->getTractionSubstation());
     526            0 :         myOverheadWireSegments.push_back(innerSegment);
     527              :         innerSegment2->setTractionSubstation(incomingSegment->getTractionSubstation());
     528            0 :         myOverheadWireSegments.push_back(innerSegment2);
     529              : 
     530            0 :         if (MSGlobals::gOverheadWireSolver) {
     531              : #ifdef HAVE_EIGEN
     532            0 :             Node* betweenBehindNode_pos = incomingSegment->getCircuit()->addNode("betweenBehindNode_pos_" + connection->getID());
     533            0 :             Element* elem = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + connection->getID(), (connection->getLength()) * WIRE_RESISTIVITY, incomingSegment->getCircuitEndNodePos(), betweenBehindNode_pos, Element::ElementType::RESISTOR_traction_wire);
     534            0 :             Element* elem2 = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + behindConnection->getID(), (behindConnection->getLength()) * WIRE_RESISTIVITY, betweenBehindNode_pos, outgoingSegment->getCircuitStartNodePos(), Element::ElementType::RESISTOR_traction_wire);
     535              : 
     536              :             innerSegment->setCircuitElementPos(elem);
     537              :             innerSegment->setCircuitStartNodePos(incomingSegment->getCircuitEndNodePos());
     538              :             innerSegment->setCircuitEndNodePos(betweenBehindNode_pos);
     539              : 
     540              :             innerSegment2->setCircuitElementPos(elem2);
     541              :             innerSegment2->setCircuitStartNodePos(betweenBehindNode_pos);
     542              :             innerSegment2->setCircuitEndNodePos(outgoingSegment->getCircuitStartNodePos());
     543              : #else
     544              :             WRITE_WARNING(TL("Overhead circuit solver requested, but solver support (Eigen) not compiled in."));
     545              : #endif
     546              :         }
     547            0 :     } else if (frontConnection != nullptr && behindConnection != nullptr) {
     548            0 :         MSOverheadWire* innerSegment = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + frontConnection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     549            0 :         MSOverheadWire* innerSegment2 = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + connection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     550            0 :         MSOverheadWire* innerSegment3 = dynamic_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace("ovrhd_inner_" + behindConnection->getID(), SUMO_TAG_OVERHEAD_WIRE_SEGMENT));
     551              : 
     552              :         innerSegment->setTractionSubstation(incomingSegment->getTractionSubstation());
     553            0 :         myOverheadWireSegments.push_back(innerSegment);
     554              :         innerSegment2->setTractionSubstation(incomingSegment->getTractionSubstation());
     555            0 :         myOverheadWireSegments.push_back(innerSegment2);
     556              :         innerSegment3->setTractionSubstation(incomingSegment->getTractionSubstation());
     557            0 :         myOverheadWireSegments.push_back(innerSegment3);
     558              : 
     559            0 :         if (MSGlobals::gOverheadWireSolver) {
     560              : #ifdef HAVE_EIGEN
     561            0 :             Node* betweenFrontNode_pos = incomingSegment->getCircuit()->addNode("betweenFrontNode_pos_" + connection->getID());
     562            0 :             Node* betweenBehindNode_pos = incomingSegment->getCircuit()->addNode("betweenBehindNode_pos_" + connection->getID());
     563            0 :             Element* elem = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + frontConnection->getID(), (frontConnection->getLength()) * WIRE_RESISTIVITY, incomingSegment->getCircuitEndNodePos(), betweenFrontNode_pos, Element::ElementType::RESISTOR_traction_wire);
     564            0 :             Element* elem2 = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + connection->getID(), (connection->getLength()) * WIRE_RESISTIVITY, betweenFrontNode_pos, betweenBehindNode_pos, Element::ElementType::RESISTOR_traction_wire);
     565            0 :             Element* elem3 = incomingSegment->getCircuit()->addElement("pos_ovrhd_inner_" + behindConnection->getID(), (behindConnection->getLength()) * WIRE_RESISTIVITY, betweenBehindNode_pos, outgoingSegment->getCircuitStartNodePos(), Element::ElementType::RESISTOR_traction_wire);
     566              : 
     567              :             innerSegment->setCircuitElementPos(elem);
     568              :             innerSegment->setCircuitStartNodePos(incomingSegment->getCircuitEndNodePos());
     569              :             innerSegment->setCircuitEndNodePos(betweenFrontNode_pos);
     570              : 
     571              :             innerSegment2->setCircuitElementPos(elem2);
     572              :             innerSegment2->setCircuitStartNodePos(betweenFrontNode_pos);
     573              :             innerSegment2->setCircuitEndNodePos(betweenBehindNode_pos);
     574              : 
     575              :             innerSegment3->setCircuitElementPos(elem3);
     576              :             innerSegment3->setCircuitStartNodePos(betweenBehindNode_pos);
     577              :             innerSegment3->setCircuitEndNodePos(outgoingSegment->getCircuitStartNodePos());
     578              : #else
     579              :             WRITE_WARNING(TL("Overhead circuit solver requested, but solver support not compiled in."));
     580              : #endif
     581              :         }
     582              :     }
     583            4 : }
     584              : 
     585              : 
     586           11 : void MSTractionSubstation::addOverheadWireClampToCircuit(const std::string id, MSOverheadWire* startSegment, MSOverheadWire* endSegment) {
     587           11 :     PositionVector pos_start = startSegment->getLane().getShape();
     588           11 :     PositionVector pos_end = endSegment->getLane().getShape();
     589           11 :     double distance = pos_start[0].distanceTo2D(pos_end.back());
     590              : 
     591           11 :     if (distance > 10) {
     592            8 :         WRITE_WARNING("The distance between two overhead wires during adding overhead wire clamp '" + id + "' defined for traction substation '" + startSegment->getTractionSubstation()->getID() + "' is " + toString(distance) + " m.")
     593              :     }
     594           22 :     getCircuit()->addElement(id, distance * WIRE_RESISTIVITY, startSegment->getCircuitStartNodePos(), endSegment->getCircuitEndNodePos(), Element::ElementType::RESISTOR_traction_wire);
     595           11 : }
     596              : 
     597              : 
     598              : void
     599           42 : MSTractionSubstation::eraseOverheadWireSegmentFromCircuit(MSOverheadWire* oldSegment) {
     600              :     //myOverheadWireSegments.push_back(static_cast<MSOverheadWire*>(MSNet::getInstance()->getStoppingPlace(overheadWireSegmentID, SUMO_TAG_OVERHEAD_WIRE_SEGMENT)));
     601           42 :     myOverheadWireSegments.erase(std::remove(myOverheadWireSegments.begin(), myOverheadWireSegments.end(), oldSegment), myOverheadWireSegments.end());
     602           42 : }
     603              : 
     604              : 
     605              : bool
     606            0 : MSTractionSubstation::isCharging() const {
     607            0 :     return myChargingVehicle;
     608              : }
     609              : 
     610              : 
     611              : void
     612           22 : MSTractionSubstation::increaseElecHybridCount() {
     613           22 :     myElecHybridCount++;
     614           22 : }
     615              : 
     616              : 
     617              : void
     618           22 : MSTractionSubstation::decreaseElecHybridCount() {
     619           22 :     myElecHybridCount--;
     620           22 : }
     621              : 
     622              : 
     623            0 : void MSTractionSubstation::addForbiddenLane(MSLane* lane) {
     624            0 :     myForbiddenLanes.push_back(lane);
     625            0 : }
     626              : 
     627              : 
     628           24 : bool MSTractionSubstation::isForbidden(const MSLane* lane) {
     629           24 :     for (std::vector<MSLane*>::iterator it = myForbiddenLanes.begin(); it != myForbiddenLanes.end(); ++it) {
     630            0 :         if (lane == (*it)) {
     631              :             return true;
     632              :         }
     633              :     }
     634              :     return false;
     635              : }
     636              : 
     637              : 
     638              : void
     639           11 : MSTractionSubstation::addClamp(const std::string& id, MSOverheadWire* startPos, MSOverheadWire* endPos) {
     640           11 :     OverheadWireClamp clamp(id, startPos, endPos, false);
     641           11 :     myOverheadWireClamps.push_back(clamp);
     642           11 : }
     643              : 
     644              : 
     645              : MSTractionSubstation::OverheadWireClamp*
     646           22 : MSTractionSubstation::findClamp(std::string clampId) {
     647           46 :     for (auto it = myOverheadWireClamps.begin(); it != myOverheadWireClamps.end(); it++) {
     648           35 :         if (it->id == clampId) {
     649              :             return &(*it);
     650              :         }
     651              :     }
     652              :     return nullptr;
     653              : }
     654              : 
     655              : 
     656              : bool
     657            8 : MSTractionSubstation::isAnySectionPreviouslyDefined() {
     658            8 :     if (myOverheadWireSegments.size() > 0 || myForbiddenLanes.size() > 0 || getCircuit()->getLastId() > 0) {
     659            0 :         return true;
     660              :     }
     661              :     return false;
     662              : }
     663              : 
     664              : 
     665              : void
     666          180 : MSTractionSubstation::addSolvingCircuitToEndOfTimestepEvents() {
     667          180 :     if (!myChargingVehicle) {
     668              :         // myCommandForSolvingCircuit = new StaticCommand<MSTractionSubstation>(&MSTractionSubstation::solveCircuit);
     669          180 :         myCommandForSolvingCircuit = new WrappingCommand<MSTractionSubstation>(this, &MSTractionSubstation::solveCircuit);
     670          180 :         MSNet::getInstance()->getEndOfTimestepEvents()->addEvent(myCommandForSolvingCircuit);
     671          180 :         setChargingVehicle(true);
     672              :     }
     673          180 : }
     674              : 
     675              : 
     676              : SUMOTime
     677          180 : MSTractionSubstation::solveCircuit(SUMOTime /*currentTime*/) {
     678              :     /*Circuit evaluation*/
     679          180 :     setChargingVehicle(false);
     680              : 
     681              : #ifdef HAVE_EIGEN
     682              : 
     683              :     // RICE_TODO: Allow for updating current limits in each time step if changed e.g. via traci or similar
     684              :     // getCircuit()->setCurrentLimit(myCurrentLimit);
     685              : 
     686              :     // Solve the electrical circuit
     687          180 :     myCircuit->solve();
     688              : 
     689          180 :     if (myCircuit->getAlphaBest() != 1.0) {
     690            0 :         WRITE_WARNINGF(TL("The requested total power could not be delivered by the overhead wire. Only % of originally requested power was provided."), toString(myCircuit->getAlphaBest()));
     691              :     }
     692              : #endif
     693              : 
     694              :     // RICE_TODO: verify what happens if eigen is not defined?
     695              :     // Note: addSolvingCircuitToEndOfTimestepEvents() and thus solveCircuit() should be called from notifyMove only if eigen is defined.
     696          180 :     addChargeValueForOutput(WATT2WATTHR(myCircuit->getTotalPowerOfCircuitSources()), myCircuit->getTotalCurrentOfCircuitSources(), myCircuit->getAlphaBest(), myCircuit->getAlphaReason());
     697              : 
     698          360 :     for (auto* it : myElecHybrid) {
     699              : 
     700              :         Element* vehElem = it->getVehElem();
     701          180 :         double voltage = vehElem->getVoltage();
     702          180 :         double current = -vehElem->getCurrent();  // Vehicle is a power source, hence its current (returned by getCurrent()) flows in opposite direction
     703              : 
     704          180 :         it->setCurrentFromOverheadWire(current);
     705          180 :         it->setVoltageOfOverheadWire(voltage);
     706              : 
     707              :         // Calulate energy charged
     708          180 :         double energyIn = WATT2WATTHR(voltage * current);  // [Wh]
     709              : 
     710              :         // Compute energy charged into/from battery considering recuperation and propulsion efficiency (not considering battery capacity)
     711          180 :         double energyCharged = it->computeChargedEnergy(energyIn);
     712              : 
     713              :         // Update energy saved in the battery pack and return trully charged energy considering limits of battery
     714          180 :         double realEnergyCharged = it->storeEnergyToBattery(energyCharged);
     715              : 
     716          180 :         it->setEnergyCharged(realEnergyCharged);
     717              : 
     718              :         // Add energy wasted to the total sum
     719          180 :         it->updateTotalEnergyWasted(energyCharged - realEnergyCharged);
     720              :         // Add the energy provided by the overhead wire segment to the output of the segment
     721          180 :         it->getActOverheadWireSegment()->addChargeValueForOutput(energyIn, it);
     722              :     }
     723              : 
     724          180 :     return 0;
     725              : }
     726              : 
     727              : void
     728          180 : MSTractionSubstation::addChargeValueForOutput(double energy, double current, double alpha, Circuit::alphaFlag alphaReason) {
     729          180 :     std::string status = "";
     730              : 
     731          180 :     myTotalEnergy += energy; //[Wh]
     732              : 
     733          180 :     std::string vehicleIDs = "";
     734          360 :     for (std::vector<MSDevice_ElecHybrid*>::iterator it = myElecHybrid.begin(); it != myElecHybrid.end(); it++) {
     735          360 :         vehicleIDs += (*it)->getID() + " ";
     736              :     }
     737              :     //vehicleIDs.erase(vehicleIDs.end());
     738              :     // TODO vehicleIDs should not be empty, but in some case, it is (due to teleporting of vehicle?)
     739          180 :     if (!vehicleIDs.empty()) {
     740              :         vehicleIDs.pop_back();
     741              :     }
     742              : 
     743          180 :     std::string currents = "";
     744          180 :     currents = myCircuit->getCurrentsOfCircuitSource(currents);
     745              : 
     746              :     // create charge row and insert it in myChargeValues
     747              :     chargeTS C(MSNet::getInstance()->getCurrentTimeStep(), getID(), vehicleIDs, energy, current, currents, mySubstationVoltage, status,
     748          720 :                (int)myElecHybrid.size(), (int)getCircuit()->getNumVoltageSources(), alpha, alphaReason);
     749          180 :     myChargeValues.push_back(C);
     750          360 : }
     751              : 
     752              : void
     753            8 : MSTractionSubstation::writeTractionSubstationOutput(OutputDevice& output) {
     754            8 :     output.openTag(SUMO_TAG_TRACTION_SUBSTATION);
     755            8 :     output.writeAttr(SUMO_ATTR_ID, myID);
     756            8 :     output.writeAttr(SUMO_ATTR_TOTALENERGYCHARGED, myTotalEnergy); //[Wh]
     757            8 :     double length = 0;
     758           50 :     for (auto it = myOverheadWireSegments.begin(); it != myOverheadWireSegments.end(); it++) {
     759           42 :         length += (*it)->getEndLanePosition() - (*it)->getBeginLanePosition();
     760              :     }
     761            8 :     output.writeAttr(SUMO_ATTR_LENGTH, length);
     762           16 :     output.writeAttr("numVoltageSources", myCircuit->getNumVoltageSources());
     763           16 :     output.writeAttr("numClamps", myOverheadWireClamps.size());
     764            8 :     output.writeAttr(SUMO_ATTR_CHARGINGSTEPS, myChargeValues.size());
     765              : 
     766              :     // start writting
     767            8 :     if (myChargeValues.size() > 0) {
     768              :         // iterate over charging values
     769          184 :         for (std::vector<MSTractionSubstation::chargeTS>::const_iterator i = myChargeValues.begin(); i != myChargeValues.end(); i++) {
     770              :             // open tag for timestep and write all parameters
     771          180 :             output.openTag(SUMO_TAG_STEP);
     772          180 :             output.writeAttr(SUMO_ATTR_TIME, time2string(i->timeStep));
     773              :             // charge values
     774          180 :             output.writeAttr("vehicleIDs", i->vehicleIDs);
     775          180 :             output.writeAttr("numVehicles", i->numVehicles);
     776              :             // same number of numVoltageSources for all time, parameter is written in the superordinate tag
     777              :             //output.writeAttr("numVoltageSources", i->numVoltageSources);
     778              :             // charging status is always ""
     779              :             //output.writeAttr(SUMO_ATTR_CHARGING_STATUS, i->status);
     780          180 :             output.writeAttr(SUMO_ATTR_ENERGYCHARGED, i->energy);
     781          180 :             output.writeAttr(SUMO_ATTR_CURRENTFROMOVERHEADWIRE, i->current);
     782          180 :             output.writeAttr("currents", i->currentsString);
     783              :             // charging values of charging station in this timestep
     784          180 :             output.writeAttr(SUMO_ATTR_VOLTAGE, i->voltage);
     785          180 :             output.writeAttr(SUMO_ATTR_ALPHACIRCUITSOLVER, i->alpha);
     786          180 :             output.writeAttr("alphaFlag", i->alphaReason);
     787              :             // close tag timestep
     788          360 :             output.closeTag();
     789              :             // update timestep of charge
     790              :         }
     791              :     }
     792              :     // close charging station tag
     793            8 :     output.closeTag();
     794            8 : }
     795              : 
     796              : /****************************************************************************/
        

Generated by: LCOV version 2.0-1