Finalize project
This commit is contained in:
@@ -0,0 +1,26 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
import geopy.distance
|
||||
|
||||
|
||||
@dataclass
|
||||
class DAF:
|
||||
"""
|
||||
"Datenaufzeichnung für automatisiertes Fahren" (DAF), see 2021_DSE_Exercise_V1.0_Final.pdf page 7
|
||||
"""
|
||||
|
||||
vehicle_identification_number: str
|
||||
gps_location: geopy.Point
|
||||
velocity: float
|
||||
near_crash_event: bool
|
||||
timestamp: datetime
|
||||
|
||||
def to_dict(self):
|
||||
return {'vehicle_identification_number': self.vehicle_identification_number,
|
||||
'gps_location': {'latitude': self.gps_location.latitude,
|
||||
'longitude': self.gps_location.longitude
|
||||
},
|
||||
'velocity': self.velocity,
|
||||
'near_crash_event': self.near_crash_event,
|
||||
'timestamp': self.timestamp.timestamp()
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import time
|
||||
from threading import Thread
|
||||
from typing import Union
|
||||
|
||||
import pika
|
||||
from pika.exceptions import AMQPConnectionError
|
||||
|
||||
|
||||
class MBWrapper:
|
||||
host: str
|
||||
exchange_type: str
|
||||
exchange_name: str
|
||||
callback: callable
|
||||
|
||||
_logger: Union['MBWrapper', None]
|
||||
|
||||
_type: str = None
|
||||
_connection: pika.BlockingConnection
|
||||
_channel: pika.BlockingConnection.channel
|
||||
|
||||
def __init__(self,
|
||||
host: str = 'rabbitmq',
|
||||
exchange_type: str = 'fanout',
|
||||
exchange_name: str = None,
|
||||
callback: callable = None,
|
||||
verbose: bool = False):
|
||||
"""
|
||||
Initializes a new Message Broker Wrapper (MBWrapper). Initialize this object afterwards with
|
||||
self.setup_sender() or self.setup_receiver() if messages will be published to or received from the queue.
|
||||
|
||||
If the exchange_name is not "logger", the MBWrapper will also initialize an sending queue which forwards
|
||||
EVERY request to this exchange, too. Therefore, every request routed via a MBWrapper is logged. Except if it is
|
||||
the logger MBWrapper. This one is initialized in the EventStore Service and receives all messages.
|
||||
|
||||
:param host: of running rabbitMQ instance
|
||||
:param exchange_type: rabbitMQ exchange type to use
|
||||
:param exchange_name: name of exchange
|
||||
:param callback: callable callback to execute if message receiver will be set up, can also be set later on.
|
||||
:param verbose: print handled messages to console
|
||||
"""
|
||||
assert exchange_name, 'Please define an exchange name'
|
||||
|
||||
# append a connection to the logging broker, if it isn't the logging broker itself
|
||||
if exchange_name != 'logger':
|
||||
self._logger = MBWrapper(exchange_name='logger')
|
||||
self._logger.setup_sender()
|
||||
else:
|
||||
self._logger = None
|
||||
|
||||
self.host = host
|
||||
self.exchange_type = exchange_type
|
||||
self.exchange_name = exchange_name
|
||||
self.callback = callback
|
||||
self.verbose = verbose
|
||||
|
||||
def print(self, *msg):
|
||||
"""
|
||||
Modified print which prints only to stout with self.verbose.
|
||||
:param msg: to print
|
||||
"""
|
||||
if self.verbose:
|
||||
print(*msg)
|
||||
|
||||
def setup_sender(self):
|
||||
"""
|
||||
Setup the MBWrapper as sender.
|
||||
"""
|
||||
assert self._type != 'receiver', 'MBWrapper is already a receiver. Use another MBWrapper.'
|
||||
self._type = 'sender'
|
||||
self._setup_channel()
|
||||
|
||||
def setup_receiver(self, callback: callable = None):
|
||||
"""
|
||||
Setup the MBWrapper as a receiver.
|
||||
A callback method which can handle the response bytes as input is mandatory.
|
||||
Set it here if not already done on init.
|
||||
"""
|
||||
if callback:
|
||||
self.callback = callback
|
||||
|
||||
assert self._type != 'sender', 'MBWrapper is already a sender. Use another MBWrapper.'
|
||||
assert self.callback, \
|
||||
'Please setup MBWrapper with "on response" self.callback which can handle a byte string as input.'
|
||||
|
||||
def consumer():
|
||||
"""
|
||||
If initialized as receiver: Consumer thread which waits for incoming messages, invokes self._receive
|
||||
"""
|
||||
self._setup_channel()
|
||||
result = self._channel.queue_declare(queue='', exclusive=True)
|
||||
queue_name = result.method.queue
|
||||
|
||||
self._channel.queue_bind(exchange=self.exchange_name, queue=queue_name)
|
||||
|
||||
self.print('[{}] Waiting for messages.'.format(self.exchange_name))
|
||||
|
||||
self._channel.basic_consume(
|
||||
queue=queue_name, on_message_callback=self._receive, auto_ack=True)
|
||||
|
||||
self._channel.start_consuming()
|
||||
|
||||
Thread(target=consumer).start()
|
||||
|
||||
def send(self, message: bytes):
|
||||
"""
|
||||
Send the message to the queue. Forward also to logger.
|
||||
|
||||
:param message: msg to send
|
||||
"""
|
||||
if type(message) is not bytes:
|
||||
message = str(message).encode()
|
||||
self._channel.basic_publish(exchange=self.exchange_name, routing_key='', body=message)
|
||||
self.print("[{}] Sent {}".format(self.exchange_name, message))
|
||||
|
||||
# has a logger IFF not 'logger' as exchange name -> forwards everything to logger exchange, too
|
||||
if self.__getattribute__('_logger'):
|
||||
# print('logging')
|
||||
self._logger.send(message)
|
||||
|
||||
def close(self):
|
||||
"""
|
||||
Closes the connection.
|
||||
"""
|
||||
self._connection.close()
|
||||
|
||||
def _setup_channel(self):
|
||||
"""
|
||||
Setup the rabbitMQ channel. Retry if not succeeded (Allow the dependencies to
|
||||
boot). Wait one second between retries.
|
||||
"""
|
||||
connect_succeeded = False
|
||||
while not connect_succeeded:
|
||||
try:
|
||||
self._connection = pika.BlockingConnection(
|
||||
pika.ConnectionParameters(host=self.host))
|
||||
connect_succeeded = True
|
||||
except pika.exceptions.AMQPConnectionError:
|
||||
print('Could not connect, retry')
|
||||
time.sleep(1)
|
||||
|
||||
self._channel = self._connection.channel()
|
||||
self._channel.exchange_declare(exchange=self.exchange_name, exchange_type=self.exchange_type)
|
||||
|
||||
def _receive(self, ch, method, properties, body):
|
||||
"""
|
||||
Reduce complexity, only forward the message body to the callback method. The others are not necessary for our
|
||||
application example.
|
||||
"""
|
||||
self.callback(body)
|
||||
@@ -0,0 +1,10 @@
|
||||
class MyDate:
|
||||
timestamp = None
|
||||
|
||||
@classmethod
|
||||
def set_timestamp(cls, timestamp):
|
||||
cls.timestamp = timestamp
|
||||
|
||||
@classmethod
|
||||
def utcnow(cls):
|
||||
return cls.timestamp
|
||||
@@ -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()
|
||||
@@ -0,0 +1,7 @@
|
||||
class MyResponse:
|
||||
def __init__(self, *args, **kwargs):
|
||||
...
|
||||
|
||||
@staticmethod
|
||||
def json(*args, **kwargs):
|
||||
return {'cursor': []}
|
||||
@@ -0,0 +1,15 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
@dataclass
|
||||
class TargetVelocity:
|
||||
vin: str
|
||||
target_velocity: float
|
||||
timestamp: datetime
|
||||
|
||||
def to_dict(self):
|
||||
return {'vin': self.vin,
|
||||
'target_velocity': self.target_velocity,
|
||||
'timestamp': self.timestamp.timestamp()
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
from enum import Enum
|
||||
|
||||
|
||||
class TrafficLightColor(Enum):
|
||||
GREEN = 0
|
||||
RED = 1
|
||||
@@ -0,0 +1,16 @@
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
||||
from dse_shared_libs.traffic_light_color import TrafficLightColor
|
||||
|
||||
|
||||
@dataclass
|
||||
class TrafficLightState:
|
||||
tlid: str
|
||||
color: TrafficLightColor
|
||||
last_switch: datetime
|
||||
|
||||
def to_dict(self):
|
||||
return {'tlid': self.tlid,
|
||||
'color': self.color.name,
|
||||
'last_switch': self.last_switch.timestamp()}
|
||||
Reference in New Issue
Block a user