Merge remote-tracking branch 'origin/master'
This commit is contained in:
commit
afc919a21d
@ -1,3 +1,4 @@
|
||||
import os
|
||||
from bson import json_util
|
||||
from flask import Flask, request
|
||||
from flask_pymongo import PyMongo
|
||||
@ -6,6 +7,38 @@ app = Flask(__name__)
|
||||
app.config["MONGO_URI"] = "mongodb://mongo:27017/entities"
|
||||
mongo = PyMongo(app)
|
||||
|
||||
CAR1_SV = int(os.environ.get('DSE2021_CAR1_SV', 130))
|
||||
CAR2_SV = int(os.environ.get('DSE2021_CAR2_SV', 130))
|
||||
CAR3_SV = int(os.environ.get('DSE2021_CAR3_SV', 130))
|
||||
|
||||
CAR1_SD = int(os.environ.get('DSE2021_CAR1_SD', 300))
|
||||
CAR2_SD = int(os.environ.get('DSE2021_CAR2_SD', 500))
|
||||
CAR3_SD = int(os.environ.get('DSE2021_CAR3_SD', 400))
|
||||
|
||||
CAR1_ST = int(os.environ.get('DSE2021_CAR1_ST', 10))
|
||||
CAR2_ST = int(os.environ.get('DSE2021_CAR2_ST', 15))
|
||||
CAR3_ST = int(os.environ.get('DSE2021_CAR3_ST', 25))
|
||||
|
||||
TL1_R = int(os.environ.get('DSE2021_TL1_R', 2000))
|
||||
TL2_R = int(os.environ.get('DSE2021_TL2_R', 800))
|
||||
TL3_R = int(os.environ.get('DSE2021_TL3_R', 1000))
|
||||
|
||||
mongo.db.trafficLights.update_one({"id": "1"}, {"$set": {"range": TL1_R}})
|
||||
mongo.db.trafficLights.update_one({"id": "2"}, {"$set": {"range": TL2_R}})
|
||||
mongo.db.trafficLights.update_one({"id": "3"}, {"$set": {"range": TL3_R}})
|
||||
|
||||
mongo.db.cars.update_one({"vin": "SCBFR7ZA5CC072256"}, {"$set": {"startingVelocity": CAR1_SV}})
|
||||
mongo.db.cars.update_one({"vin": "5GZCZ43D13S812715"}, {"$set": {"startingVelocity": CAR2_SV}})
|
||||
mongo.db.cars.update_one({"vin": "5GZCZ43D13S812716"}, {"$set": {"startingVelocity": CAR3_SV}})
|
||||
|
||||
mongo.db.cars.update_one({"vin": "SCBFR7ZA5CC072256"}, {"$set": {"startingDistance": CAR1_SD}})
|
||||
mongo.db.cars.update_one({"vin": "5GZCZ43D13S812715"}, {"$set": {"startingDistance": CAR2_SD}})
|
||||
mongo.db.cars.update_one({"vin": "5GZCZ43D13S812716"}, {"$set": {"startingDistance": CAR3_SD}})
|
||||
|
||||
mongo.db.cars.update_one({"vin": "SCBFR7ZA5CC072256"}, {"$set": {"startingTime": CAR1_ST}})
|
||||
mongo.db.cars.update_one({"vin": "5GZCZ43D13S812715"}, {"$set": {"startingTime": CAR2_ST}})
|
||||
mongo.db.cars.update_one({"vin": "5GZCZ43D13S812716"}, {"$set": {"startingTime": CAR3_ST}})
|
||||
|
||||
|
||||
@app.route('/api/v1/resources/cars', methods=['GET'])
|
||||
def get_cars():
|
||||
|
||||
@ -2,25 +2,16 @@
|
||||
{
|
||||
"oem": "BENTLEY",
|
||||
"modelType": "Continental",
|
||||
"vin": "SCBFR7ZA5CC072256",
|
||||
"startingVelocity": 130,
|
||||
"startingDistance": 300,
|
||||
"startingTime": 10
|
||||
"vin": "SCBFR7ZA5CC072256"
|
||||
},
|
||||
{
|
||||
"oem": "SATURN",
|
||||
"modelType": "Vue",
|
||||
"vin": "5GZCZ43D13S812715",
|
||||
"startingVelocity": 130,
|
||||
"startingDistance": 500,
|
||||
"startingTime": 15
|
||||
"vin": "5GZCZ43D13S812715"
|
||||
},
|
||||
{
|
||||
"oem": "SATURN",
|
||||
"modelType": "Vue2",
|
||||
"vin": "5GZCZ43D13S812716",
|
||||
"startingVelocity": 130,
|
||||
"startingDistance": 100,
|
||||
"startingTime": 25
|
||||
"vin": "5GZCZ43D13S812716"
|
||||
}
|
||||
]
|
||||
@ -2,22 +2,19 @@
|
||||
{
|
||||
"id": "1",
|
||||
"location": [16.20719, 47.89584],
|
||||
"range": 2000,
|
||||
"switchingTime": 5,
|
||||
"switchingTime": 26,
|
||||
"color": "RED"
|
||||
},
|
||||
{
|
||||
"id": "2",
|
||||
"location": [16.20814, 47.90937],
|
||||
"range": 800,
|
||||
"switchingTime": 15,
|
||||
"switchingTime": 16,
|
||||
"color": "GREEN"
|
||||
},
|
||||
{
|
||||
"id": "3",
|
||||
"location": [16.20917, 47.92703],
|
||||
"range": 1000,
|
||||
"switchingTime": 10,
|
||||
"switchingTime": 20,
|
||||
"color": "RED"
|
||||
}
|
||||
]
|
||||
@ -16,7 +16,7 @@ from event_logger import EventLogger
|
||||
class TestEventLogger(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.el = EventLogger(StrictRedis(), False, False)
|
||||
self.timestamp = datetime.datetime.now()
|
||||
self.timestamp = datetime.datetime.utcnow()
|
||||
|
||||
def test_unpack_daf(self):
|
||||
daf = DAF(vehicle_identification_number='my_vin',
|
||||
@ -60,7 +60,7 @@ class TestEventLogger(unittest.TestCase):
|
||||
self.assertEqual(message, json.dumps(unknown))
|
||||
|
||||
def test_unpack_unknown_object(self):
|
||||
obj = datetime.datetime.now()
|
||||
obj = datetime.datetime.utcnow()
|
||||
|
||||
key, message = self.el._unpack_message_to_log(pickle.dumps(obj))
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import pickle
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
from circuitbreaker import circuit
|
||||
@ -13,7 +14,7 @@ from pika.exceptions import AMQPConnectionError
|
||||
|
||||
SWITCHING_TIME = 15
|
||||
# Scale speed of switching by factor x
|
||||
SCALING = 1
|
||||
SCALING = int(os.environ.get('DSE2021_SCALING', 1))
|
||||
|
||||
|
||||
class TrafficLight:
|
||||
@ -56,23 +57,10 @@ class TrafficLight:
|
||||
|
||||
def start(self):
|
||||
"""
|
||||
Starts the traffic light by spawning a new thread. It toggles its state every self.switching_time / SCALING
|
||||
seconds. When it switches, it notifies the orchestrator with self.send_status_update().
|
||||
Starts the traffic light by spawning a new thread..
|
||||
"""
|
||||
self.running = True
|
||||
|
||||
def switcher():
|
||||
num_colors = len(TrafficLightColor)
|
||||
# set current color to the one before starting color, because switch immediately happens in loop
|
||||
# therefore it sleeps on the end of the loop and first status is immediately sent to msg broker
|
||||
self.current_color = TrafficLightColor((self._starting_color.value - 1) % num_colors)
|
||||
while self.running:
|
||||
self.current_color = TrafficLightColor((self.current_color.value + 1) % num_colors)
|
||||
self.last_switch = datetime.now()
|
||||
self.send_status_update()
|
||||
time.sleep(self.switching_time / SCALING)
|
||||
|
||||
self._t = threading.Thread(target=switcher)
|
||||
self._t = threading.Thread(target=self._switcher)
|
||||
self._t.start()
|
||||
|
||||
def stop(self):
|
||||
@ -91,6 +79,21 @@ class TrafficLight:
|
||||
self._tl_mb.send(pickle.dumps(
|
||||
TrafficLightState(tlid=self.tlid, color=self.current_color, last_switch=self.last_switch)))
|
||||
|
||||
def _switcher(self):
|
||||
"""
|
||||
Toggles the traffic lights state every self.switching_time / SCALING seconds.
|
||||
When it switches, it notifies the orchestrator with self.send_status_update()
|
||||
"""
|
||||
num_colors = len(TrafficLightColor)
|
||||
# set current color to the one before starting color, because switch immediately happens in loop
|
||||
# therefore it sleeps on the end of the loop and first status is immediately sent to msg broker
|
||||
self.current_color = TrafficLightColor((self._starting_color.value - 1) % num_colors)
|
||||
while self.running:
|
||||
self.current_color = TrafficLightColor((self.current_color.value + 1) % num_colors)
|
||||
self.last_switch = datetime.utcnow()
|
||||
self.send_status_update()
|
||||
time.sleep(self.switching_time / SCALING)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
...
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import pickle
|
||||
import threading
|
||||
import time
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Union
|
||||
|
||||
@ -19,11 +20,13 @@ STARTING_POINT = geopy.Point(47.89053, 16.20703)
|
||||
# Driving direction in degrees: 0=N, 90=E, 180=S, 270=W
|
||||
BEARING = 2
|
||||
# Scale speed of vehicles by factor x
|
||||
SCALING = 1
|
||||
SCALING = int(os.environ.get('DSE2021_SCALING', 1))
|
||||
# in km/h
|
||||
STARTING_VELOCITY = 130 * SCALING
|
||||
# Interval between status updates in seconds (is not scaled)
|
||||
UPDATE_INTERVAL = 1
|
||||
# Specify if NCE should happen
|
||||
NCE = int(os.environ.get('DSE2021_NCE', 1))
|
||||
# At x km the NCE shall happen
|
||||
NCE_KM = 2.4
|
||||
# Time in seconds to recover from NCE (will be scaled)
|
||||
@ -104,8 +107,12 @@ class Vehicle:
|
||||
|
||||
:return: True if NCE invoked, otherwise False
|
||||
"""
|
||||
if self._nce_possible and not self._nce_happened:
|
||||
if self._driven_kms >= NCE_KM:
|
||||
d = geopy.distance.distance(kilometers=0.4)
|
||||
tl2_loc = geopy.Point(47.90937, 16.20814)
|
||||
# Calculate point 400m south of traffic light 2
|
||||
nce_point = d.destination(point=tl2_loc, bearing=182)
|
||||
if NCE == 1 and self._nce_possible and not self._nce_happened:
|
||||
if self._gps_location.latitude >= nce_point.latitude:
|
||||
self._nce_happened = True
|
||||
self._last_velocity = self.velocity
|
||||
self.velocity = 0
|
||||
@ -163,14 +170,14 @@ class Vehicle:
|
||||
|
||||
# Get old and updated timestamps
|
||||
old_timestamp = self.last_update
|
||||
updated_timestamp = datetime.now()
|
||||
updated_timestamp = datetime.utcnow()
|
||||
self.last_update = updated_timestamp
|
||||
|
||||
# get driving time between timestamps (in seconds)
|
||||
driving_time = (updated_timestamp - old_timestamp).total_seconds()
|
||||
|
||||
# reached distance in kilometers: convert km/h to km/s and multiply by driving time
|
||||
kilometers = self.velocity / 3600 * driving_time * SCALING
|
||||
kilometers = self.velocity / 3600 * driving_time
|
||||
|
||||
# Define a general distance object, initialized with a distance of k km.
|
||||
d = geopy.distance.distance(kilometers=kilometers)
|
||||
@ -196,7 +203,7 @@ class Vehicle:
|
||||
informs the message broker about the current state (DAF) of the vehicle.
|
||||
"""
|
||||
print('{} starts driving ... SCALING: x{}\n\n'.format(self.vin, SCALING))
|
||||
self.last_update = datetime.now()
|
||||
self.last_update = datetime.utcnow()
|
||||
self._driven_kms = 0
|
||||
|
||||
self._t = threading.Thread(target=self.drive)
|
||||
@ -223,14 +230,16 @@ class Vehicle:
|
||||
def check_reset(self):
|
||||
"""
|
||||
Checks if end of route is reached and resets vehicle to starting conditions.
|
||||
It also resets on malicious coordinates.
|
||||
|
||||
Afterwards, the vehicle is still driving, but again from start.
|
||||
"""
|
||||
if self._driven_kms >= RESET_KM:
|
||||
print('\n\nEnd of route reached ... resetting and restarting vehicle')
|
||||
self._gps_location = self._starting_point
|
||||
self._driven_kms = 0
|
||||
self.last_update = datetime.now()
|
||||
self.nce = False
|
||||
self._reset()
|
||||
|
||||
if self._gps_location.latitude < self._starting_point.latitude or \
|
||||
self._gps_location.longitude < self._starting_point.longitude:
|
||||
self._reset()
|
||||
|
||||
@circuit(failure_threshold=10, expected_exception=AMQPConnectionError)
|
||||
def send_status_update(self):
|
||||
@ -255,6 +264,14 @@ class Vehicle:
|
||||
else:
|
||||
print('We are still recovering ... ignoring new target velocity.')
|
||||
|
||||
def _reset(self):
|
||||
print('\n\nEnd of route reached ... resetting and restarting vehicle')
|
||||
self._gps_location = self._starting_point
|
||||
self._driven_kms = 0
|
||||
self.last_update = datetime.utcnow()
|
||||
self.nce = False
|
||||
self.velocity = STARTING_VELOCITY
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
...
|
||||
|
||||
113
components/i_feed/test_ifeed.py
Normal file
113
components/i_feed/test_ifeed.py
Normal file
@ -0,0 +1,113 @@
|
||||
import datetime
|
||||
import pickle
|
||||
import time
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
import geopy
|
||||
from dse_shared_libs.daf import DAF
|
||||
from dse_shared_libs.mock.datetime import MyDate
|
||||
from dse_shared_libs.target_velocity import TargetVelocity
|
||||
from dse_shared_libs.traffic_light_color import TrafficLightColor
|
||||
|
||||
from devices.traffic_light import TrafficLight
|
||||
from devices.vehicle import Vehicle
|
||||
from dse_shared_libs.mock.rabbit_mq_mocks import MyBlockingConnection
|
||||
|
||||
|
||||
class TestVehicle(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
with patch('pika.BlockingConnection', MyBlockingConnection):
|
||||
self.vin = 'my_vin'
|
||||
self.starting_velocity = 130
|
||||
self.starting_point = geopy.Point(0, 0, 0)
|
||||
self.timestamp = datetime.datetime.utcnow()
|
||||
|
||||
self.v = Vehicle(
|
||||
vin=self.vin,
|
||||
starting_point=self.starting_point,
|
||||
starting_velocity=self.starting_velocity
|
||||
)
|
||||
|
||||
self.v.last_update = self.timestamp
|
||||
self.v._driven_kms = 0
|
||||
|
||||
def tearDown(self) -> None:
|
||||
time.sleep(0.1)
|
||||
|
||||
def test_nce_prop(self):
|
||||
with patch('time.sleep', return_value=None):
|
||||
# initially false
|
||||
self.assertEqual(self.v.nce, False)
|
||||
# get past nce km
|
||||
self.v._gps_location.latitude = 48
|
||||
# now nce should fire
|
||||
self.assertEqual(self.v.nce, True)
|
||||
# second call is false again
|
||||
self.assertEqual(self.v.nce, False)
|
||||
|
||||
def test_daf_prop(self):
|
||||
with patch('devices.vehicle.datetime', MyDate):
|
||||
MyDate.set_timestamp(self.timestamp)
|
||||
|
||||
# test if daf object created properly
|
||||
daf = DAF(vehicle_identification_number=self.vin,
|
||||
gps_location=self.starting_point,
|
||||
velocity=self.starting_velocity,
|
||||
timestamp=self.timestamp,
|
||||
near_crash_event=False)
|
||||
self.assertEqual(self.v.daf, daf)
|
||||
|
||||
def test_current_location_prop(self):
|
||||
with patch('devices.vehicle.datetime', MyDate):
|
||||
MyDate.set_timestamp(self.timestamp)
|
||||
|
||||
# initial position should be starting point
|
||||
self.assertEqual(self.v.gps_location, self.starting_point)
|
||||
|
||||
# lets say last time was about an hour ago
|
||||
self.v.last_update = self.timestamp - datetime.timedelta(hours=1)
|
||||
|
||||
# we drive "almost" directly north
|
||||
loc = self.v.gps_location
|
||||
self.assertEqual(round(loc.latitude, 2), 1.17)
|
||||
self.assertEqual(round(loc.longitude, 2), 0.04)
|
||||
|
||||
def test_driven_kms_prop(self):
|
||||
# initial driven kms are 0
|
||||
self.assertEqual(self.v.driven_kms, '0km')
|
||||
|
||||
# alter driven kms
|
||||
self.v._driven_kms = 44.24552
|
||||
# will be rounded to 2 digits
|
||||
self.assertEqual(self.v.driven_kms, '44.25km')
|
||||
|
||||
def test_new_velocity(self):
|
||||
# starting velocity is set to 130
|
||||
self.assertEqual(self.v.velocity, 130)
|
||||
|
||||
# we get a new velocity, lets say 55 ...
|
||||
tv = TargetVelocity(vin='my_vin', target_velocity=55, timestamp=self.timestamp)
|
||||
self.v.new_velocity(pickle.dumps(tv))
|
||||
# then the vehicle should comply with that
|
||||
self.assertEqual(self.v.velocity, 55)
|
||||
|
||||
|
||||
class TestTrafficLight(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
with patch('pika.BlockingConnection', MyBlockingConnection):
|
||||
self.tl = TrafficLight(tlid='my_tl',
|
||||
switching_time=1,
|
||||
starting_color=TrafficLightColor.RED)
|
||||
|
||||
def tearDown(self) -> None:
|
||||
self.tl.stop()
|
||||
|
||||
def test_switching(self):
|
||||
self.assertFalse(hasattr(self.tl, 'current_color'))
|
||||
self.tl.start()
|
||||
self.assertEqual(self.tl.current_color, TrafficLightColor.RED)
|
||||
time.sleep(1.2)
|
||||
self.assertEqual(self.tl.current_color, TrafficLightColor.GREEN)
|
||||
time.sleep(1)
|
||||
self.assertEqual(self.tl.current_color, TrafficLightColor.RED)
|
||||
@ -1,9 +1,10 @@
|
||||
import datetime
|
||||
import pickle
|
||||
import sys
|
||||
import os
|
||||
from typing import List, Dict
|
||||
from datetime import datetime, timedelta
|
||||
from math import floor, ceil
|
||||
from math import floor
|
||||
|
||||
import requests
|
||||
from dse_shared_libs import daf, traffic_light_state, traffic_light_color, target_velocity
|
||||
@ -22,7 +23,7 @@ sys.modules['target_velocity'] = target_velocity
|
||||
|
||||
ENTITY_IDENT_URL = 'http://entityident:5002/api/v1/resources/'
|
||||
|
||||
SCALING = 1
|
||||
SCALING = int(os.environ.get('DSE2021_SCALING', 1))
|
||||
|
||||
|
||||
class Orchestrator:
|
||||
@ -60,7 +61,7 @@ class Orchestrator:
|
||||
for traffic_light in traffic_lights['cursor']:
|
||||
self.tls[traffic_light['id']] = {'color': traffic_light['color'],
|
||||
'switching_time': traffic_light['switchingTime'],
|
||||
'last_switch': datetime.now()}
|
||||
'last_switch': datetime.utcnow()}
|
||||
|
||||
def setup_msg_queues(self):
|
||||
"""
|
||||
@ -88,7 +89,7 @@ class Orchestrator:
|
||||
"""
|
||||
Gets the daf object's pickle binary dump.
|
||||
Unpickle, calculate new target velocity based on daf and current tl data and
|
||||
respond new target velicity for this vehicle.
|
||||
respond new target velocity for this vehicle.
|
||||
|
||||
:param pickle_binary: daf object pickle binary dump
|
||||
"""
|
||||
@ -100,55 +101,14 @@ class Orchestrator:
|
||||
params={'lat': loc.latitude, 'lon': loc.longitude})
|
||||
traffic_lights_geo = response.json()
|
||||
current_vel = received_daf_object.velocity
|
||||
target_vel = 130 * SCALING
|
||||
print('Nearest traffic lights: {}'.format(traffic_lights_geo['cursor']))
|
||||
for traffic_light in traffic_lights_geo['cursor']:
|
||||
# Should only ever contain one traffic light (the next in line)
|
||||
tl_id = traffic_light['id']
|
||||
distance = traffic_light['calculatedRange']
|
||||
switching_time = self.tls[tl_id]['switching_time'] / float(SCALING)
|
||||
# Time until next switch must be scaled accordingly
|
||||
next_switch_time = self.tls[tl_id]['last_switch'] + timedelta(seconds=switching_time)
|
||||
time_until_switch = (next_switch_time - datetime.now()).total_seconds()
|
||||
print('Distance to TL: {}'.format(distance))
|
||||
print('Time until switch in seconds: {}'.format(time_until_switch))
|
||||
if self.tls[tl_id]['color'] is TrafficLightColor.RED:
|
||||
speed_needed_max = (distance / float(time_until_switch)) * 3.6
|
||||
if speed_needed_max < 130 * SCALING:
|
||||
if current_vel < speed_needed_max:
|
||||
target_vel = current_vel
|
||||
else:
|
||||
target_vel = floor(speed_needed_max)
|
||||
else:
|
||||
# Cannot make it on next green
|
||||
i = 2
|
||||
while speed_needed_max > 130 * SCALING:
|
||||
next_green_phase_start = time_until_switch + switching_time * i
|
||||
speed_needed_max = (distance / float(next_green_phase_start)) * 3.6
|
||||
i = i + 2
|
||||
target_vel = floor(speed_needed_max)
|
||||
else:
|
||||
# Check if we can reach TL in time
|
||||
speed_needed_min = (distance / float(time_until_switch)) * 3.6
|
||||
if speed_needed_min < 130 * SCALING:
|
||||
if current_vel < speed_needed_min:
|
||||
target_vel = 130 * SCALING
|
||||
else:
|
||||
target_vel = current_vel
|
||||
else:
|
||||
i = 1
|
||||
speed_needed_max = 132 * SCALING
|
||||
while speed_needed_max > 130 * SCALING:
|
||||
next_green_phase_start = time_until_switch + switching_time * i
|
||||
speed_needed_max = (distance / float(next_green_phase_start)) * 3.6
|
||||
i = i + 2
|
||||
target_vel = floor(speed_needed_max)
|
||||
|
||||
target_vel = self._compute_velocity(traffic_lights_geo, current_vel)
|
||||
|
||||
response_channel = self._velocity_mbs[received_daf_object.vehicle_identification_number]
|
||||
print('Target velocity: {}'.format(target_vel))
|
||||
response_channel.send(pickle.dumps(
|
||||
TargetVelocity(vin=received_daf_object.vehicle_identification_number, target_velocity=target_vel,
|
||||
timestamp=datetime.now())))
|
||||
timestamp=datetime.utcnow())))
|
||||
|
||||
def handle_tl_state_receive(self, msg):
|
||||
"""
|
||||
@ -164,3 +124,55 @@ class Orchestrator:
|
||||
self.tls[tl_state.tlid]['color'] = tl_state.color
|
||||
self.tls[tl_state.tlid]['last_switch'] = tl_state.last_switch
|
||||
print(tl_state)
|
||||
|
||||
def _compute_velocity(self, traffic_lights_geo, current_vel):
|
||||
target_vel = 130 * SCALING
|
||||
|
||||
print('Nearest traffic lights: {}'.format(traffic_lights_geo['cursor']))
|
||||
for traffic_light in traffic_lights_geo['cursor']:
|
||||
# Should only ever contain one traffic light (the next in line)
|
||||
tl_id = traffic_light['id']
|
||||
distance = traffic_light['calculatedRange']
|
||||
switching_time = self.tls[tl_id]['switching_time'] / float(SCALING)
|
||||
# Time until next switch must be scaled accordingly
|
||||
next_switch_time = self.tls[tl_id]['last_switch'] + timedelta(seconds=switching_time)
|
||||
time_until_switch = (next_switch_time - datetime.utcnow()).total_seconds()
|
||||
print('Distance to TL: {}'.format(distance))
|
||||
print('Time until switch in seconds: {}'.format(time_until_switch))
|
||||
|
||||
if self.tls[tl_id]['color'] is TrafficLightColor.RED:
|
||||
speed_needed_max = (distance / float(time_until_switch)) * 3.6
|
||||
if speed_needed_max < 130 * SCALING:
|
||||
if current_vel < speed_needed_max:
|
||||
target_vel = current_vel
|
||||
else:
|
||||
target_vel = floor(speed_needed_max)
|
||||
|
||||
else:
|
||||
# Cannot make it on next green
|
||||
i = 2
|
||||
while speed_needed_max > 130 * SCALING:
|
||||
next_green_phase_start = time_until_switch + switching_time * i
|
||||
speed_needed_max = (distance / float(next_green_phase_start)) * 3.6
|
||||
i = i + 2
|
||||
target_vel = floor(speed_needed_max)
|
||||
|
||||
elif self.tls[tl_id]['color'] is TrafficLightColor.GREEN:
|
||||
# Check if we can reach TL in time
|
||||
speed_needed_min = (distance / float(time_until_switch)) * 3.6
|
||||
if speed_needed_min < 130 * SCALING:
|
||||
if current_vel < speed_needed_min:
|
||||
target_vel = 130 * SCALING
|
||||
else:
|
||||
target_vel = current_vel
|
||||
else:
|
||||
i = 1
|
||||
speed_needed_max = 132 * SCALING
|
||||
while speed_needed_max > 130 * SCALING:
|
||||
next_green_phase_start = time_until_switch + switching_time * i
|
||||
speed_needed_max = (distance / float(next_green_phase_start)) * 3.6
|
||||
i = i + 2
|
||||
target_vel = floor(speed_needed_max)
|
||||
else:
|
||||
print('Unknown Traffic Light Color!')
|
||||
return target_vel
|
||||
|
||||
54
components/orchestration/test_orchestrator.py
Normal file
54
components/orchestration/test_orchestrator.py
Normal file
@ -0,0 +1,54 @@
|
||||
import datetime
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
|
||||
from dse_shared_libs.mock.response import MyResponse
|
||||
from dse_shared_libs.traffic_light_color import TrafficLightColor
|
||||
|
||||
from orchestrator import Orchestrator
|
||||
|
||||
|
||||
class TestOrchestrator(unittest.TestCase):
|
||||
def setUp(self) -> None:
|
||||
self.timestamp = datetime.datetime.utcnow()
|
||||
with patch('requests.sessions.Session.get', MyResponse):
|
||||
self.orc = Orchestrator()
|
||||
|
||||
def test_full_speed_if_nothing_in_range(self):
|
||||
tl_geo = {'cursor': []}
|
||||
current_vel = 55.0
|
||||
|
||||
target_vel = self.orc._compute_velocity(tl_geo, current_vel)
|
||||
self.assertEqual(130, target_vel)
|
||||
|
||||
def test_keep_speed_if_far_away_and_green(self):
|
||||
self.orc.tls = {'1': {'color': TrafficLightColor.GREEN, 'switching_time': 1000, 'last_switch': self.timestamp}}
|
||||
tl_geo = {'cursor': [
|
||||
{'id': '1', 'calculatedRange': 1000}
|
||||
]}
|
||||
current_vel = 55.0
|
||||
|
||||
target_vel = self.orc._compute_velocity(tl_geo, current_vel)
|
||||
self.assertEqual(current_vel, target_vel)
|
||||
|
||||
def test_slow_down_if_passing_RED_not_possible(self):
|
||||
self.orc.tls = {'1': {'color': TrafficLightColor.RED, 'switching_time': 100000, 'last_switch': self.timestamp}}
|
||||
tl_geo = {'cursor': [
|
||||
{'id': '1', 'calculatedRange': 1}
|
||||
]}
|
||||
current_vel = 130.0
|
||||
|
||||
target_vel = self.orc._compute_velocity(tl_geo, current_vel)
|
||||
self.assertNotEqual(current_vel, target_vel)
|
||||
self.assertEqual(0, target_vel)
|
||||
|
||||
def test_adjust_speed_to_get_over_on_green_if_currently_red(self):
|
||||
self.orc.tls = {'1': {'color': TrafficLightColor.RED, 'switching_time': 100, 'last_switch': self.timestamp}}
|
||||
tl_geo = {'cursor': [
|
||||
{'id': '1', 'calculatedRange': 1000}
|
||||
]}
|
||||
current_vel = 130.0
|
||||
|
||||
target_vel = self.orc._compute_velocity(tl_geo, current_vel)
|
||||
self.assertNotEqual(current_vel, target_vel)
|
||||
self.assertEqual(36.0, target_vel)
|
||||
0
components/shared/dse_shared_libs/mock/__init__.py
Normal file
0
components/shared/dse_shared_libs/mock/__init__.py
Normal file
10
components/shared/dse_shared_libs/mock/datetime.py
Normal file
10
components/shared/dse_shared_libs/mock/datetime.py
Normal file
@ -0,0 +1,10 @@
|
||||
class MyDate:
|
||||
timestamp = None
|
||||
|
||||
@classmethod
|
||||
def set_timestamp(cls, timestamp):
|
||||
cls.timestamp = timestamp
|
||||
|
||||
@classmethod
|
||||
def utcnow(cls):
|
||||
return cls.timestamp
|
||||
43
components/shared/dse_shared_libs/mock/rabbit_mq_mocks.py
Normal file
43
components/shared/dse_shared_libs/mock/rabbit_mq_mocks.py
Normal file
@ -0,0 +1,43 @@
|
||||
class MyQueue:
|
||||
@property
|
||||
def queue(self):
|
||||
return None
|
||||
|
||||
|
||||
class MyMethod:
|
||||
method = MyQueue()
|
||||
|
||||
|
||||
class MyChannel:
|
||||
@staticmethod
|
||||
def exchange_declare(*args, **kwargs):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def queue_declare(*args, **kwargs):
|
||||
return MyMethod
|
||||
|
||||
@staticmethod
|
||||
def queue_bind(*args, **kwargs):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def basic_consume(*args, **kwargs):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def basic_publish(*args, **kwargs):
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def start_consuming(*args, **kwargs):
|
||||
return None
|
||||
|
||||
|
||||
class MyBlockingConnection:
|
||||
def __init__(self, *args, **kwargs):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def channel():
|
||||
return MyChannel()
|
||||
7
components/shared/dse_shared_libs/mock/response.py
Normal file
7
components/shared/dse_shared_libs/mock/response.py
Normal file
@ -0,0 +1,7 @@
|
||||
class MyResponse:
|
||||
def __init__(self, *args, **kwargs):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def json(*args, **kwargs):
|
||||
return {'cursor': []}
|
||||
@ -1,4 +1,4 @@
|
||||
flask
|
||||
flask==1.1.4
|
||||
Flask-Cors
|
||||
requests
|
||||
Flask-PyMongo
|
||||
|
||||
@ -5,7 +5,7 @@ from bson import json_util
|
||||
from flask import Flask, jsonify
|
||||
from flask_cors import CORS
|
||||
from flask import request
|
||||
import json;
|
||||
import json
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(app)
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: scaling-configmap
|
||||
data:
|
||||
scaling: "1"
|
||||
19
kubernetes/configmaps/simulation-parameters-configmap.yaml
Normal file
19
kubernetes/configmaps/simulation-parameters-configmap.yaml
Normal file
@ -0,0 +1,19 @@
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: simulation-parameters-configmap
|
||||
data:
|
||||
DSE2021_SCALING: "2"
|
||||
DSE2021_NCE: "1"
|
||||
DSE2021_CAR1_SV: "130"
|
||||
DSE2021_CAR2_SV: "130"
|
||||
DSE2021_CAR3_SV: "130"
|
||||
DSE2021_CAR1_SD: "300"
|
||||
DSE2021_CAR2_SD: "500"
|
||||
DSE2021_CAR3_SD: "400"
|
||||
DSE2021_CAR1_ST: "10"
|
||||
DSE2021_CAR2_ST: "15"
|
||||
DSE2021_CAR3_ST: "25"
|
||||
DSE2021_TL1_R: "2000"
|
||||
DSE2021_TL2_R: "800"
|
||||
DSE2021_TL3_R: "1000"
|
||||
@ -23,11 +23,11 @@ spec:
|
||||
- containerPort: 80
|
||||
resources: {}
|
||||
env:
|
||||
- name: SCALING
|
||||
- name: DSE2021_SCALING
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: scaling-configmap
|
||||
key: scaling
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_SCALING
|
||||
restartPolicy: Always
|
||||
serviceAccountName: ""
|
||||
volumes: null
|
||||
|
||||
@ -23,11 +23,71 @@ spec:
|
||||
- containerPort: 5002
|
||||
resources: {}
|
||||
env:
|
||||
- name: SCALING
|
||||
- name: DSE2021_SCALING
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: scaling-configmap
|
||||
key: scaling
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_SCALING
|
||||
- name: DSE2021_CAR1_SV
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_CAR1_SV
|
||||
- name: DSE2021_CAR2_SV
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_CAR2_SV
|
||||
- name: DSE2021_CAR3_SV
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_CAR3_SV
|
||||
- name: DSE2021_CAR1_SD
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_CAR1_SD
|
||||
- name: DSE2021_CAR2_SD
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_CAR2_SD
|
||||
- name: DSE2021_CAR3_SD
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_CAR3_SD
|
||||
- name: DSE2021_CAR1_ST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_CAR1_ST
|
||||
- name: DSE2021_CAR2_ST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_CAR2_ST
|
||||
- name: DSE2021_CAR3_ST
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_CAR3_ST
|
||||
- name: DSE2021_TL1_R
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_TL1_R
|
||||
- name: DSE2021_TL2_R
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_TL2_R
|
||||
- name: DSE2021_TL3_R
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_TL3_R
|
||||
restartPolicy: Always
|
||||
serviceAccountName: ""
|
||||
volumes: null
|
||||
|
||||
@ -23,11 +23,11 @@ spec:
|
||||
- containerPort: 5001
|
||||
resources: {}
|
||||
env:
|
||||
- name: SCALING
|
||||
- name: DSE2021_SCALING
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: scaling-configmap
|
||||
key: scaling
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_SCALING
|
||||
restartPolicy: Always
|
||||
serviceAccountName: ""
|
||||
volumes: null
|
||||
|
||||
@ -21,11 +21,16 @@ spec:
|
||||
name: ifeed
|
||||
resources: {}
|
||||
env:
|
||||
- name: SCALING
|
||||
- name: DSE2021_SCALING
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: scaling-configmap
|
||||
key: scaling
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_SCALING
|
||||
- name: DSE2021_NCE
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_NCE
|
||||
restartPolicy: Always
|
||||
serviceAccountName: ""
|
||||
volumes: null
|
||||
|
||||
@ -21,11 +21,11 @@ spec:
|
||||
name: orchestration
|
||||
resources: {}
|
||||
env:
|
||||
- name: SCALING
|
||||
- name: DSE2021_SCALING
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: scaling-configmap
|
||||
key: scaling
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_SCALING
|
||||
restartPolicy: Always
|
||||
serviceAccountName: ""
|
||||
volumes: null
|
||||
|
||||
@ -23,11 +23,11 @@ spec:
|
||||
- containerPort: 5004
|
||||
resources: {}
|
||||
env:
|
||||
- name: SCALING
|
||||
- name: DSE2021_SCALING
|
||||
valueFrom:
|
||||
configMapKeyRef:
|
||||
name: scaling-configmap
|
||||
key: scaling
|
||||
name: simulation-parameters-configmap
|
||||
key: DSE2021_SCALING
|
||||
restartPolicy: Always
|
||||
serviceAccountName: ""
|
||||
volumes: null
|
||||
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 125 KiB |
Loading…
x
Reference in New Issue
Block a user