Merge branch 'master' of gitlab.com:kranklyboy/webapptemplate
This commit is contained in:
commit
6806f1f5dd
@ -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
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,5 +1,3 @@
|
|||||||
html, body {
|
#map{
|
||||||
height: 100%;
|
height : 100vh; width: 100%; padding:0px; margin: 0px;
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
BIN
components/control_center/src/assets/pictures/car_orange.png
Normal file
BIN
components/control_center/src/assets/pictures/car_orange.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
components/control_center/src/assets/sound/crash.mp3
Normal file
BIN
components/control_center/src/assets/sound/crash.mp3
Normal file
Binary file not shown.
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -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()}
|
||||||
|
|
||||||
|
|||||||
@ -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)
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
flask
|
flask
|
||||||
Flask-Cors
|
Flask-Cors
|
||||||
requests
|
requests
|
||||||
|
Flask-PyMongo
|
||||||
|
jsonify
|
||||||
@ -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']
|
||||||
|
|||||||
@ -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
|
||||||
Loading…
x
Reference in New Issue
Block a user