Merge branch 'master' of gitlab.com:kranklyboy/webapptemplate

This commit is contained in:
Tobias Eidelpes 2021-06-13 12:45:56 +02:00
commit 6806f1f5dd
20 changed files with 284 additions and 144 deletions

View File

@ -6,7 +6,6 @@ import {LandingComponent} from './component/landing/landing.component';
import {RestService} from './services/rest.service'; import {RestService} from './services/rest.service';
import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http'; import {HTTP_INTERCEPTORS, HttpClientModule} from '@angular/common/http';
import {InterceptorService} from './services/interceptor.service'; import {InterceptorService} from './services/interceptor.service';
import {WebsocketService} from './services/websocket.service';
import {LoggerModule, NgxLoggerLevel} from 'ngx-logger'; import {LoggerModule, NgxLoggerLevel} from 'ngx-logger';
import {environment} from '../environments/environment'; import {environment} from '../environments/environment';
import {FormsModule, ReactiveFormsModule} from '@angular/forms'; import {FormsModule, ReactiveFormsModule} from '@angular/forms';
@ -39,7 +38,6 @@ import {AgmCoreModule, GoogleMapsAPIWrapper} from '@agm/core';
providers: [ providers: [
GoogleMapsAPIWrapper, GoogleMapsAPIWrapper,
RestService, RestService,
WebsocketService,
{ {
provide: HTTP_INTERCEPTORS, useClass: InterceptorService, multi: true provide: HTTP_INTERCEPTORS, useClass: InterceptorService, multi: true
}, },

View File

@ -1,5 +1,3 @@
html, body { #map{
height: 100%; height : 100vh; width: 100%; padding:0px; margin: 0px;
padding: 0;
margin: 0;
} }

View File

@ -1,8 +1,26 @@
<button (click)="toggleMarker()">Drive!</button> <agm-map disableDefaultUI="true" id="map" #map [zoom]="zoom" [latitude]="center.lat" [longitude]="center.lng">
<agm-marker (markerClick)="openInfoWindow(m.value.vin)" [openInfoWindow]="true"
<agm-map *ngIf="traffic_light_markers.length > 0" id="map" #map [zoom]="zoom" [latitude]="center.lat" [longitude]="center.lng" style="height: 900px; width: 1200px"> *ngFor="let m of car_markers | keyvalue" [iconUrl]="m.value.iconUrl"
<!--<agm-marker *ngFor="let m of markers" [iconUrl]="m.iconUrl" [visible]="m.visible" [latitude]="m.lat" [longitude]="m.lng" > [latitude]="m.value.lat" [longitude]="m.value.lng" [animation]="(m.value.near_crash_event)?'BOUNCE':''">
</agm-marker>--> <agm-info-window style="opacity: 0.5!important;" (infoWindowClose)="closeInfoWindow(m.value.vin)"
<agm-marker *ngFor="let m of traffic_light_markers" [iconUrl]="m.iconUrl" [latitude]="m.lat" [longitude]="m.lng" > [isOpen]="isInfoWindowOpen(m.value.vin)" [latitude]="m.value.lat"
[longitude]="m.value.lng">
<p>VIN: {{m.value.vin}}</p>
<p>OEM: {{m.value.oem}}</p>
<p>Model Type: {{m.value.modelType}}</p>
<p>Velocity: {{m.value.velocity}}</p>
<p>Timestamp: {{m.value.timestamp}}</p>
<p>NCE: {{m.value.near_crash_event}}</p>
</agm-info-window>
</agm-marker>
<agm-marker *ngFor="let m of traffic_light_markers | keyvalue" [iconUrl]="m.value.iconUrl"
[latitude]="m.value.lat" [longitude]="m.value.lng">
<agm-info-window>
<p>Id: {{m.value.id}}</p>
<p>Switching Time: {{m.value.switchingTime}}</p>
<p>Range: {{m.value.range}}</p>
<p>Color: {{m.value.color}}</p>
<p>Last Switch: {{m.value.last_switch}}</p>
</agm-info-window>
</agm-marker> </agm-marker>
</agm-map> </agm-map>

View File

@ -1,7 +1,9 @@
import {Component, OnInit, ViewChild} from '@angular/core'; import {Component, OnInit, ViewChild} from '@angular/core';
import {AgmMap, AgmMarker} from '@agm/core'; import {AgmMap} from '@agm/core';
import {RestService} from '../../services/rest.service'; import {RestService} from '../../services/rest.service';
import {NGXLogger} from 'ngx-logger'; import {NGXLogger} from 'ngx-logger';
import {interval, Subscription} from 'rxjs';
import {startWith, switchMap} from 'rxjs/operators';
@Component({ @Component({
selector: 'app-landing', selector: 'app-landing',
@ -22,72 +24,137 @@ export class LandingComponent implements OnInit {
zoom = 14; zoom = 14;
center = {lat: 47.90620, lng: 16.20785}; center = {lat: 47.90620, lng: 16.20785};
// Test Data
markers = [
{
Id: 1,
name: 'Car-1',
lat: 47.89053,
lng: 16.20703,
visible: true,
iconUrl: 'assets/pictures/car.png'
},
{
Id: 2,
name: 'Car-2',
lat: 47.89853,
lng: 16.20703,
visible: true,
iconUrl: 'assets/pictures/car.png'
},
];
traffic_light_markers = []; traffic_light_markers = new Map();
car_markers = new Map();
infoWindows = new Map();
timeInterval: Subscription;
TL_RED_IMAGE = 'assets/pictures/traffic_light_red.png';
TL_GREEN_IMAGE = 'assets/pictures/traffic_light_green.png';
CAR_IMAGE = 'assets/pictures/car.png';
CAR_IMAGE_NCE = 'assets/pictures/car_orange.png';
NCE_SOUND = 'assets/sound/crash.mp3';
ngOnInit() { ngOnInit() {
let traffic_lights = []; this.getTrafficLights();
this.getCars();
}
getCarEvents(vin) {
this.timeInterval = interval(1000)
.pipe(
startWith(0),
switchMap(() => this.restService.getCarEvents(vin))
).subscribe((data: any) => {
const carEvent = data.body['cursor'];
const car = this.car_markers.get(vin);
car['velocity'] = carEvent['velocity'];
car['timestamp'] = carEvent['timestamp'];
car['near_crash_event'] = carEvent['near_crash_event'];
if (car['near_crash_event']) {
car['iconUrl'] = this.CAR_IMAGE_NCE;
this.playCrashSound();
} else {
car['iconUrl'] = this.CAR_IMAGE;
}
car['lat'] = carEvent['gps_location']['latitude'];
car['lng'] = carEvent['gps_location']['longitude'];
this.car_markers.set(car['vin'], car);
},
err => this.logger.error(err),
() => {
this.logger.debug('loaded traffic light events');
}
);
}
getTrafficLightEvents(tlid) {
this.timeInterval = interval(1000)
.pipe(
startWith(0),
switchMap(() => this.restService.getTrafficLightEvents(tlid))
).subscribe(
(data: any) => {
const traffic_light_event = data.body['cursor'];
const traffic_light = this.traffic_light_markers.get(traffic_light_event['tlid']);
traffic_light['color'] = traffic_light_event['color'];
traffic_light['last_switch'] = traffic_light_event['last_switch'];
traffic_light['iconUrl'] = this.trafficLightToImage(traffic_light['color']);
this.traffic_light_markers.set(traffic_light_event['tlid'], traffic_light);
},
err => this.logger.error(err),
() => {
this.logger.debug('loaded traffic light events');
}
);
}
getTrafficLights() {
this.restService.getTrafficLights().subscribe( this.restService.getTrafficLights().subscribe(
(data: any) => { (data: any) => {
traffic_lights = data;
console.log(data['cursor']);
for (const traffic_light of data['cursor']) { for (const traffic_light of data['cursor']) {
traffic_light['iconUrl'] = 'assets/pictures/traffic_light_red.png'; traffic_light['iconUrl'] = this.trafficLightToImage(traffic_light['color']);
traffic_light['lat'] = traffic_light['location'][1]; traffic_light['lat'] = traffic_light['location'][1];
traffic_light['lng'] = traffic_light['location'][0]; traffic_light['lng'] = traffic_light['location'][0];
console.log(traffic_light); this.traffic_light_markers.set(traffic_light['id'], traffic_light);
this.traffic_light_markers.push(traffic_light);
} }
}, },
err => this.logger.error(err), err => this.logger.error(err),
() => { () => {
this.logger.debug('loaded traffic lights'); this.logger.debug('loaded traffic lights');
for (const value of this.traffic_light_markers.values()) {
this.getTrafficLightEvents(value['id']);
}
} }
); );
} }
getCars() {
getDelta(source, destination, steps) { this.restService.getCars().subscribe(
return { (data: any) => {
lat: (destination.lat - source.lat) / steps, for (const car of data['cursor']) {
lng: (destination.lng - source.lng) / steps car['iconUrl'] = 'assets/pictures/car.png';
}; this.car_markers.set(car['vin'], car);
} }
},
toggleMarker() { err => this.logger.error(err),
const delta = this.getDelta(this.markers[0], this.markers[1], 100); () => {
this.logger.debug('loaded cars');
this.move(this.markers[0], delta, 0, 10, 100); console.log(this.car_markers);
for (const value of this.car_markers.values()) {
this.getCarEvents(value['vin']);
}
}
);
} }
move(source, delta, counter, delay, steps) { private trafficLightToImage(trafficLight) {
source.lat += delta.lat; return (trafficLight === 'RED') ? this.TL_RED_IMAGE : this.TL_GREEN_IMAGE;
source.lng += delta.lng;
if (counter !== steps) {
counter++;
setTimeout(this.move.bind(this), delay, source, delta, counter, delay, steps);
}
} }
openInfoWindow(id) {
this.infoWindows.set(id, true);
}
closeInfoWindow(id) {
this.infoWindows.delete(id);
}
isInfoWindowOpen(id) {
return this.infoWindows.has(id);
}
playCrashSound() {
const audio = new Audio();
audio.src = this.NCE_SOUND;
audio.load();
audio.play();
}
} }

View File

@ -32,7 +32,7 @@ export class InterceptorService implements HttpInterceptor {
} }
}); });
this.logger.debug('Interceptor works'); //this.logger.debug('Interceptor works');
// pipe the response observable // pipe the response observable
return next.handle(req).pipe( return next.handle(req).pipe(

View File

@ -2,7 +2,6 @@ import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core'; import {Injectable} from '@angular/core';
import {NGXLogger} from 'ngx-logger'; import {NGXLogger} from 'ngx-logger';
import {environment} from '../../environments/environment'; import {environment} from '../../environments/environment';
import {Observable} from 'rxjs';
@Injectable() @Injectable()
export class RestService { export class RestService {
@ -14,7 +13,20 @@ export class RestService {
) { ) {
} }
getTrafficLightEvents(tlid) {
return this.http.get(this.currentLocation + 'traffic_light_events?id=' + tlid, {observe: 'response'});
}
getCarEvents(vin) {
return this.http.get(this.currentLocation + 'car_events?vin=' + vin, {observe: 'response'});
}
getTrafficLights() { getTrafficLights() {
return this.http.get(this.currentLocation + 'traffic_lights'); return this.http.get(this.currentLocation + 'traffic_lights');
} }
getCars() {
return this.http.get(this.currentLocation + 'cars');
}
} }

View File

@ -1,52 +0,0 @@
import {Injectable} from '@angular/core';
import {NGXLogger} from 'ngx-logger';
import {fromEvent, interval, Observable} from 'rxjs';
import {environment} from '../../environments/environment';
import {WSEvents} from '../interfaces/interface';
@Injectable({
providedIn: 'root'
})
export class WebsocketService {
private wsEndpoint = environment.ws_location + ':' + environment.ws_port + '/test-ws-endpoint/';
private readonly ws: WebSocket;
private wsEvents: WSEvents;
constructor(private logger: NGXLogger) {
this.logger.debug('Initiating ws connection on', this.wsEndpoint);
this.ws = new WebSocket(this.wsEndpoint);
interval(5000).subscribe(() => {
// continuously check if the connection is still open
if (this.ws.readyState !== WebSocket.OPEN) {
this.logger.error('Lost websocket connection ...', this.ws);
}
});
}
wsTestCall(msg: string): WSEvents {
this.logger.debug(
'Performing ws test call',
'current ws ready state ==', this.ws.readyState + ',',
'expected == 1 == open');
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(msg);
} else {
return undefined;
}
if (!this.wsEvents) {
this.wsEvents = {
message: fromEvent(this.ws, 'message'),
error: fromEvent(this.ws, 'error'),
close: fromEvent(this.ws, 'close')
};
}
return this.wsEvents;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

View File

@ -2,8 +2,7 @@ import {NgxLoggerLevel} from 'ngx-logger';
export const environment = { export const environment = {
production: true, production: true,
location: window.location.hostname, location: 'xway',
port: 5004, port: 5004,
ws_url_root: 'ws://' + window.location.hostname + ':' + window.location.port + '/',
log_level: NgxLoggerLevel.WARN, log_level: NgxLoggerLevel.WARN,
}; };

View File

@ -6,10 +6,8 @@ import {NgxLoggerLevel} from 'ngx-logger';
export const environment = { export const environment = {
production: false, production: false,
location: 'xwayserver', location: 'xway',
port: 5004, port: 5004,
ws_location: 'ws://127.0.0.1',
ws_port: 8000,
log_level: NgxLoggerLevel.DEBUG, log_level: NgxLoggerLevel.DEBUG,
}; };

View File

@ -4,6 +4,11 @@
@import '~bootstrap/dist/css/bootstrap.min.css'; @import '~bootstrap/dist/css/bootstrap.min.css';
body { body {
width: 95%; width: 100%;
margin: auto; margin: 0;
padding: 0;
}
.gm-style-iw-a {
opacity: 0.8 !important;
} }

View File

@ -4,20 +4,20 @@
"location": [16.20719, 47.89584], "location": [16.20719, 47.89584],
"range": 542, "range": 542,
"switchingTime": 15, "switchingTime": 15,
"initialColor": "RED" "color": "RED"
}, },
{ {
"id": "2", "id": "2",
"location": [16.20814, 47.90937], "location": [16.20814, 47.90937],
"range": 725, "range": 725,
"switchingTime": 20, "switchingTime": 20,
"initialColor": "GREEN" "color": "GREEN"
}, },
{ {
"id": "3", "id": "3",
"location": [16.20917, 47.92703], "location": [16.20917, 47.92703],
"range": 910, "range": 910,
"switchingTime": 25, "switchingTime": 25,
"initialColor": "RED" "color": "RED"
} }
] ]

View File

@ -39,6 +39,14 @@ class TrafficLight:
tlid: str, tlid: str,
switching_time: int = SWITCHING_TIME, switching_time: int = SWITCHING_TIME,
starting_color: TrafficLightColor = TrafficLightColor.RED): starting_color: TrafficLightColor = TrafficLightColor.RED):
"""
Init a new Traffic Light client. This will already initialize its message queue.
Start it with self.start().
:param tlid: ID of the traffic light
:param switching_time: time in seconds to switch between states (TrafficLightColor.RED and .GREEN)
:param starting_color: TrafficLightColor to start with
"""
self.tlid = tlid self.tlid = tlid
self.switching_time = switching_time self.switching_time = switching_time
self._starting_color = starting_color self._starting_color = starting_color

View File

@ -70,6 +70,19 @@ class Vehicle:
vin: str, vin: str,
starting_point: geopy.Point = STARTING_POINT, starting_point: geopy.Point = STARTING_POINT,
starting_velocity: float = STARTING_VELOCITY): starting_velocity: float = STARTING_VELOCITY):
"""
Initialize a new vehicle client. Already initializes the corresponding sending and receiving message queue.
A vehicle sends every UPDATE_INTERVAL seconds its current DAF to the orchestrator. It receives as response the
new TargetVelocity to achieve a possible green wave. After a fixed amount of kilometers, the NCE event is
happening. The car then stands still for TIME_TO_RECOVER seconds. Afterwards, it starts driving with the last
known velocity. While it is recovering, it ignores the target velocity responses from the orchestrator.
A vehicle drives a full route between start and end. After reaching the end, it restores the internal state and
starts again from the beginning.
:param vin: Vehicle Identification Number
:param starting_point: point on globe to start at
:param starting_velocity: velocity to start driving with
"""
self.vin = vin self.vin = vin
self._starting_point = starting_point self._starting_point = starting_point
self._gps_location = starting_point self._gps_location = starting_point
@ -85,6 +98,10 @@ class Vehicle:
def nce(self): def nce(self):
""" """
On accessing this property, it is calculated if the NCE shall happen. NCE only happens up to once per route. On accessing this property, it is calculated if the NCE shall happen. NCE only happens up to once per route.
If the NCE happens, the car stops driving (velocity = 0) for TIME_TO_RECOVER seconds. In this time,
responses from the orchestrator are ignored. After recovery, the vehicle starts driving again with the last
known velocity.
:return: True if NCE invoked, otherwise False :return: True if NCE invoked, otherwise False
""" """
if self._nce_possible and not self._nce_happened: if self._nce_possible and not self._nce_happened:
@ -115,10 +132,13 @@ class Vehicle:
@property @property
def daf(self): def daf(self):
""" """
:return: "Datenaufzeichnung für automatisiertes Fahren" (DAF) object Return the DAF object of the vehicle. The properties are always evaluated on calling. That guarantees accurate
values.
:return: current "Datenaufzeichnung für automatisiertes Fahren" (DAF) object
""" """
# ATTENTION: ORDER MANDATORY # ATTENTION: ORDER MANDATORY (except for static vin)
return DAF(vehicle_identification_number=self.vin, return DAF(vehicle_identification_number=self.vin,
# first deduce nce - is calculated, sets velocity to 0 if NCE # first deduce nce - is calculated, sets velocity to 0 if NCE
near_crash_event=self.nce, near_crash_event=self.nce,
@ -133,10 +153,10 @@ class Vehicle:
@property @property
def gps_location(self): def gps_location(self):
""" """
Update self.gps_location with given speed in km/h and the driven time in seconds An accurate current position of the vehicle at the moment the property is called is retunred.
:param velocity: in km/h The gps location is derived from the last position this property was called at and the
:param time: in seconds time the vehicle was driving since then. Therefore, it is not necessary to call this function exactly at a given
:param bearing: direction in degrees: 0=N, 90=E, 180=S, 270=W time span, because it calculates the driven kms relative to the calling time.
""" """
# Define starting point. # Define starting point.
start = self._gps_location start = self._gps_location
@ -165,6 +185,9 @@ class Vehicle:
@property @property
def driven_kms(self): def driven_kms(self):
"""
:returns: a string representation of the driven kms
"""
return '{}km'.format(round(self._driven_kms, 2)) return '{}km'.format(round(self._driven_kms, 2))
def start_driving(self): def start_driving(self):
@ -211,12 +234,16 @@ class Vehicle:
@circuit(failure_threshold=10, expected_exception=AMQPConnectionError) @circuit(failure_threshold=10, expected_exception=AMQPConnectionError)
def send_status_update(self): def send_status_update(self):
"""
Sends the current DAF to the orchestrator. The orchestrator will then respond asynchronously on the response
queue.
"""
print(self.driven_kms, '\t', self.daf) print(self.driven_kms, '\t', self.daf)
self._daf_mb.send(pickle.dumps(self.daf)) self._daf_mb.send(pickle.dumps(self.daf))
def new_velocity(self, response: bytes): def new_velocity(self, response: bytes):
""" """
Will be invoked if new target velocity message received Will be invoked if new target velocity message received from orchestrator.
:param response: pickled TargetVelocity object :param response: pickled TargetVelocity object
""" """
response: TargetVelocity = pickle.loads(response) response: TargetVelocity = pickle.loads(response)

View File

@ -52,7 +52,7 @@ class Orchestrator:
self.vins.append(car['vin']) self.vins.append(car['vin'])
for traffic_light in traffic_lights['cursor']: for traffic_light in traffic_lights['cursor']:
self.tls[traffic_light['id']] = {'color': traffic_light['initialColor'], self.tls[traffic_light['id']] = {'color': traffic_light['color'],
'switching_time': traffic_light['switchingTime'], 'switching_time': traffic_light['switchingTime'],
'last_switch': datetime.now()} 'last_switch': datetime.now()}

View File

@ -24,6 +24,20 @@ class MBWrapper:
exchange_name: str = None, exchange_name: str = None,
callback: callable = None, callback: callable = None,
verbose: bool = False): 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' assert exchange_name, 'Please define an exchange name'
# append a connection to the logging broker, if it isn't the logging broker itself # append a connection to the logging broker, if it isn't the logging broker itself
@ -40,22 +54,37 @@ class MBWrapper:
self.verbose = verbose self.verbose = verbose
def print(self, *msg): def print(self, *msg):
"""
Modified print which prints only to stout with self.verbose.
:param msg: to print
"""
if self.verbose: if self.verbose:
print(*msg) print(*msg)
def setup_sender(self): def setup_sender(self):
"""
Setup the MBWrapper as sender.
"""
assert self._type != 'receiver', 'MBWrapper is already a receiver. Use another MBWrapper.' assert self._type != 'receiver', 'MBWrapper is already a receiver. Use another MBWrapper.'
self._type = 'sender' self._type = 'sender'
self._setup_channel() self._setup_channel()
def setup_receiver(self): 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._type != 'sender', 'MBWrapper is already a sender. Use another MBWrapper.'
assert self.callback, \ assert self.callback, \
'Please setup MBWrapper with "on response" self.callback which can handle a byte string as input.' 'Please setup MBWrapper with "on response" self.callback which can handle a byte string as input.'
def consumer(): def consumer():
""" """
Consumer thread which waits for incoming messages, invokes self._receive If initialized as receiver: Consumer thread which waits for incoming messages, invokes self._receive
""" """
self._setup_channel() self._setup_channel()
result = self._channel.queue_declare(queue='', exclusive=True) result = self._channel.queue_declare(queue='', exclusive=True)
@ -73,6 +102,11 @@ class MBWrapper:
Thread(target=consumer).start() Thread(target=consumer).start()
def send(self, message: bytes): 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: if type(message) is not bytes:
message = str(message).encode() message = str(message).encode()
self._channel.basic_publish(exchange=self.exchange_name, routing_key='', body=message) self._channel.basic_publish(exchange=self.exchange_name, routing_key='', body=message)
@ -84,9 +118,16 @@ class MBWrapper:
self._logger.send(message) self._logger.send(message)
def close(self): def close(self):
"""
Closes the connection.
"""
self._connection.close() self._connection.close()
def _setup_channel(self): 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 connect_succeeded = False
while not connect_succeeded: while not connect_succeeded:
try: try:
@ -102,6 +143,7 @@ class MBWrapper:
def _receive(self, ch, method, properties, body): def _receive(self, ch, method, properties, body):
""" """
Reduce complexity, only forward the message body to the callback method Reduce complexity, only forward the message body to the callback method. The others are not necessary for our
application example.
""" """
self.callback(body) self.callback(body)

View File

@ -1,3 +1,5 @@
flask flask
Flask-Cors Flask-Cors
requests requests
Flask-PyMongo
jsonify

View File

@ -12,18 +12,12 @@ ENTITY_IDENT_URL = 'http://entityident:5002/api/v1/resources/'
EVENT_STORE_URL = 'http://eventstore:5001/api/keys/' EVENT_STORE_URL = 'http://eventstore:5001/api/keys/'
@app.route('/')
def hello_world():
return 'Hello World'
@app.route('/api/v1/resources/car_events', methods=['GET']) @app.route('/api/v1/resources/car_events', methods=['GET'])
def get_cars_events(): def get_cars_events():
vin = request.args.get('vin') vin = request.args.get('vin')
try: try:
response = requests.get(EVENT_STORE_URL + 'DAF:' + vin + '/') response = requests.get(EVENT_STORE_URL + 'DAF:' + vin + '/0/')
cars = json.loads(response.text) cars = json.loads(response.text)
except requests.exceptions.ConnectionError as e: except requests.exceptions.ConnectionError as e:
@ -32,9 +26,24 @@ def get_cars_events():
return json_util.dumps({'cursor': cars}) return json_util.dumps({'cursor': cars})
@app.route('/api/v1/resources/traffic_light_events', methods=['GET'])
def get_traffic_light_events():
id = request.args.get('id')
try:
response = requests.get(EVENT_STORE_URL + 'TL:' + id + '/0/')
traffic_lights = json.loads(response.text)
except requests.exceptions.ConnectionError as e:
print("Is the EVENT_STORE_URL running and reachable?")
raise e
return json_util.dumps({'cursor': traffic_lights})
@app.route('/api/v1/resources/cars', methods=['GET']) @app.route('/api/v1/resources/cars', methods=['GET'])
def get_cars(): def get_cars():
try: try:
response = requests.get(ENTITY_IDENT_URL + 'cars') response = requests.get(ENTITY_IDENT_URL + 'cars')
cars = response.json()['cursor'] cars = response.json()['cursor']
@ -48,7 +57,6 @@ def get_cars():
@app.route('/api/v1/resources/traffic_lights', methods=['GET']) @app.route('/api/v1/resources/traffic_lights', methods=['GET'])
def get_traffic_lights(): def get_traffic_lights():
try: try:
response = requests.get(ENTITY_IDENT_URL + 'traffic_lights') response = requests.get(ENTITY_IDENT_URL + 'traffic_lights')
traffic_lights = response.json()['cursor'] traffic_lights = response.json()['cursor']

View File

@ -90,4 +90,14 @@ services:
depends_on: depends_on:
- entityident - entityident
- orchestration - orchestration
- eventstore - eventstore
controlcenter:
build:
context: ../components/control_center
dockerfile: Dockerfile
expose:
- 80
ports:
- 80:80
depends_on:
- xway