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:

Listing 35 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.

Listing 36 drone-list.component.ts
 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.

Listing 37 drone.ts
 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:

Listing 38 missionDrone.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}
Listing 39 mavrosState.ts
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}
Listing 40 position.ts
1export interface Position {
2    latitude: number;
3    longitude: number;
4    altitude: number;
5}

Add the new file, droneFlightStatus.ts:

Listing 41 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

Listing 42 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.

Listing 43 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';
 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.

Listing 44 drone-list.component.ts
 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.

Listing 45 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:

../../_images/drone-list-active-drone.webp

Fig. 81 Active Drone.



After selecting the drone, its GPS position will appear and the “Actions” buttons is shown.

../../_images/drone-list-selected-drone.webp

Fig. 82 Selected Drone.


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

Listing 46 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';
 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

Listing 47 drone-list.component.ts
 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}

3 - Adding Download Button on the UI

Listing 48 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    <p>
12        <button (click)="downloadConfig(asset.id)">
13            Download Config File
14        </button>
15    </p>
16
17    <p *ngIf="asset.isActive && (!selectedAsset || asset.id !== selectedAsset.id)">
18        <button (click)="selectAsset(asset)">Select Drone</button>
19    </p>
20
21    <p *ngIf="asset.isActive && selectedAsset && asset.id === selectedAsset.id">
22        <span><b>Latitude:</b> {{ asset.drone.md.position.latitude }}</span> <br>
23        <span><b>Longitude:</b> {{ asset.drone.md.position.longitude }}</span> <br>
24        <span><b>Altitude:</b> {{ asset.drone.md.position.altitude }}</span> <br>
25    </p>
26
27    <hr>
28</div>

Now you must see the Download button like this:

../../_images/drone-list-download-config.webp

Fig. 83 Selected Drone.


!

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.

../../_images/drone-list-downloaded-config.webp

Fig. 84 Downloaded HEIFU Tutorial SK config file.


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.


Listing 49 rosCatkinEntrypoint.sh
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.

Listing 50 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
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:

Listing 51 drone-list.component.ts
 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.

Listing 52 drone-list.component.ts
  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:

Listing 53 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>
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>&nbsp;</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>&nbsp;</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.

../../_images/drone-list-landed.webp

Fig. 85 Landed Drone.

../../_images/drone-list-flying.webp

Fig. 86 Flying drone.