diff --git a/components/entitiy_ident/entity_ident_service/entity_ident_server.py b/components/entitiy_ident/entity_ident_service/entity_ident_server.py
index 3409e5e..23587b5 100644
--- a/components/entitiy_ident/entity_ident_service/entity_ident_server.py
+++ b/components/entitiy_ident/entity_ident_service/entity_ident_server.py
@@ -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():
diff --git a/components/entitiy_ident/mongo/cars.json b/components/entitiy_ident/mongo/cars.json
index 363fe74..b6d5a87 100644
--- a/components/entitiy_ident/mongo/cars.json
+++ b/components/entitiy_ident/mongo/cars.json
@@ -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"
}
]
\ No newline at end of file
diff --git a/components/entitiy_ident/mongo/traffic_lights.json b/components/entitiy_ident/mongo/traffic_lights.json
index 0fbe8e3..6f83730 100644
--- a/components/entitiy_ident/mongo/traffic_lights.json
+++ b/components/entitiy_ident/mongo/traffic_lights.json
@@ -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"
}
]
\ No newline at end of file
diff --git a/components/event_store/service/test_event_logger.py b/components/event_store/service/test_event_logger.py
index ac13786..3128283 100644
--- a/components/event_store/service/test_event_logger.py
+++ b/components/event_store/service/test_event_logger.py
@@ -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))
diff --git a/components/i_feed/devices/traffic_light.py b/components/i_feed/devices/traffic_light.py
index 3b3c7e8..429d6c5 100644
--- a/components/i_feed/devices/traffic_light.py
+++ b/components/i_feed/devices/traffic_light.py
@@ -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__':
...
diff --git a/components/i_feed/devices/vehicle.py b/components/i_feed/devices/vehicle.py
index d4b55d5..4856273 100644
--- a/components/i_feed/devices/vehicle.py
+++ b/components/i_feed/devices/vehicle.py
@@ -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__":
...
diff --git a/components/i_feed/test_ifeed.py b/components/i_feed/test_ifeed.py
new file mode 100644
index 0000000..8faeba9
--- /dev/null
+++ b/components/i_feed/test_ifeed.py
@@ -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)
diff --git a/components/orchestration/orchestrator.py b/components/orchestration/orchestrator.py
index 5277a01..acee465 100644
--- a/components/orchestration/orchestrator.py
+++ b/components/orchestration/orchestrator.py
@@ -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
diff --git a/components/orchestration/test_orchestrator.py b/components/orchestration/test_orchestrator.py
new file mode 100644
index 0000000..50ad708
--- /dev/null
+++ b/components/orchestration/test_orchestrator.py
@@ -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)
diff --git a/components/shared/dse_shared_libs/mock/__init__.py b/components/shared/dse_shared_libs/mock/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/components/shared/dse_shared_libs/mock/datetime.py b/components/shared/dse_shared_libs/mock/datetime.py
new file mode 100644
index 0000000..a1db7c7
--- /dev/null
+++ b/components/shared/dse_shared_libs/mock/datetime.py
@@ -0,0 +1,10 @@
+class MyDate:
+ timestamp = None
+
+ @classmethod
+ def set_timestamp(cls, timestamp):
+ cls.timestamp = timestamp
+
+ @classmethod
+ def utcnow(cls):
+ return cls.timestamp
diff --git a/components/shared/dse_shared_libs/mock/rabbit_mq_mocks.py b/components/shared/dse_shared_libs/mock/rabbit_mq_mocks.py
new file mode 100644
index 0000000..a6a551d
--- /dev/null
+++ b/components/shared/dse_shared_libs/mock/rabbit_mq_mocks.py
@@ -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()
diff --git a/components/shared/dse_shared_libs/mock/response.py b/components/shared/dse_shared_libs/mock/response.py
new file mode 100644
index 0000000..93e33d5
--- /dev/null
+++ b/components/shared/dse_shared_libs/mock/response.py
@@ -0,0 +1,7 @@
+class MyResponse:
+ def __init__(self, *args, **kwargs):
+ ...
+
+ @staticmethod
+ def json(*args, **kwargs):
+ return {'cursor': []}
diff --git a/components/x_way/requirements.txt b/components/x_way/requirements.txt
index ec961a5..b09968e 100644
--- a/components/x_way/requirements.txt
+++ b/components/x_way/requirements.txt
@@ -1,4 +1,4 @@
-flask
+flask==1.1.4
Flask-Cors
requests
Flask-PyMongo
diff --git a/components/x_way/x_way_server.py b/components/x_way/x_way_server.py
index 47e1fdb..007db8c 100644
--- a/components/x_way/x_way_server.py
+++ b/components/x_way/x_way_server.py
@@ -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)
diff --git a/kubernetes/configmaps/scaling-configmap.yaml b/kubernetes/configmaps/scaling-configmap.yaml
deleted file mode 100644
index a2de7d1..0000000
--- a/kubernetes/configmaps/scaling-configmap.yaml
+++ /dev/null
@@ -1,6 +0,0 @@
-apiVersion: v1
-kind: ConfigMap
-metadata:
- name: scaling-configmap
-data:
- scaling: "1"
\ No newline at end of file
diff --git a/kubernetes/configmaps/simulation-parameters-configmap.yaml b/kubernetes/configmaps/simulation-parameters-configmap.yaml
new file mode 100644
index 0000000..f28712b
--- /dev/null
+++ b/kubernetes/configmaps/simulation-parameters-configmap.yaml
@@ -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"
\ No newline at end of file
diff --git a/kubernetes/deployments/controlcenter-deployment.yaml b/kubernetes/deployments/controlcenter-deployment.yaml
index 3ab29b4..596e1e3 100644
--- a/kubernetes/deployments/controlcenter-deployment.yaml
+++ b/kubernetes/deployments/controlcenter-deployment.yaml
@@ -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
diff --git a/kubernetes/deployments/entityident-deployment.yaml b/kubernetes/deployments/entityident-deployment.yaml
index 2c894e3..afcbd65 100644
--- a/kubernetes/deployments/entityident-deployment.yaml
+++ b/kubernetes/deployments/entityident-deployment.yaml
@@ -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
diff --git a/kubernetes/deployments/eventstore-deployment.yaml b/kubernetes/deployments/eventstore-deployment.yaml
index a38908f..cb10349 100644
--- a/kubernetes/deployments/eventstore-deployment.yaml
+++ b/kubernetes/deployments/eventstore-deployment.yaml
@@ -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
diff --git a/kubernetes/deployments/ifeed-deployment.yaml b/kubernetes/deployments/ifeed-deployment.yaml
index 981cc7c..2e00553 100644
--- a/kubernetes/deployments/ifeed-deployment.yaml
+++ b/kubernetes/deployments/ifeed-deployment.yaml
@@ -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
diff --git a/kubernetes/deployments/orchestration-deployment.yaml b/kubernetes/deployments/orchestration-deployment.yaml
index a71e304..21bff31 100644
--- a/kubernetes/deployments/orchestration-deployment.yaml
+++ b/kubernetes/deployments/orchestration-deployment.yaml
@@ -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
diff --git a/kubernetes/deployments/xway-deployment.yaml b/kubernetes/deployments/xway-deployment.yaml
index 99fed99..ba50fff 100644
--- a/kubernetes/deployments/xway-deployment.yaml
+++ b/kubernetes/deployments/xway-deployment.yaml
@@ -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
diff --git a/project_plan/arch proposal.drawio b/project_plan/arch proposal.drawio
index 440f925..9d8399a 100644
--- a/project_plan/arch proposal.drawio
+++ b/project_plan/arch proposal.drawio
@@ -1 +1 @@
-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
\ No newline at end of file
+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
\ No newline at end of file
diff --git a/project_plan/arch proposal.png b/project_plan/arch proposal.png
deleted file mode 100644
index 406c9b8..0000000
Binary files a/project_plan/arch proposal.png and /dev/null differ