========================= 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: .. highlight:: ts .. code-block:: :linenos: :caption: asset.service.ts :emphasize-lines: 6, 18-42 import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/internal/Observable'; import { PaginatorDto } from 'src/app/lib/models/paginator.dto'; import { Asset } from '../models/asset'; import { Socket, SocketIoConfig } from 'ngx-socket-io'; import { WebStorageService } from '../../lib/web-storage.service'; @Injectable({ providedIn: 'root' }) export class AssetService { backendUrl = 'https://bexstream-preprod.beyond-vision.com/api/v1/asset'; getAllDronesUrl = this.backendUrl + '/all/drone'; config: SocketIoConfig = { url: 'https://bexstream-preprod.beyond-vision.com', options: { query: { source: 'frontend', page: 'monitor', token: this.webStorage.getStoredToken() } } }; private socket: Socket = new Socket(this.config); public initSocket(): Socket { this.config.options.query.token = this.webStorage.getStoredToken(); this.socket.connect(); return this.socket; } public closeSocket() { this.socket?.disconnect(); } public sendSelectedAsset(droneData: any) { this.socket.emit('/frontend/selectedAsset', droneData); } } | | Now that we have setup the socket configuration we need to use these methods on the DroneListComponent. .. code-block:: :linenos: :caption: drone-list.component.ts :emphasize-lines: 1, 11, 23, 26-28 import { Component, OnInit, OnDestroy } from '@angular/core'; import { PaginatorDto } from 'src/app/lib/models/paginator.dto'; import { Asset } from '../../models/asset'; import { AssetService } from '../../services/asset.service'; @Component({ selector: 'app-drone-list', templateUrl: './drone-list.component.html', styleUrls: ['./drone-list.component.less'] }) export class DroneListComponent implements OnInit, OnDestroy { assets: Asset[] = []; paginator: PaginatorDto = new PaginatorDto(); constructor(private assetService: AssetService) { } ngOnInit(): void { this.paginator.limit = 0; this.paginator.filter = ''; this.listDrones(); this.assetService.initSocket(); } ngOnDestroy() { this.assetService.closeSocket(); } ... } | 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. .. code-block:: :linenos: :caption: drone.ts :emphasize-lines: 1,2, 13, 14, 25,26 import { DroneFlightStatus } from "./droneFlightStatus"; import { MissionDrone } from "./missionDrone"; export class Drone { id: string; latitude: number; // last latitude recorded once disconnects longitude: number; // last longitude recorded once disconnects onMission: boolean; hasCamera?: boolean; has360Camera?: boolean; hasSpeaker?: boolean; isDirectConnect?: boolean; // usually via telemetry md: MissionDrone; flightStatus: DroneFlightStatus; constructor() { this.id = ''; this.latitude = 0.0; this.isDirectConnect = false; this.longitude = 0.0; this.onMission = false; this.hasSpeaker = false; this.hasCamera = false; this.has360Camera = false; this.md = new MissionDrone(); this.flightStatus = new DroneFlightStatus(); } } | Add the new files: missionDrone.ts, mavrosState.ts, position.ts: .. code-block:: :linenos: :caption: missionDrone.ts import { MavrosState } from "./mavrosState"; import { Position } from "./position"; export class MissionDrone { name: string; battery: number; satellite = 0; position: Position; gpsFixMode = ''; compassAngle: number; previousD = 0; queue: [number, number] = [0, 0]; deltaT = 0; state: MavrosState | null; constructor(name?: string, state?: MavrosState, position?: Position, battery?: number, compassAngle?: number) { if (name) { this.name = name; } else { this.name = ''; } if (state) { this.state = state; } else { this.state = { connected: false, armed: false, guided: false, manual_input: false, mode: 'STABILIZE', system_status: 0, armedString: 'Not Armed' }; } if (position) { this.position = position; } else { this.position = { latitude: 0.0, longitude: 0.0, altitude: 0.0 }; } this.battery = battery || 0; this.compassAngle = compassAngle || 0; } } .. code-block:: :linenos: :caption: mavrosState.ts export interface MavrosState { connected: boolean; armed: boolean; armedString?: string; guided: boolean; manual_input: boolean; mode: string; system_status: number; } .. code-block:: :linenos: :caption: position.ts export interface Position { latitude: number; longitude: number; altitude: number; } | Add the new file, droneFlightStatus.ts: .. code-block:: :linenos: :caption: droneFlightStatus.ts export class DroneFlightStatus { id?: string; isTakeOff: boolean; isLanded: boolean; isFlying: boolean; guided: boolean; armed: boolean; connected: boolean; lastKnownBattery: number; lastLandDate?: Date; lastTakeOffDate?: Date; constructor() { this.isLanded = true; this.isFlying = false; this.isTakeOff = false; this.armed = false; this.connected = false; this.lastKnownBattery = 0; this.guided = false; } } | And finally add velocity.ts .. code-block:: :linenos: :caption: velocity.ts export interface Velocity { x: number; y: number; z: number; roll: number; pitch: number; yaw: number; } | 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. .. code-block:: :linenos: :caption: asset.service.ts :emphasize-lines: 8, 42-64 import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/internal/Observable'; import { PaginatorDto } from 'src/app/lib/models/paginator.dto'; import { Asset } from '../models/asset'; import { Socket, SocketIoConfig } from 'ngx-socket-io'; import { WebStorageService } from '../../lib/web-storage.service'; import { Position } from '../models/position'; @Injectable({ providedIn: 'root' }) export class AssetService { backendUrl = 'https://bexstream-preprod.beyond-vision.com/api/v1/asset'; getAllDronesUrl = this.backendUrl + '/all/drone'; config: SocketIoConfig = { url: 'https://bexstream-preprod.beyond-vision.com', options: { query: { source: 'frontend', page: 'monitor', token: this.webStorage.getStoredToken() } } }; private socket: Socket = new Socket(this.config); public initSocket(): Socket { this.config.options.query.token = this.webStorage.getStoredToken(); this.socket.connect(); return this.socket; } public closeSocket() { this.socket?.disconnect(); } /** * Subscription methods */ public getAssetPos(assetId: string): Observable { return new Observable(observer => { this.socket.on(assetId + '/position', (data: any) => { observer.next({ latitude: data.x, longitude: data.y, altitude: data.a }); }); }); } /** * Publish methods */ public sendSelectedAsset(droneData: any) { this.socket.emit('/frontend/selectedAsset', droneData); } } 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. .. code-block:: :linenos: :caption: drone-list.component.ts :emphasize-lines: 2, 5-7, 19,21, 46-63 import { Component, OnDestroy, OnInit } from '@angular/core'; import { Subscription } from 'rxjs'; import { PaginatorDto } from 'src/app/lib/models/paginator.dto'; import { Asset } from '../../models/asset'; import { MissionDrone } from '../../models/missionDrone'; import { Position } from '../../models/position'; import { Velocity } from '../../models/velocity'; import { AssetService } from '../../services/asset.service'; @Component({ selector: 'app-drone-list', templateUrl: './drone-list.component.html', styleUrls: ['./drone-list.component.less'] }) export class DroneListComponent implements OnInit, OnDestroy { assets: Asset[] = []; paginator: PaginatorDto = new PaginatorDto(); subscriptions: Subscription = new Subscription(); selectedAsset: Asset = null; constructor(private assetService: AssetService) { } ngOnInit(): void { this.paginator.limit = 0; this.paginator.filter = ''; this.listDrones(); this.assetService.initSocket(); } ngOnDestroy() { this.subscriptions?.unsubscribe(); this.assetService.closeSocket(); } private listDrones() { this.assetService .getAllDrones(this.paginator) .subscribe((assets: Asset[]) => { this.assets = assets; }) } public selectAsset(selectedAsset: Asset) { this.selectedAsset = selectedAsset; this.selectedAsset.drone.md = new MissionDrone(); this.initSubscriptions(); } public initSubscriptions() { this.assetService.sendSelectedAsset(this.selectedAsset.id); this.subscriptions?.add(this.assetService.getAssetPos(this.selectedAsset.id).subscribe((data: Position) => { if (this.selectedAsset.drone) { this.selectedAsset.drone.md.position.latitude = data.latitude; this.selectedAsset.drone.md.position.longitude = data.longitude; this.selectedAsset.drone.md.position.altitude = data.altitude; } })); } } | 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. .. code-block:: html+ng2 :linenos: :caption: drone-list.component.html :emphasize-lines: 12-22

Drones List



{{ asset.name }}

{{ asset.drone.id }}

Latitude: {{ asset.drone.latitude }}, Longitude: {{ asset.drone.longitude }}

On Mission

Not on Mission

Active

Last Connected: {{asset.lastConnected}}

Latitude: {{ asset.drone.md.position.latitude }}
Longitude: {{ asset.drone.md.position.longitude }}
Altitude: {{ asset.drone.md.position.altitude }}


| 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: .. figure:: ../../_static/images/bexstream/tutorial/drone-list-active-drone.webp :align: center Active Drone. | | After selecting the drone, its GPS position will appear and the "Actions" buttons is shown. .. figure:: ../../_static/images/bexstream/tutorial/drone-list-selected-drone.webp :align: center 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 ---------------------------------------------- .. code-block:: :linenos: :caption: asset.service.ts :emphasize-lines: 18, 42-44 import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/internal/Observable'; import { PaginatorDto } from 'src/app/lib/models/paginator.dto'; import { Asset } from '../models/asset'; import { Socket, SocketIoConfig } from 'ngx-socket-io'; import { WebStorageService } from '../../lib/web-storage.service'; import { Position } from '../models/position'; @Injectable({ providedIn: 'root' }) export class AssetService { backendUrl = 'https://bexstream-preprod.beyond-vision.com/api/v1/asset'; getAllDronesUrl = this.backendUrl + '/all/drone'; getConfigUrl = this.backendUrl + '/config'; config: SocketIoConfig = { url: 'https://bexstream-preprod.beyond-vision.com', options: { query: { source: 'frontend', page: 'monitor', token: this.webStorage.getStoredToken() } } }; private socket: Socket = new Socket(this.config); public initSocket(): Socket { this.config.options.query.token = this.webStorage.getStoredToken(); this.socket.connect(); return this.socket; } public closeSocket() { this.socket?.disconnect(); } public downloadConfig(id: string): Observable { return this.http.get(`${this.getConfigUrl}/${id}`); } /** * Subscription methods */ public getAssetPos(assetId: string): Observable { return new Observable(observer => { this.socket.on(assetId + '/position', (data: any) => { observer.next({ latitude: data.x, longitude: data.y, altitude: data.a }); }); }); } /** * Publish methods */ public sendSelectedAsset(droneData: any) { this.socket.emit('/frontend/selectedAsset', droneData); } } 2 - Download File logic ----------------------- .. code-block:: :linenos: :caption: drone-list.component.ts :emphasize-lines: 65-80 import { Component, OnDestroy, OnInit } from '@angular/core'; import { Subscription } from 'rxjs'; import { PaginatorDto } from 'src/app/lib/models/paginator.dto'; import { Asset } from '../../models/asset'; import { MissionDrone } from '../../models/missionDrone'; import { Position } from '../../models/position'; import { Velocity } from '../../models/velocity'; import { AssetService } from '../../services/asset.service'; @Component({ selector: 'app-drone-list', templateUrl: './drone-list.component.html', styleUrls: ['./drone-list.component.less'] }) export class DroneListComponent implements OnInit, OnDestroy { assets: Asset[] = []; paginator: PaginatorDto = new PaginatorDto(); subscriptions: Subscription = new Subscription(); selectedAsset: Asset = null; constructor(private assetService: AssetService) { } ngOnInit(): void { this.paginator.limit = 0; this.paginator.filter = ''; this.listDrones(); this.assetService.initSocket(); } ngOnDestroy() { this.subscriptions?.unsubscribe(); this.assetService.closeSocket(); } private listDrones() { this.assetService .getAllDrones(this.paginator) .subscribe((assets: Asset[]) => { this.assets = assets; }) } public selectAsset(selectedAsset: Asset) { this.selectedAsset = selectedAsset; this.selectedAsset.drone.md = new MissionDrone(); this.initSubscriptions(); } public initSubscriptions() { this.assetService.sendSelectedAsset(this.selectedAsset.id); this.subscriptions?.add(this.assetService.getAssetPos(this.selectedAsset.id).subscribe((data: Position) => { if (this.selectedAsset.drone) { this.selectedAsset.drone.md.position.latitude = data.latitude; this.selectedAsset.drone.md.position.longitude = data.longitude; this.selectedAsset.drone.md.position.altitude = data.altitude; } })); } downloadFile(data: string) { const blob = new Blob([data], {type: 'text/csv'}); const url = window.URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.download = 'config.' + environment.platformExtension; anchor.href = url; anchor.click(); } downloadConfig(droneId: string) { this.assetService .downloadConfig(droneId) .subscribe(data => { this.downloadFile(data.cypher); }); } } | 3 - Adding Download Button on the UI -------------------------------- .. code-block:: html+ng2 :linenos: :caption: drone-list.component.html :emphasize-lines: 11-15

Drones List



{{ asset.name }}

{{ asset.drone.id }}

Latitude: {{ asset.drone.latitude }}, Longitude: {{ asset.drone.longitude }}

On Mission

Not on Mission

Active

Last Connected: {{asset.lastConnected}}

Latitude: {{ asset.drone.md.position.latitude }}
Longitude: {{ asset.drone.md.position.longitude }}
Altitude: {{ asset.drone.md.position.altitude }}


| Now you must see the Download button like this: .. figure:: ../../_static/images/bexstream/tutorial/drone-list-download-config.webp :align: center 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 ` 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. .. figure:: ../../_static/images/bexstream/tutorial/drone-list-downloaded-config.webp :align: center 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. | .. code-block:: sh :linenos: :caption: rosCatkinEntrypoint.sh #!/bin/bash # Source ROS distro environment and local catwin workspace source "$HOME_WS/.profile" && source "/opt/ros/$ROS_DISTRO/setup.bash" && sourc$ command="$@ argEndpoint:=prod argCamType:=imx477" bash -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. .. code-block:: :linenos: :caption: asset.service.ts :emphasize-lines: 65-77 import { HttpClient, HttpParams } from '@angular/common/http'; import { Injectable } from '@angular/core'; import { Observable } from 'rxjs/internal/Observable'; import { PaginatorDto } from 'src/app/lib/models/paginator.dto'; import { Asset } from '../models/asset'; import { Socket, SocketIoConfig } from 'ngx-socket-io'; import { WebStorageService } from '../../lib/web-storage.service'; @Injectable({ providedIn: 'root' }) export class AssetService { backendUrl = 'https://bexstream-preprod.beyond-vision.com/api/v1/asset'; getAllDronesUrl = this.backendUrl + '/all/drone'; config: SocketIoConfig = { url: 'https://bexstream-preprod.beyond-vision.com', options: { query: { source: 'frontend', page: 'monitor', token: this.webStorage.getStoredToken() } } }; private socket: Socket = new Socket(this.config); public initSocket(): Socket { this.config.options.query.token = this.webStorage.getStoredToken(); this.socket.connect(); return this.socket; } public closeSocket() { this.socket?.disconnect(); } /** * Subscription methods */ public getAssetPos(assetId: string): Observable { return new Observable(observer => { this.socket.on(assetId + '/position', (data: any) => { observer.next({ latitude: data.x, longitude: data.y, altitude: data.a }); }); }); } /** * Publish methods */ public sendSelectedAsset(droneData: any) { this.socket.emit('/frontend/selectedAsset', droneData); } // Sends Take off public sendDroneTakeoff(droneData: any) { this.socket?.emit('/frontend/takeoff', droneData); } // Sends Land public sendDroneLand(droneData: any) { this.socket?.emit('/frontend/land', droneData); } // Sends Velocity Command (used to move drone) public sendDroneVel(droneData: any) { this.socket?.emit('/frontend/cmd', droneData); } } | | 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: .. code-block:: :linenos: :caption: drone-list.component.ts :emphasize-lines: 69-75 import { Component, OnDestroy, OnInit } from '@angular/core'; import { Subscription } from 'rxjs'; import { PaginatorDto } from 'src/app/lib/models/paginator.dto'; import { Asset } from '../../models/asset'; import { MavrosState } from '../../models/mavrosState'; import { MissionDrone } from '../../models/missionDrone'; import { Position } from '../../models/position'; import { Velocity } from '../../models/velocity'; import { AssetService } from '../../services/asset.service'; @Component({ selector: 'app-drone-list', templateUrl: './drone-list.component.html', styleUrls: ['./drone-list.component.less'] }) export class DroneListComponent implements OnInit, OnDestroy { assets: Asset[] = []; paginator: PaginatorDto = new PaginatorDto(); subscriptions: Subscription = new Subscription(); selectedAsset: Asset = null; constructor(private assetService: AssetService) { } ngOnInit(): void { this.paginator.limit = 0; this.paginator.filter = ''; this.listDrones(); this.assetService.initSocket(); } ngOnDestroy() { this.subscriptions?.unsubscribe(); this.assetService.closeSocket(); } private listDrones() { this.assetService .getAllDrones(this.paginator) .subscribe((assets: Asset[]) => { this.assets = assets; }) } public selectAsset(selectedAsset: Asset) { this.selectedAsset = selectedAsset; this.selectedAsset.drone.md = new MissionDrone(); this.initSubscriptions(); } public initSubscriptions() { this.assetService.sendSelectedAsset(this.selectedAsset.id); this.subscriptions?.add(this.assetService.getAssetPos(this.selectedAsset.id).subscribe((data: Position) => { if (this.selectedAsset.drone) { this.selectedAsset.drone.md.position.latitude = data.latitude; this.selectedAsset.drone.md.position.longitude = data.longitude; this.selectedAsset.drone.md.position.altitude = data.altitude; } })); this.subscriptions?.add(this.assetService.getDroneState(this.selectedAsset.id).subscribe((data: MavrosState) => { if (this.selectedAsset.drone) { this.selectedAsset.drone.md.state = data; if (data.system_status > 3) { // flying this.selectedAsset.drone.flightStatus.isFlying = true; this.selectedAsset.drone.flightStatus.isLanded = false; } else if (data.system_status === 3) { // landed and ready to takeoff this.selectedAsset.drone.flightStatus.isFlying = false; this.selectedAsset.drone.flightStatus.isLanded = true; } } } } downloadFile(data: string) { const blob = new Blob([data], {type: 'text/csv'}); const url = window.URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.download = 'config.' + environment.platformExtension; anchor.href = url; anchor.click(); } downloadConfig(droneId: string) { this.assetService .downloadConfig(droneId) .subscribe(data => { this.downloadFile(data.cypher); }); } } | After these flags have been added let us add the methods to control the drone. .. code-block:: :linenos: :caption: drone-list.component.ts :emphasize-lines: 98-146 import { Component, OnDestroy, OnInit } from '@angular/core'; import { Subscription } from 'rxjs'; import { PaginatorDto } from 'src/app/lib/models/paginator.dto'; import { Asset } from '../../models/asset'; import { MavrosState } from '../../models/mavrosState'; import { MissionDrone } from '../../models/missionDrone'; import { Position } from '../../models/position'; import { Velocity } from '../../models/velocity'; import { AssetService } from '../../services/asset.service'; @Component({ selector: 'app-drone-list', templateUrl: './drone-list.component.html', styleUrls: ['./drone-list.component.less'] }) export class DroneListComponent implements OnInit, OnDestroy { assets: Asset[] = []; paginator: PaginatorDto = new PaginatorDto(); subscriptions: Subscription = new Subscription(); selectedAsset: Asset = null; constructor(private assetService: AssetService) { } ngOnInit(): void { this.paginator.limit = 0; this.paginator.filter = ''; this.listDrones(); this.assetService.initSocket(); } ngOnDestroy() { this.subscriptions?.unsubscribe(); this.assetService.closeSocket(); } private listDrones() { this.assetService .getAllDrones(this.paginator) .subscribe((assets: Asset[]) => { this.assets = assets; }) } public selectAsset(selectedAsset: Asset) { this.selectedAsset = selectedAsset; this.selectedAsset.drone.md = new MissionDrone(); this.initSubscriptions(); } public initSubscriptions() { this.assetService.sendSelectedAsset(this.selectedAsset.id); this.subscriptions?.add(this.assetService.getAssetPos(this.selectedAsset.id).subscribe((data: Position) => { if (this.selectedAsset.drone) { this.selectedAsset.drone.md.position.latitude = data.latitude; this.selectedAsset.drone.md.position.longitude = data.longitude; this.selectedAsset.drone.md.position.altitude = data.altitude; } })); this.subscriptions?.add(this.assetService.getDroneState(this.selectedAsset.id).subscribe((data: MavrosState) => { if (this.selectedAsset.drone) { this.selectedAsset.drone.md.state = data; if (data.system_status > 3) { // flying this.selectedAsset.drone.flightStatus.isFlying = true; this.selectedAsset.drone.flightStatus.isLanded = false; } else if (data.system_status === 3) { // landed and ready to takeoff this.selectedAsset.drone.flightStatus.isFlying = false; this.selectedAsset.drone.flightStatus.isLanded = true; } } } } downloadFile(data: string) { const blob = new Blob([data], {type: 'text/csv'}); const url = window.URL.createObjectURL(blob); const anchor = document.createElement('a'); anchor.download = 'config.' + environment.platformExtension; anchor.href = url; anchor.click(); } downloadConfig(droneId: string) { this.assetService .downloadConfig(droneId) .subscribe(data => { this.downloadFile(data.cypher); }); } /** * Commands to control the drone. */ public takeOffDrone() { const droneData = { msg: '', assetName: this.selectedAsset.name, assetId: this.selectedAsset.id }; this.assetService.sendDroneTakeoff(droneData); } public landDrone() { const droneData = { msg: '', assetName: this.selectedAsset.name, assetId: this.selectedAsset.id }; this.assetService.sendDroneLand(droneData); } public moveDroneLeft() { const vel: Velocity = { x: 0.0, y: 3.0, z: 0.0, roll: 0.0, pitch: 0.0, yaw: 0.0 }; const droneDataVel = { msg: vel, assetName: this.selectedAsset.name, assetId: this.selectedAsset.id }; vel.y = this.DRONE_FE_MAX_SPEED; this.assetService.sendDroneVel(droneDataVel); } public moveDroneRight() { const vel: Velocity = { x: 0.0, y: 0.0, z: 0.0, roll: 0.0, pitch: 0.0, yaw: 0.0 }; const droneDataVel = { msg: vel, assetName: this.selectedAsset.name, assetId: this.selectedAsset.id }; vel.y = - this.DRONE_FE_MAX_SPEED; this.assetService.sendDroneVel(droneDataVel); } public moveDroneForward() { const vel: Velocity = { x: 0.0, y: 3.0, z: 0.0, roll: 0.0, pitch: 0.0, yaw: 0.0 }; const droneDataVel = { msg: vel, assetName: this.selectedAsset.name, assetId: this.selectedAsset.id }; vel.x = this.DRONE_FE_MAX_SPEED; this.assetService.sendDroneVel(droneDataVel); } public moveDroneBack() { const vel: Velocity = { x: 0.0, y: 0.0, z: 0.0, roll: 0.0, pitch: 0.0, yaw: 0.0 }; const droneDataVel = { msg: vel, assetName: this.selectedAsset.name, assetId: this.selectedAsset.id }; vel.x = - this.DRONE_FE_MAX_SPEED; this.assetService.sendDroneVel(droneDataVel); } } | 3 - Add these functionalities onto the UI: ------------------------------------------------------ .. code-block:: html+ng2 :linenos: :caption: drone-list.component.html :emphasize-lines: 28-43

Drones List



{{ asset.name }}

{{ asset.drone.id }}

Latitude: {{ asset.drone.latitude }}, Longitude: {{ asset.drone.longitude }}

On Mission

Not on Mission

Active

Last Connected: {{asset.lastConnected}}

Latitude: {{ asset.drone.md.position.latitude }}
Longitude: {{ asset.drone.md.position.longitude }}
Altitude: {{ asset.drone.md.position.altitude }}

 
 

| Now you can start to control the drone. Remember to takeoff the drone and then you can move it. .. figure:: ../../_static/images/bexstream/tutorial/drone-list-landed.webp :align: center Landed Drone. .. figure:: ../../_static/images/bexstream/tutorial/drone-list-flying.webp :align: center Flying drone.