1# Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.org/sumo
2# Copyright (C) 2017-2019 German Aerospace Center (DLR) and others.
3# This program and the accompanying materials
4# are made available under the terms of the Eclipse Public License v2.0
5# which accompanies this distribution, and is available at
6# http://www.eclipse.org/legal/epl-v20.html
7# SPDX-License-Identifier: EPL-2.0
8
9# @file    _config.py
10# @author Leonhard Luecken
11# @date   2017-04-09
12# @version $Id$
13
14
15from collections import defaultdict
16import os
17import sys
18import xml.etree.ElementTree as ET
19
20if 'SUMO_HOME' in os.environ:
21    tools = os.path.join(os.environ['SUMO_HOME'], 'tools')
22    sys.path.append(tools)
23else:
24    sys.exit("please declare environment variable 'SUMO_HOME'")
25
26from simpla._platoonmode import PlatoonMode  # noqa
27import simpla._reporting as rp  # noqa
28from simpla import SimplaException  # noqa
29
30warn = rp.Warner("Config")
31report = rp.Reporter("Config")
32
33
34def initDefaults():
35    '''
36    Init default values for the configuration parameters.
37    They are overriden by specification in a configuration file (see load() method).
38    '''
39    global CONTROL_RATE, VEH_SELECTORS, MAX_PLATOON_GAP, CATCHUP_DIST, PLATOON_SPLIT_TIME
40    global VTYPE_FILE, PLATOON_VTYPES, LC_MODE, SPEEDFACTOR, SWITCH_IMPATIENCE_FACTOR
41
42    # Rate for updating the platoon manager checks and advices
43    CONTROL_RATE = 1.0
44
45    # specify substring for vtypes, that should be controlled by platoon manager
46    VEH_SELECTORS = [""]
47
48    # Distance in meters below which a vehicle joins a leading platoon
49    MAX_PLATOON_GAP = 15.0
50
51    # Distance in meters below which a vehicle tries to catch up with a platoon in front
52    CATCHUP_DIST = 50.0
53
54    # Latency time in secs. until a platoon is split if vehicles exceed PLATOON_SPLIT_DISTANCE to their
55    # leaders within a platoon (or if they are not the direct follower),
56    # or drive on different lanes than their leader within the platoon
57    PLATOON_SPLIT_TIME = 3.0
58
59    # The switch impatience factor determines the magnitude of the effect
60    # that an increasing waiting time has on the active speed factor of a vehicle:
61    # activeSpeedFactor = modeSpecificSpeedFactor/(1+impatienceFactor*waitingTime)
62    SWITCH_IMPATIENCE_FACTOR = 0.1
63
64    # Lanechange modes for the different platooning modes
65    LC_MODE = {
66        # for solitary mode use default mode
67        PlatoonMode.NONE: 0b1001010101,
68        # for platoon leader use default mode
69        PlatoonMode.LEADER: 0b1001010101,
70        # for platoon follower do not change lanes, except for traci commands
71        # of for strategic reasons (these override traci)
72        PlatoonMode.FOLLOWER: 0b1000000010,
73        # for platoon catchup as for follower
74        PlatoonMode.CATCHUP: 0b1000000010,
75        # for platoon catchup follower as for follower
76        PlatoonMode.CATCHUP_FOLLOWER: 0b1000000010
77    }
78
79    # speedfactors for the different platooning modes
80    SPEEDFACTOR = {
81        PlatoonMode.NONE: None,  # is not altered
82        PlatoonMode.LEADER: 1.0,
83        PlatoonMode.FOLLOWER: 1.0,
84        PlatoonMode.CATCHUP: 1.1,
85        PlatoonMode.CATCHUP_FOLLOWER: None  # is set to the same as for catchup mode below if not explicitely set
86    }
87
88    # file with vtype maps for platoon types
89    VTYPE_FILE = ""
90
91    # map of original to platooning vTypes
92    PLATOON_VTYPES = defaultdict(dict)
93
94
95# perform initialization
96initDefaults()
97
98
99def loadVTypeMap(fn):
100    '''loadVTypeMap(string) -> dict
101
102    Reads lines of the form 'origMode:leadMode:followMode:catchupMode:catchupFollowMode' (last three elements
103    can be omitted) from a given file and write corresponding key:value pairs to PLATOON_VTYPES
104    '''
105    global PLATOON_VTYPES
106
107    with open(fn, "r") as f:
108        #         if rp.VERBOSITY >= 2:
109        if rp.VERBOSITY >= 2:
110            report("Loading vehicle type mappings from file '%s'..." % fn, True)
111        splits = [l.split(":") for l in f.readlines()]
112        NrBadLines = 0
113        for j, spl in enumerate(splits):
114            if len(spl) >= 2 and len(spl) <= 5:
115                stripped = list(map(lambda x: x.strip(), spl))
116                origType = stripped[0]
117                if origType == "":
118                    raise SimplaException("Original vType must be specified in line %s of vType file '%s'!" % (j, fn))
119                if rp.VERBOSITY >= 2:
120                    report("original type: '%s'" % origType, True)
121
122                leadType = stripped[1]
123                if leadType == "":
124                    raise SimplaException(
125                        "Platoon leader vType must be specified in line %s of vType file '%s'!" % (j, fn))
126                if rp.VERBOSITY >= 2:
127                    report("platoon leader type: '%s'" % leadType, True)
128
129                if (len(stripped) >= 3 and stripped[2] != ""):
130                    followerType = stripped[2]
131                    if rp.VERBOSITY >= 2:
132                        report("platoon follower type: '%s'" % followerType, True)
133                else:
134                    followerType = None
135
136                if (len(stripped) >= 4 and stripped[3] != ""):
137                    catchupType = stripped[3]
138                    if rp.VERBOSITY >= 2:
139                        report("catchup type: '%s'" % catchupType, True)
140                else:
141                    catchupType = None
142
143                if len(stripped) >= 5 and stripped[4] != "":
144                    catchupFollowerType = stripped[4]
145                    if rp.VERBOSITY >= 2:
146                        report("catchup follower type: '%s'" % catchupFollowerType, True)
147                else:
148                    catchupFollowerType = None
149
150                PLATOON_VTYPES[origType][PlatoonMode.NONE] = origType
151                PLATOON_VTYPES[origType][PlatoonMode.LEADER] = leadType
152                PLATOON_VTYPES[origType][PlatoonMode.FOLLOWER] = followerType
153                PLATOON_VTYPES[origType][PlatoonMode.CATCHUP] = catchupType
154                PLATOON_VTYPES[origType][PlatoonMode.CATCHUP_FOLLOWER] = catchupFollowerType
155            else:
156                NrBadLines += 1
157        if NrBadLines > 0:
158            if rp.VERBOSITY >= 1:
159                warn(("vType file '%s' contained %d lines that were not parsed into a colon-separated " +
160                      "sequence of strings!") % (fn, NrBadLines))
161
162
163def load(filename):
164    '''load(string)
165
166    This loads configuration parameters from a file and overwrites default values.
167    '''
168    global CONTROL_RATE, VEH_SELECTORS, MAX_PLATOON_GAP, CATCHUP_DIST, PLATOON_SPLIT_TIME
169    global VTYPE_FILE, PLATOON_VTYPES, LC_MODE, SPEEDFACTOR, SWITCH_IMPATIENCE_FACTOR
170
171    configDir = os.path.dirname(filename)
172    configElements = ET.parse(filename).getroot().getchildren()
173    parsedTags = []
174    for e in configElements:
175        parsedTags.append(e.tag)
176        if e.tag == "verbosity":
177            if hasAttributes(e):
178                verbosity = int(list(e.attrib.values())[0])
179                if verbosity in range(5):
180                    rp.VERBOSITY = verbosity
181                else:
182                    if rp.VERBOSITY >= 1:
183                        warn("Verbosity must be one of %s! Ignoring given value: %s" %
184                             (str(list(range(5))), verbosity), True)
185        elif e.tag == "controlRate":
186            if hasAttributes(e):
187                rate = float(list(e.attrib.values())[0])
188                if rate <= 0.:
189                    if rp.VERBOSITY >= 1:
190                        warn("Parameter controlRate must be positive. Ignoring given value: %s" % (rate), True)
191                else:
192                    CONTROL_RATE = float(rate)
193        elif e.tag == "vehicleSelectors":
194            if hasAttributes(e):
195                VEH_SELECTORS = list(map(lambda x: x.strip(), list(e.attrib.values())[0].split(",")))
196        elif e.tag == "maxPlatoonGap":
197            if hasAttributes(e):
198                maxgap = float(list(e.attrib.values())[0])
199                if maxgap <= 0:
200                    if rp.VERBOSITY >= 1:
201                        warn("Parameter maxPlatoonGap must be positive. Ignoring given value: %s" % (maxgap), True)
202                else:
203                    MAX_PLATOON_GAP = maxgap
204        elif e.tag == "catchupDist":
205            if hasAttributes(e):
206                dist = float(list(e.attrib.values())[0])
207                if maxgap <= 0:
208                    if rp.VERBOSITY >= 1:
209                        warn("Parameter catchupDist must be positive. Ignoring given value: %s" % (dist), True)
210                else:
211                    CATCHUP_DIST = dist
212        elif e.tag == "switchImpatienceFactor":
213            if hasAttributes(e):
214                impfact = float(list(e.attrib.values())[0])
215                if impfact < 0:
216                    if rp.VERBOSITY >= 1:
217                        warn("Parameter switchImpatienceFactor must be non-negative. Ignoring given value: %s" %
218                             (impfact), True)
219                else:
220                    SWITCH_IMPATIENCE_FACTOR = impfact
221        elif e.tag == "platoonSplitTime":
222            if hasAttributes(e):
223                splittime = float(list(e.attrib.values())[0])
224                if splittime < 0:
225                    if rp.VERBOSITY >= 1:
226                        warn("Parameter platoonSplitTime must be non-negative. Ignoring given value: %s" %
227                             (splittime), True)
228                else:
229                    PLATOON_SPLIT_TIME = splittime
230        elif e.tag == "lcMode":
231            if hasAttributes(e):
232                if ("leader" in e.attrib):
233                    if isValidLCMode(int(e.attrib["leader"])):
234                        LC_MODE[PlatoonMode.LEADER] = int(e.attrib["leader"])
235                if ("follower" in e.attrib):
236                    if isValidLCMode(int(e.attrib["follower"])):
237                        LC_MODE[PlatoonMode.FOLLOWER] = int(e.attrib["follower"])
238                if ("catchup" in e.attrib):
239                    if isValidLCMode(int(e.attrib["catchup"])):
240                        LC_MODE[PlatoonMode.CATCHUP] = int(e.attrib["catchup"])
241                if ("catchupFollower" in e.attrib):
242                    if isValidLCMode(int(e.attrib["catchupFollower"])):
243                        LC_MODE[PlatoonMode.CATCHUP_FOLLOWER] = int(e.attrib["catchupFollower"])
244                if ("original" in e.attrib):
245                    if isValidLCMode(int(e.attrib["original"])):
246                        LC_MODE[PlatoonMode.NONE] = int(e.attrib["original"])
247        elif e.tag == "speedFactor":
248            if hasAttributes(e):
249                if ("leader" in e.attrib):
250                    if isValidSpeedFactor(float(e.attrib["leader"])):
251                        SPEEDFACTOR[PlatoonMode.LEADER] = float(e.attrib["leader"])
252                if ("follower" in e.attrib):
253                    if isValidSpeedFactor(float(e.attrib["follower"])):
254                        SPEEDFACTOR[PlatoonMode.FOLLOWER] = float(e.attrib["follower"])
255                if ("catchup" in e.attrib):
256                    if isValidSpeedFactor(float(e.attrib["catchup"])):
257                        SPEEDFACTOR[PlatoonMode.CATCHUP] = float(e.attrib["catchup"])
258                if ("catchupFollower" in e.attrib):
259                    if isValidSpeedFactor(float(e.attrib["catchupFollower"])):
260                        SPEEDFACTOR[PlatoonMode.CATCHUP_FOLLOWER] = float(e.attrib["catchupFollower"])
261                if ("original" in e.attrib):
262                    if isValidSpeedFactor(float(e.attrib["original"])):
263                        SPEEDFACTOR[PlatoonMode.NONE] = float(e.attrib["original"])
264        elif e.tag == "vTypeMapFile":
265            if hasAttributes(e):
266                fn = os.path.join(configDir, list(e.attrib.values())[0])
267                if not os.path.isfile(fn):
268                    raise SimplaException("Given vTypeMapFile '%s' does not exist." % fn)
269                VTYPE_FILE = fn
270        elif e.tag == "vTypeMap":
271            if hasAttributes(e):
272                if "original" not in e.attrib:
273                    warn("vTypeMap must specify original type. Ignoring malformed vTypeMap element.", True)
274                else:
275                    origType = e.attrib["original"]
276                    PLATOON_VTYPES[origType][PlatoonMode.NONE] = origType
277                    if ("leader" in e.attrib):
278                        leaderType = e.attrib["leader"]
279                        PLATOON_VTYPES[origType][PlatoonMode.LEADER] = leaderType
280                        # report("Registering vtype map '%s':'%s'"%(origType,leaderType), True)
281                    if ("follower" in e.attrib):
282                        followerType = e.attrib["follower"]
283                        PLATOON_VTYPES[origType][PlatoonMode.FOLLOWER] = followerType
284                        # report("Registering vtype map '%s':'%s'"%(origType,followerType), True)
285                    if ("catchup" in e.attrib):
286                        catchupType = e.attrib["catchup"]
287                        PLATOON_VTYPES[origType][PlatoonMode.CATCHUP] = catchupType
288                        # report("Registering vtype map '%s':'%s'"%(origType,followerType), True)
289                    if ("catchupFollower" in e.attrib):
290                        catchupFollowerType = e.attrib["catchupFollower"]
291                        PLATOON_VTYPES[origType][PlatoonMode.CATCHUP_FOLLOWER] = catchupFollowerType
292                        # report("Registering vtype map '%s':'%s'"%(origType,followerType), True)
293        elif rp.VERBOSITY >= 1:
294            warn("Encountered unknown configuration parameter '%s'!" % e.tag, True)
295
296    if "vTypeMapFile" in parsedTags:
297        # load vType mapping from file
298        loadVTypeMap(VTYPE_FILE)
299
300    if SPEEDFACTOR[PlatoonMode.CATCHUP_FOLLOWER] is None:
301        # if unset, set speedfactor for catchupfollower mode to the same as in catchup mode
302        SPEEDFACTOR[PlatoonMode.CATCHUP_FOLLOWER] = SPEEDFACTOR[PlatoonMode.CATCHUP]
303
304
305def hasAttributes(element):
306    '''
307    check if xml element has at least one attribute
308    '''
309    # print("Checking element {tag}:\n{attributes}".format(tag=element.tag, attributes=str(element.attrib)))
310    if len(element.attrib) == 0:
311        warn("No attributes found for tag '%s'." % element.tag, True)
312        return False
313    else:
314        return True
315
316
317def isValidLCMode(mode):
318    if 0 <= mode <= 1023:
319        return True
320    else:
321        warn("Given lane change mode '%d' lies out of admissible range [0,255]. Using default mode instead." % (
322            mode), True)
323        return False
324
325
326def isValidSpeedFactor(value):
327    if 0 < value:
328        return True
329    else:
330        warn("Given speedFactor %s is invalid. Using default value." % (value), True)
331        return False
332