Part V - Live Control¶
In this part we will learn how to use websockets to send commands and receive information from a drone. In this fashion we are able to communicate data to and from the drone almost in real time with very little overhead.
1 - Socket Setup¶
Firstly, add the ng-socket-io package version 3.2.0 to your project. ! It has to be version 3.2.0 !
npm install ngx-socket-io@3.2.0
Secondly, let us add the sockets configurations on asset.service.ts:
1import { HttpClient, HttpParams } from '@angular/common/http';
2import { Injectable } from '@angular/core';
3import { Observable } from 'rxjs/internal/Observable';
4import { PaginatorDto } from 'src/app/lib/models/paginator.dto';
5import { Asset } from '../models/asset';
6import { Socket, SocketIoConfig } from 'ngx-socket-io';
7import { WebStorageService } from '../../lib/web-storage.service';
8
9@Injectable({
10 providedIn: 'root'
11})
12export class AssetService {
13
14
15 backendUrl = 'https://bexstream-preprod.beyond-vision.com/api/v1/asset';
16 getAllDronesUrl = this.backendUrl + '/all/drone';
17
18 config: SocketIoConfig = {
19 url: 'https://bexstream-preprod.beyond-vision.com',
20 options: {
21 query: {
22 source: 'frontend',
23 page: 'monitor',
24 token: this.webStorage.getStoredToken()
25 }
26 }
27 };
28 private socket: Socket = new Socket(this.config);
29
30 public initSocket(): Socket {
31 this.config.options.query.token = this.webStorage.getStoredToken();
32 this.socket.connect();
33 return this.socket;
34 }
35
36 public closeSocket() {
37 this.socket?.disconnect();
38 }
39
40 public sendSelectedAsset(droneData: any) {
41 this.socket.emit('/frontend/selectedAsset', droneData);
42 }
43}
Now that we have setup the socket configuration we need to use these methods on the DroneListComponent.
1import { Component, OnInit, OnDestroy } from '@angular/core';
2import { PaginatorDto } from 'src/app/lib/models/paginator.dto';
3import { Asset } from '../../models/asset';
4import { AssetService } from '../../services/asset.service';
5
6@Component({
7 selector: 'app-drone-list',
8 templateUrl: './drone-list.component.html',
9 styleUrls: ['./drone-list.component.less']
10})
11export class DroneListComponent implements OnInit, OnDestroy {
12
13 assets: Asset[] = [];
14 paginator: PaginatorDto = new PaginatorDto();
15
16 constructor(private assetService: AssetService) { }
17
18 ngOnInit(): void {
19 this.paginator.limit = 0;
20 this.paginator.filter = '';
21 this.listDrones();
22
23 this.assetService.initSocket();
24 }
25
26 ngOnDestroy() {
27 this.assetService.closeSocket();
28 }
29
30 ...
31}
2 - Socket subscription¶
Bexstream’s websockets follow the Publish-Subscriber architecture, to send information you must publish it and to receive inforamtion you must subscribe to it.
On this step we will see how to get the drone’s current position, by subscribing to the corresponding publisher.
1 - Updating Drone Models¶
The first step is to add to our application the extra models that are needed to deal with the new information that we are getting. In order to do this we need to expand the current Drone Model.
1import { DroneFlightStatus } from "./droneFlightStatus";
2import { MissionDrone } from "./missionDrone";
3
4export class Drone {
5 id: string;
6 latitude: number; // last latitude recorded once disconnects
7 longitude: number; // last longitude recorded once disconnects
8 onMission: boolean;
9 hasCamera?: boolean;
10 has360Camera?: boolean;
11 hasSpeaker?: boolean;
12 isDirectConnect?: boolean; // usually via telemetry
13 md: MissionDrone;
14 flightStatus: DroneFlightStatus;
15
16 constructor() {
17 this.id = '';
18 this.latitude = 0.0;
19 this.isDirectConnect = false;
20 this.longitude = 0.0;
21 this.onMission = false;
22 this.hasSpeaker = false;
23 this.hasCamera = false;
24 this.has360Camera = false;
25 this.md = new MissionDrone();
26 this.flightStatus = new DroneFlightStatus();
27
28 }
29}
Add the new files: missionDrone.ts, mavrosState.ts, position.ts:
1import { MavrosState } from "./mavrosState";
2import { Position } from "./position";
3
4export class MissionDrone {
5 name: string;
6 battery: number;
7 satellite = 0;
8 position: Position;
9 gpsFixMode = '';
10 compassAngle: number;
11 previousD = 0;
12 queue: [number, number] = [0, 0];
13 deltaT = 0;
14 state: MavrosState | null;
15
16 constructor(name?: string, state?: MavrosState, position?: Position, battery?: number, compassAngle?: number) {
17 if (name) {
18 this.name = name;
19 } else {
20 this.name = '';
21 }
22
23 if (state) {
24 this.state = state;
25 } else {
26 this.state = {
27 connected: false,
28 armed: false,
29 guided: false,
30 manual_input: false,
31 mode: 'STABILIZE',
32 system_status: 0,
33 armedString: 'Not Armed'
34 };
35 }
36
37 if (position) {
38 this.position = position;
39 } else {
40 this.position = { latitude: 0.0, longitude: 0.0, altitude: 0.0 };
41 }
42
43 this.battery = battery || 0;
44
45 this.compassAngle = compassAngle || 0;
46 }
47}
1export interface MavrosState {
2 connected: boolean;
3 armed: boolean;
4 armedString?: string;
5 guided: boolean;
6 manual_input: boolean;
7 mode: string;
8 system_status: number;
9}
1export interface Position {
2 latitude: number;
3 longitude: number;
4 altitude: number;
5}
Add the new file, droneFlightStatus.ts:
1export class DroneFlightStatus {
2 id?: string;
3 isTakeOff: boolean;
4 isLanded: boolean;
5 isFlying: boolean;
6 guided: boolean;
7 armed: boolean;
8 connected: boolean;
9 lastKnownBattery: number;
10 lastLandDate?: Date;
11 lastTakeOffDate?: Date;
12
13 constructor() {
14 this.isLanded = true;
15 this.isFlying = false;
16 this.isTakeOff = false;
17 this.armed = false;
18 this.connected = false;
19 this.lastKnownBattery = 0;
20 this.guided = false;
21 }
22}
And finally add velocity.ts
1export interface Velocity {
2 x: number;
3 y: number;
4 z: number;
5 roll: number;
6 pitch: number;
7 yaw: number;
8}
2 - Adding the needed methods on AssetService¶
We will add the sendSelectedAsset and the getAssetPos methods. The sendSelectedAsset is a publisher method that sends the id of the drone that this socket connection wants to get data of. The getAssetPos method is where we actually will get the drone positioning data from.
1import { HttpClient, HttpParams } from '@angular/common/http';
2import { Injectable } from '@angular/core';
3import { Observable } from 'rxjs/internal/Observable';
4import { PaginatorDto } from 'src/app/lib/models/paginator.dto';
5import { Asset } from '../models/asset';
6import { Socket, SocketIoConfig } from 'ngx-socket-io';
7import { WebStorageService } from '../../lib/web-storage.service';
8import { Position } from '../models/position';
9
10@Injectable({
11 providedIn: 'root'
12})
13export class AssetService {
14
15
16 backendUrl = 'https://bexstream-preprod.beyond-vision.com/api/v1/asset';
17 getAllDronesUrl = this.backendUrl + '/all/drone';
18
19 config: SocketIoConfig = {
20 url: 'https://bexstream-preprod.beyond-vision.com',
21 options: {
22 query: {
23 source: 'frontend',
24 page: 'monitor',
25 token: this.webStorage.getStoredToken()
26 }
27 }
28 };
29 private socket: Socket = new Socket(this.config);
30
31 public initSocket(): Socket {
32 this.config.options.query.token = this.webStorage.getStoredToken();
33 this.socket.connect();
34 return this.socket;
35 }
36
37 public closeSocket() {
38 this.socket?.disconnect();
39 }
40
41
42 /**
43 * Subscription methods
44 */
45 public getAssetPos(assetId: string): Observable<Position> {
46 return new Observable<Position>(observer => {
47 this.socket.on(assetId + '/position', (data: any) => {
48 observer.next({
49 latitude: data.x,
50 longitude: data.y,
51 altitude: data.a
52 });
53 });
54 });
55 }
56
57
58
59 /**
60 * Publish methods
61 */
62 public sendSelectedAsset(droneData: any) {
63 this.socket.emit('/frontend/selectedAsset', droneData);
64 }
65}
2 - Subscribing to backend API¶
Firstly we need to select the drone that we want to get data from, to do this we must use the sendSelectedAsset method. Then we must subscribe to the needed publishers of drone data.
1import { Component, OnDestroy, OnInit } from '@angular/core';
2import { Subscription } from 'rxjs';
3import { PaginatorDto } from 'src/app/lib/models/paginator.dto';
4import { Asset } from '../../models/asset';
5import { MissionDrone } from '../../models/missionDrone';
6import { Position } from '../../models/position';
7import { Velocity } from '../../models/velocity';
8import { AssetService } from '../../services/asset.service';
9
10@Component({
11 selector: 'app-drone-list',
12 templateUrl: './drone-list.component.html',
13 styleUrls: ['./drone-list.component.less']
14})
15export class DroneListComponent implements OnInit, OnDestroy {
16
17 assets: Asset[] = [];
18 paginator: PaginatorDto = new PaginatorDto();
19 subscriptions: Subscription = new Subscription();
20
21 selectedAsset: Asset = null;
22
23 constructor(private assetService: AssetService) { }
24
25 ngOnInit(): void {
26 this.paginator.limit = 0;
27 this.paginator.filter = '';
28 this.listDrones();
29
30 this.assetService.initSocket();
31 }
32
33 ngOnDestroy() {
34 this.subscriptions?.unsubscribe();
35 this.assetService.closeSocket();
36 }
37
38 private listDrones() {
39 this.assetService
40 .getAllDrones(this.paginator)
41 .subscribe((assets: Asset[]) => {
42 this.assets = assets;
43 })
44 }
45
46 public selectAsset(selectedAsset: Asset) {
47 this.selectedAsset = selectedAsset;
48 this.selectedAsset.drone.md = new MissionDrone();
49
50 this.initSubscriptions();
51 }
52
53 public initSubscriptions() {
54 this.assetService.sendSelectedAsset(this.selectedAsset.id);
55
56 this.subscriptions?.add(this.assetService.getAssetPos(this.selectedAsset.id).subscribe((data: Position) => {
57 if (this.selectedAsset.drone) {
58 this.selectedAsset.drone.md.position.latitude = data.latitude;
59 this.selectedAsset.drone.md.position.longitude = data.longitude;
60 this.selectedAsset.drone.md.position.altitude = data.altitude;
61 }
62 }));
63 }
64}
3 - Showing the data on UI¶
Now that we are already receiving data we can show it on the UI. Edit the drone-list.component.html.
1<h3>Drones List</h3>
2<hr><hr>
3<div *ngFor="let asset of assets">
4 <h5>{{ asset.name }}</h5>
5 <p>{{ asset.drone.id }}</p>
6 <p *ngIf="asset.drone.latitude">Latitude: {{ asset.drone.latitude }}, Longitude: {{ asset.drone.longitude }}</p>
7 <p *ngIf="asset.drone.onMission">On Mission</p>
8 <p *ngIf="!asset.drone.onMission">Not on Mission</p>
9 <p *ngIf="asset.isActive">Active</p>
10 <p>Last Connected: {{asset.lastConnected}}</p>
11
12 <p *ngIf="asset.isActive && (!selectedAsset || asset.id !== selectedAsset.id)">
13 <button (click)="selectAsset(asset)">Select Drone</button>
14 </p>
15
16 <p *ngIf="asset.isActive && selectedAsset && asset.id === selectedAsset.id">
17 <span><b>Latitude:</b> {{ asset.drone.md.position.latitude }}</span> <br>
18 <span><b>Longitude:</b> {{ asset.drone.md.position.longitude }}</span> <br>
19 <span><b>Altitude:</b> {{ asset.drone.md.position.altitude }}</span> <br>
20 </p>
21
22 <hr>
23</div>
Run your application and run a simulated/real drone. Once the drone is active, the “select” button will appear and then you can select that drone. After selecting the drone the GPS position will appear like in the following image:
After selecting the drone, its GPS position will appear and the “Actions” buttons is shown.
3 - Dowloading the Drone’s Config¶
In this section we will add the features needed to download the Drone’s Config file. This file is credential that the drone will use to identify itself when connecting to the backend API. It is needed on real and simulated drones. On the next chapter we will explain how to run a simulated drone and how to use this file.
1 - Adding downloadConfig method on AssetService¶
1import { HttpClient, HttpParams } from '@angular/common/http';
2import { Injectable } from '@angular/core';
3import { Observable } from 'rxjs/internal/Observable';
4import { PaginatorDto } from 'src/app/lib/models/paginator.dto';
5import { Asset } from '../models/asset';
6import { Socket, SocketIoConfig } from 'ngx-socket-io';
7import { WebStorageService } from '../../lib/web-storage.service';
8import { Position } from '../models/position';
9
10@Injectable({
11 providedIn: 'root'
12})
13export class AssetService {
14
15
16 backendUrl = 'https://bexstream-preprod.beyond-vision.com/api/v1/asset';
17 getAllDronesUrl = this.backendUrl + '/all/drone';
18 getConfigUrl = this.backendUrl + '/config';
19
20 config: SocketIoConfig = {
21 url: 'https://bexstream-preprod.beyond-vision.com',
22 options: {
23 query: {
24 source: 'frontend',
25 page: 'monitor',
26 token: this.webStorage.getStoredToken()
27 }
28 }
29 };
30 private socket: Socket = new Socket(this.config);
31
32 public initSocket(): Socket {
33 this.config.options.query.token = this.webStorage.getStoredToken();
34 this.socket.connect();
35 return this.socket;
36 }
37
38 public closeSocket() {
39 this.socket?.disconnect();
40 }
41
42 public downloadConfig(id: string): Observable<any> {
43 return this.http.get<any>(`${this.getConfigUrl}/${id}`);
44 }
45
46
47 /**
48 * Subscription methods
49 */
50 public getAssetPos(assetId: string): Observable<Position> {
51 return new Observable<Position>(observer => {
52 this.socket.on(assetId + '/position', (data: any) => {
53 observer.next({
54 latitude: data.x,
55 longitude: data.y,
56 altitude: data.a
57 });
58 });
59 });
60 }
61
62
63
64 /**
65 * Publish methods
66 */
67 public sendSelectedAsset(droneData: any) {
68 this.socket.emit('/frontend/selectedAsset', droneData);
69 }
70}
2 - Download File logic¶
1import { Component, OnDestroy, OnInit } from '@angular/core';
2import { Subscription } from 'rxjs';
3import { PaginatorDto } from 'src/app/lib/models/paginator.dto';
4import { Asset } from '../../models/asset';
5import { MissionDrone } from '../../models/missionDrone';
6import { Position } from '../../models/position';
7import { Velocity } from '../../models/velocity';
8import { AssetService } from '../../services/asset.service';
9
10@Component({
11 selector: 'app-drone-list',
12 templateUrl: './drone-list.component.html',
13 styleUrls: ['./drone-list.component.less']
14})
15export class DroneListComponent implements OnInit, OnDestroy {
16
17 assets: Asset[] = [];
18 paginator: PaginatorDto = new PaginatorDto();
19 subscriptions: Subscription = new Subscription();
20
21 selectedAsset: Asset = null;
22
23 constructor(private assetService: AssetService) { }
24
25 ngOnInit(): void {
26 this.paginator.limit = 0;
27 this.paginator.filter = '';
28 this.listDrones();
29
30 this.assetService.initSocket();
31 }
32
33 ngOnDestroy() {
34 this.subscriptions?.unsubscribe();
35 this.assetService.closeSocket();
36 }
37
38 private listDrones() {
39 this.assetService
40 .getAllDrones(this.paginator)
41 .subscribe((assets: Asset[]) => {
42 this.assets = assets;
43 })
44 }
45
46 public selectAsset(selectedAsset: Asset) {
47 this.selectedAsset = selectedAsset;
48 this.selectedAsset.drone.md = new MissionDrone();
49
50 this.initSubscriptions();
51 }
52
53 public initSubscriptions() {
54 this.assetService.sendSelectedAsset(this.selectedAsset.id);
55
56 this.subscriptions?.add(this.assetService.getAssetPos(this.selectedAsset.id).subscribe((data: Position) => {
57 if (this.selectedAsset.drone) {
58 this.selectedAsset.drone.md.position.latitude = data.latitude;
59 this.selectedAsset.drone.md.position.longitude = data.longitude;
60 this.selectedAsset.drone.md.position.altitude = data.altitude;
61 }
62 }));
63 }
64
65 downloadFile(data: string) {
66 const blob = new Blob([data], {type: 'text/csv'});
67 const url = window.URL.createObjectURL(blob);
68 const anchor = document.createElement('a');
69 anchor.download = 'config.' + environment.platformExtension;
70 anchor.href = url;
71 anchor.click();
72 }
73
74 downloadConfig(droneId: string) {
75 this.assetService
76 .downloadConfig(droneId)
77 .subscribe(data => {
78 this.downloadFile(data.cypher);
79 });
80 }
81}
4 - Launching the simulation¶
You have two ways to interact with a drone: i) use a real one; ii) use a simulated one.
The following steps will show you how to launch a simulated drone on your pc.
1 - Setup¶
First, install the HEIFU docker simulation <https://github.com/BV-OpenSource/BV-UAV-Docker/tree/master> on your pc.
git clone https://github.com/BV-OpenSource/BV-UAV-Docker.git
2 - Configurations¶
Next download the config file for the specific drone and place it on ControlDocker/config.bext. In this tutorial you can use the download button that we created previously.
Select the environment that you will work on. In our case we will use the prod environment.
On ControlDocker/rosCatkinEntrypoint.sh change line 6 to argEndpoint:=prod.
1#!/bin/bash
2
3# Source ROS distro environment and local catwin workspace
4source "$HOME_WS/.profile" && source "/opt/ros/$ROS_DISTRO/setup.bash" && sourc$
5
6command="$@ argEndpoint:=prod argCamType:=imx477"
7bash -c "$command"
3 - Running the simulated drone¶
cd BV-UAV-Docker
./runDockers.sh
Wait for it to install the requirements, it takes like 15-20 minutes.
When it is running you can check on the drones list that the corresponding drone is now “Active” and you can click on “Select Drone” to start sending commands.
5 - Sending commands to the drone¶
1 - Add the needed methods to the service component:¶
In this step we want to send to the drone the following commands:
Takeoff
Land
Move left, right, forward and backwards
So we will need to add these actions on the AssetService.
1import { HttpClient, HttpParams } from '@angular/common/http';
2import { Injectable } from '@angular/core';
3import { Observable } from 'rxjs/internal/Observable';
4import { PaginatorDto } from 'src/app/lib/models/paginator.dto';
5import { Asset } from '../models/asset';
6import { Socket, SocketIoConfig } from 'ngx-socket-io';
7import { WebStorageService } from '../../lib/web-storage.service';
8
9@Injectable({
10 providedIn: 'root'
11})
12export class AssetService {
13
14
15 backendUrl = 'https://bexstream-preprod.beyond-vision.com/api/v1/asset';
16 getAllDronesUrl = this.backendUrl + '/all/drone';
17
18 config: SocketIoConfig = {
19 url: 'https://bexstream-preprod.beyond-vision.com',
20 options: {
21 query: {
22 source: 'frontend',
23 page: 'monitor',
24 token: this.webStorage.getStoredToken()
25 }
26 }
27 };
28 private socket: Socket = new Socket(this.config);
29
30 public initSocket(): Socket {
31 this.config.options.query.token = this.webStorage.getStoredToken();
32 this.socket.connect();
33 return this.socket;
34 }
35
36 public closeSocket() {
37 this.socket?.disconnect();
38 }
39
40
41 /**
42 * Subscription methods
43 */
44 public getAssetPos(assetId: string): Observable<Position> {
45 return new Observable<Position>(observer => {
46 this.socket.on(assetId + '/position', (data: any) => {
47 observer.next({
48 latitude: data.x,
49 longitude: data.y,
50 altitude: data.a
51 });
52 });
53 });
54 }
55
56
57 /**
58 * Publish methods
59 */
60 public sendSelectedAsset(droneData: any) {
61 this.socket.emit('/frontend/selectedAsset', droneData);
62 }
63
64 // Sends Take off
65 public sendDroneTakeoff(droneData: any) {
66 this.socket?.emit('/frontend/takeoff', droneData);
67 }
68
69 // Sends Land
70 public sendDroneLand(droneData: any) {
71 this.socket?.emit('/frontend/land', droneData);
72 }
73
74 // Sends Velocity Command (used to move drone)
75 public sendDroneVel(droneData: any) {
76 this.socket?.emit('/frontend/cmd', droneData);
77 }
78}
2 - Add the needed methods to the DroneList component:¶
Our next step is to update the state of our drone and add logic on top of it. We do not want the drone to move if it is still landed. So we will have a flightStatus variable with two flags: isLanded and isFlying.
To do this add the next subscription:
1import { Component, OnDestroy, OnInit } from '@angular/core';
2import { Subscription } from 'rxjs';
3import { PaginatorDto } from 'src/app/lib/models/paginator.dto';
4import { Asset } from '../../models/asset';
5import { MavrosState } from '../../models/mavrosState';
6import { MissionDrone } from '../../models/missionDrone';
7import { Position } from '../../models/position';
8import { Velocity } from '../../models/velocity';
9import { AssetService } from '../../services/asset.service';
10
11@Component({
12 selector: 'app-drone-list',
13 templateUrl: './drone-list.component.html',
14 styleUrls: ['./drone-list.component.less']
15})
16export class DroneListComponent implements OnInit, OnDestroy {
17
18 assets: Asset[] = [];
19 paginator: PaginatorDto = new PaginatorDto();
20 subscriptions: Subscription = new Subscription();
21
22 selectedAsset: Asset = null;
23
24 constructor(private assetService: AssetService) { }
25
26 ngOnInit(): void {
27 this.paginator.limit = 0;
28 this.paginator.filter = '';
29 this.listDrones();
30
31 this.assetService.initSocket();
32 }
33
34 ngOnDestroy() {
35 this.subscriptions?.unsubscribe();
36 this.assetService.closeSocket();
37 }
38
39 private listDrones() {
40 this.assetService
41 .getAllDrones(this.paginator)
42 .subscribe((assets: Asset[]) => {
43 this.assets = assets;
44 })
45 }
46
47 public selectAsset(selectedAsset: Asset) {
48 this.selectedAsset = selectedAsset;
49 this.selectedAsset.drone.md = new MissionDrone();
50
51 this.initSubscriptions();
52 }
53
54 public initSubscriptions() {
55 this.assetService.sendSelectedAsset(this.selectedAsset.id);
56
57 this.subscriptions?.add(this.assetService.getAssetPos(this.selectedAsset.id).subscribe((data: Position) => {
58 if (this.selectedAsset.drone) {
59 this.selectedAsset.drone.md.position.latitude = data.latitude;
60 this.selectedAsset.drone.md.position.longitude = data.longitude;
61 this.selectedAsset.drone.md.position.altitude = data.altitude;
62 }
63 }));
64
65 this.subscriptions?.add(this.assetService.getDroneState(this.selectedAsset.id).subscribe((data: MavrosState) => {
66 if (this.selectedAsset.drone) {
67 this.selectedAsset.drone.md.state = data;
68
69 if (data.system_status > 3) { // flying
70 this.selectedAsset.drone.flightStatus.isFlying = true;
71 this.selectedAsset.drone.flightStatus.isLanded = false;
72 } else if (data.system_status === 3) { // landed and ready to takeoff
73 this.selectedAsset.drone.flightStatus.isFlying = false;
74 this.selectedAsset.drone.flightStatus.isLanded = true;
75 }
76 }
77 }
78 }
79
80 downloadFile(data: string) {
81 const blob = new Blob([data], {type: 'text/csv'});
82 const url = window.URL.createObjectURL(blob);
83 const anchor = document.createElement('a');
84 anchor.download = 'config.' + environment.platformExtension;
85 anchor.href = url;
86 anchor.click();
87 }
88
89 downloadConfig(droneId: string) {
90 this.assetService
91 .downloadConfig(droneId)
92 .subscribe(data => {
93 this.downloadFile(data.cypher);
94 });
95 }
96}
After these flags have been added let us add the methods to control the drone.
1import { Component, OnDestroy, OnInit } from '@angular/core';
2import { Subscription } from 'rxjs';
3import { PaginatorDto } from 'src/app/lib/models/paginator.dto';
4import { Asset } from '../../models/asset';
5import { MavrosState } from '../../models/mavrosState';
6import { MissionDrone } from '../../models/missionDrone';
7import { Position } from '../../models/position';
8import { Velocity } from '../../models/velocity';
9import { AssetService } from '../../services/asset.service';
10
11@Component({
12 selector: 'app-drone-list',
13 templateUrl: './drone-list.component.html',
14 styleUrls: ['./drone-list.component.less']
15})
16export class DroneListComponent implements OnInit, OnDestroy {
17
18 assets: Asset[] = [];
19 paginator: PaginatorDto = new PaginatorDto();
20 subscriptions: Subscription = new Subscription();
21
22 selectedAsset: Asset = null;
23
24 constructor(private assetService: AssetService) { }
25
26 ngOnInit(): void {
27 this.paginator.limit = 0;
28 this.paginator.filter = '';
29 this.listDrones();
30
31 this.assetService.initSocket();
32 }
33
34 ngOnDestroy() {
35 this.subscriptions?.unsubscribe();
36 this.assetService.closeSocket();
37 }
38
39 private listDrones() {
40 this.assetService
41 .getAllDrones(this.paginator)
42 .subscribe((assets: Asset[]) => {
43 this.assets = assets;
44 })
45 }
46
47 public selectAsset(selectedAsset: Asset) {
48 this.selectedAsset = selectedAsset;
49 this.selectedAsset.drone.md = new MissionDrone();
50
51 this.initSubscriptions();
52 }
53
54 public initSubscriptions() {
55 this.assetService.sendSelectedAsset(this.selectedAsset.id);
56
57 this.subscriptions?.add(this.assetService.getAssetPos(this.selectedAsset.id).subscribe((data: Position) => {
58 if (this.selectedAsset.drone) {
59 this.selectedAsset.drone.md.position.latitude = data.latitude;
60 this.selectedAsset.drone.md.position.longitude = data.longitude;
61 this.selectedAsset.drone.md.position.altitude = data.altitude;
62 }
63 }));
64
65 this.subscriptions?.add(this.assetService.getDroneState(this.selectedAsset.id).subscribe((data: MavrosState) => {
66 if (this.selectedAsset.drone) {
67 this.selectedAsset.drone.md.state = data;
68
69 if (data.system_status > 3) { // flying
70 this.selectedAsset.drone.flightStatus.isFlying = true;
71 this.selectedAsset.drone.flightStatus.isLanded = false;
72 } else if (data.system_status === 3) { // landed and ready to takeoff
73 this.selectedAsset.drone.flightStatus.isFlying = false;
74 this.selectedAsset.drone.flightStatus.isLanded = true;
75 }
76 }
77 }
78 }
79
80 downloadFile(data: string) {
81 const blob = new Blob([data], {type: 'text/csv'});
82 const url = window.URL.createObjectURL(blob);
83 const anchor = document.createElement('a');
84 anchor.download = 'config.' + environment.platformExtension;
85 anchor.href = url;
86 anchor.click();
87 }
88
89 downloadConfig(droneId: string) {
90 this.assetService
91 .downloadConfig(droneId)
92 .subscribe(data => {
93 this.downloadFile(data.cypher);
94 });
95 }
96
97
98 /**
99 * Commands to control the drone.
100 */
101 public takeOffDrone() {
102 const droneData = { msg: '', assetName: this.selectedAsset.name, assetId: this.selectedAsset.id };
103 this.assetService.sendDroneTakeoff(droneData);
104 }
105
106 public landDrone() {
107 const droneData = { msg: '', assetName: this.selectedAsset.name, assetId: this.selectedAsset.id };
108 this.assetService.sendDroneLand(droneData);
109 }
110
111 public moveDroneLeft() {
112 const vel: Velocity = { x: 0.0, y: 3.0, z: 0.0, roll: 0.0, pitch: 0.0, yaw: 0.0 };
113 const droneDataVel = { msg: vel, assetName: this.selectedAsset.name, assetId: this.selectedAsset.id };
114
115 vel.y = this.DRONE_FE_MAX_SPEED;
116
117 this.assetService.sendDroneVel(droneDataVel);
118 }
119
120 public moveDroneRight() {
121 const vel: Velocity = { x: 0.0, y: 0.0, z: 0.0, roll: 0.0, pitch: 0.0, yaw: 0.0 };
122 const droneDataVel = { msg: vel, assetName: this.selectedAsset.name, assetId: this.selectedAsset.id };
123
124 vel.y = - this.DRONE_FE_MAX_SPEED;
125
126 this.assetService.sendDroneVel(droneDataVel);
127 }
128
129
130 public moveDroneForward() {
131 const vel: Velocity = { x: 0.0, y: 3.0, z: 0.0, roll: 0.0, pitch: 0.0, yaw: 0.0 };
132 const droneDataVel = { msg: vel, assetName: this.selectedAsset.name, assetId: this.selectedAsset.id };
133
134 vel.x = this.DRONE_FE_MAX_SPEED;
135
136 this.assetService.sendDroneVel(droneDataVel);
137 }
138
139 public moveDroneBack() {
140 const vel: Velocity = { x: 0.0, y: 0.0, z: 0.0, roll: 0.0, pitch: 0.0, yaw: 0.0 };
141 const droneDataVel = { msg: vel, assetName: this.selectedAsset.name, assetId: this.selectedAsset.id };
142
143 vel.x = - this.DRONE_FE_MAX_SPEED;
144
145 this.assetService.sendDroneVel(droneDataVel);
146 }
147}
3 - Add these functionalities onto the UI:¶
1<h3>Drones List</h3>
2<hr><hr>
3<div *ngFor="let asset of assets">
4 <h5>{{ asset.name }}</h5>
5 <p>{{ asset.drone.id }}</p>
6 <p *ngIf="asset.drone.latitude">Latitude: {{ asset.drone.latitude }}, Longitude: {{ asset.drone.longitude }}</p>
7 <p *ngIf="asset.drone.onMission">On Mission</p>
8 <p *ngIf="!asset.drone.onMission">Not on Mission</p>
9 <p *ngIf="asset.isActive">Active</p>
10 <p>Last Connected: {{asset.lastConnected}}</p>
11
12 <p>
13 <button (click)="downloadConfig(asset.id)">
14 Download Config File
15 </button>
16 </p>
17
18 <p *ngIf="asset.isActive && (!selectedAsset || asset.id !== selectedAsset.id)">
19 <button (click)="selectAsset(asset)">Select Drone</button>
20 </p>
21
22 <p *ngIf="asset.isActive && selectedAsset && asset.id === selectedAsset.id">
23 <span><b>Latitude:</b> {{ asset.drone.md.position.latitude }}</span> <br>
24 <span><b>Longitude:</b> {{ asset.drone.md.position.longitude }}</span> <br>
25 <span><b>Altitude:</b> {{ asset.drone.md.position.altitude }}</span> <br>
26 </p>
27
28 <table *ngIf="asset.isActive && selectedAsset && asset.id === selectedAsset.id">
29 <tbody>
30 <tr>
31 <td *ngIf="selectedAsset.drone.flightStatus.isLanded"><button (click)="takeOffDrone()">TakeOff</button></td>
32 <td *ngIf="!selectedAsset.drone.flightStatus.isLanded"><button (click)="landDrone()">Land</button></td>
33 </tr>
34 <tr>
35 <td> </td>
36 </tr>
37
38 <tr *ngIf="!selectedAsset.drone.flightStatus.isLanded">
39 <td colspan="2" align="center"><button (click)="moveDroneForward()">Move Forward</button></td>
40 </tr>
41 <tr *ngIf="!selectedAsset.drone.flightStatus.isLanded">
42 <td><button (click)="moveDroneLeft()">Move Left</button></td>
43 <td><button (click)="moveDroneRight()">Move Right</button></td>
44 </tr>
45 <tr *ngIf="!selectedAsset.drone.flightStatus.isLanded">
46 <td colspan="2" align="center"><button (click)="moveDroneBack()">Move Back</button></td>
47 </tr>
48 <tr>
49 <td> </td>
50 </tr>
51 </tbody>
52 </table>
53
54 <hr>
55
56</div>
Now you can start to control the drone. Remember to takeoff the drone and then you can move it.