This is part 2 of a three-part blog series explaining how I wrote some code to control the basic features of a DJI Ryze Tello drone. I set myself this challenge ahead of a hackathon event held at our offices in Swansea, UK. In part 1 I explained how to connect to the drone and send commands to it to enable the drone to do tasks such as take off and land. You can read part 1 here. Below I take things one step forward allowing you to fly the drone in various directions.
Getting the drone to move in a specified direction is a very similar process to what we have already done but with one slight difference. The directional commands “left”, “right”, “forward” and “back” for the drone each allow an integer to be specified after the command, the integer is how many centimetres the drone will move in the specified direction.
First, we need to refactor our handleInput function a little, as we will now be sending a value from 20 to 200 after some of our commands, writing a select to handle each and every possible combination we could send is a bad idea. Instead, we will use the string.startsWith method to check that our line starts with a keyword such as “forward” or “left” and then take the amount from the end of the line using string.split.
Unfortunately, we can’t just add a boolean expression case to our switch due to the way switch works. In short, this is because it uses the strict equality operator (===) to check the value of the argument against whatever is on the right-hand side of the case keyword, this means no functions or boolean expressions will be evaluated.
Our way around this slight blocker is to go back to a good old if statement. First, let’s cater for the functionality that we already have. What I like to do here is to create simple functions that encapsulate the boolean logic that we can re-use, so I created functions like below:
The functions take in the line, and will simply return either true or false depending on how the boolean expression is evaluated.
We can then create a series of if statements using these functions and use them to execute our sendTakeoff and sendLand functions like below:
Once that refactoring is done the handleInput function should look something like this
I’ve declared the isTakeoff and isLand functions within the scope of handleInput just for the sake of keeping everything together.
Now we can create a function to detect when we are sending a forward command to the drone. We can create our sendForward function, this will be very similar to our sendTakeoff and sendLand functions that we created earlier, but this time we will add a distance parameter with a default value of 20. This is so that if a user neglects to send the command with a distance value, we can safely default it to the lowest value possible and still send a valid command to the drone.
Finally, we can add our if statement:
Although it doesn’t look like much is going on here in terms of lines of code, there are two very important things happening. First, we are calling strings split method on our line, this function will split a string up into sections wherever a specified token is present. As we are splitting by a single white-space, and our line should be in the format like below:
The word “forward”, followed by a single white-space and then an integer. For example “forward 20”.
This split function will return us an array with two elements inside like this: [“forward”,”20”].
As you can see from the example, the first element, in the 0 position of the array, will be “forward” and the second element, in the 1st position will be “20”. We are then using array de-structuring here to assign the first element in the array to the variable name and the second element in the array to the variable dist. The rest is simple, we then call our sendForwardfunction with the socket and dist as arguments. The command should then be sent to the drone.
Let’s give it a shot, fire up the application using:
> node ./src/app.js
Once we are up and running, issue the “takeoff” command to the drone. Once the take off sequence is complete we can then instruct the drone to move forward by issuing a command like below:
> forward 20
We should then see the drone move in a forward direction.
To add the other directions we can just repeat the above steps, but instead of sending “forward” we just need to send either “back”, “left” or “right”.
The output you should see in the terminal:
A nice way to expand this would be to create some form of UI that’s not the command line because let’s be fair, typing commands is hardly practical. Perhaps taking the basic concepts I’ve touched on here and wrapping them in some form of Electron or React/Vue/Angular app. Even getting the stream of data that’s available from the drone and creating some sort of visualizations from it would be an interesting coding challenge. I will explore some of these in part 3.
All of the code I’ve written here (and some extras) can be found in my GitHub https://github.com/csscottc/drone-ctrl – Feel free to check it out and use it however you see fit. Part 1 of this blog series can be found here.
In part 3 I will explain how to build a simple UI that will complete the original task of allowing a non-coder the ability to control the drone.
At Gofore we pride ourselves for having the best of the best when it comes to talent. Finding and securing the best people is not easy and we have to find creative and interesting ways to attract them. In an effort to attract a new crop of talented developers to our office in South Wales, UK, we recently invited 15 students from around the area to join us in the Swansea office for a hackathon event. The idea was to identify students with the potential to grow into tomorrows experts.
We devised a task to create an application that could control a DJI Ryze Tello drone with nothing but code written during the hackathon. The students could use any language or tools they wanted and the only firm requirement was that there was some element of user interaction involved, in other words, the ability for a non-coder to control the drone. The students then faced a series of races/challenges to test out the code they had written.
So with that in mind, I thought it was only fair to attempt some of the challenges myself. So in this blog, I’ll show you my take on getting a drone to fly with Node.js
In theory, you can use any programming language for this, the one requirement is that the language somehow allows you to open up a datagram socket as the way we will communicate with the drone is via UDP.
We can connect to the drone directly via WiFi as the Tello drone has its own hot-spot built in. Once we are connected we can start to send commands to the drone. The drone has an SDK that we can use which allows us to send plaintext commands to it.
More information is available in the official SDK documentation
I won’t go into too much detail about how I set up my project as there’s nothing that special going on. This is a simple “vanilla” Node.js project with no dependencies other than some of the core modules that come with Node. That being said, here are the steps you will need to follow to get started on your own:
Create a new folder on your system for the project, I’ve chosen to name the project tello-ctrl but feel free to use anything you would like.
- (optional) Initalize a new git repository (git init) and link to a repository on GitHub/BitBucket/GitLab
- Inside the project folder run npm init -y this command will run the usual npm init command and will accept the default values that npm init provides.
- Create a new source folder ‘src’ in the tello-ctrl folder, this is where all of our code will go. In order to do this, I’m going to make use of the ‘readline’ package that’s available as part of node. First we need to import the package (as I’m not using Babel or any other pre-processor here I’ll have to use the older require style syntax to do this) then once we have this imported, we can use the createInterface function which will take two streams as arguments; one stream to read from and one stream to write to. We can pass in the process.stdin and process.stdout streams for read and write respectively.
All of the code in this project is contained in my GitHub account https://github.com/csscottc/drone-ctrl.
Once all that is done, you should have a directory structure that looks something like this (when viewed in vs code)
Preparing for take off
Code for this section can be found in the basicio branch.
Once the project setup is done its time to actually write some code. The first thing we need our app to do is to accept some basic inputs from the terminal, down the line this will allow us to send commands to the drone when we detect certain strings as input. For example, if the user enters “takeoff” we can, in turn, send a take off command to the drone.
Once that’s done we can add an event listener to rl for the “line” event that is fired. This event will be fired whenever a line is detected, which in our use case will be whenever the user hits the enter/return key after typing in a command. The event listener will take a function that it will fire whenever the line event is detected, it will pass the line that has been entered as the first argument to our function. For now, we will just pass the line that’s received straight over to console.log, This means that whenever a line is detected from the user, the line will be logged to the console.
Once that’s done we can run our app for the first time. To do this head back to the terminal and then run:
> node src/app.js
You should notice that the lines “Lets get started!” And “Please enter a command:” are printed to the terminal. If you enter some text and then press enter/return you should see whatever text you entered repeated.
Now that we have some rudimentary IO set up we can start to look out for when the user enters specific commands, such as “takeoff”, “land”, “forward”, “back”, “left”, “right”. To do this we will add a new function “handleInput” which will take in the line passed to it by the event listener and will perform a simple switch statement on the content of the line. By doing this, depending on what the line received from the user contains we can then execute specific functionality in our app.
Once we’ve created our handleInput function, we can then pass the line we receive from our event listener to it like below:
If we run the application this time then only when the line is equal to either “takeoff” or “land” should we see something printed to the terminal.
Take Off & Landing
The code for this section is available in the basic-movement branch.
It’s now time to finally connect to our drone and send some of the most important commands to it. Take off and Land.
In order to send commands to the drone, as mentioned previously we will use the UDP protocol. In order to create a UDP socket in Node.js we need to make use of the “dgram” module, similarly to how we made use of the “readline” module earlier for our basic IO. The createSocket function that’s available as part of the “dgram” module can be used to create a socket which can be bound to a port of our choosing. Once we have a socket bound to a port, it can be configured to listen to incoming messages as well as to send outgoing messages for us.
When you connect to the Tello drone over Wi-Fi, it will be listening for command type messages on port 8889. It will assign its self the IP address 192.168.10.1 on the network it hosts – we will need this information when creating our socket.
To keep things tidy we will create a function called “getSocket”, the function will create a socket using the “createSocket” function that’s imported from the “dgram” package and will then bind this socket to the Tello command port, which is 8889. It will then return to us this socket that is ready to be used for communication with the drone.
Another thing we will do at this point is to wrap up some of our existing code in an Immediately Invoked Function Expression (IIFE). This will allow us to declare the function as async which will allow us to make use of Async/Await, enhancing the readability and maintainability of the code later on. To do this we will take everything other than the handleInput function and the require statements for “dgram” and “readline”, wrapping them in an IIFE as shown below.
All we really need to know about IIFE’s for the purposes of this app is that the function will essentially be called as soon as it has been created. Our next step is to add the call to our new getSocket function within the IIFE that we just created.
The socket that we created emits some events that will be very useful for us when debugging and running our app, in order to make use of these events we need to register some event handlers.
The events that can be emitted from the socket that we care about are:
“message” – This event is fired when a message is received by the socket. An event handler can be provided that will be called with the message that has been received as its first argument, the message can then be used as desired by the developer, in our case we will just be logging messages to the terminal. The event handler is also called with another argument called rinfo, which contains information about where the message was received from.
“error” – This event is fired when an error occurs with the socket connection. An event handler can be provided that will be called with the error that occurred as its first argument, the error can then be inspected and used for logging and error handing purposes.
“listening” – This event is fired when the Socket has been created and is listening (ready to accept) incoming messages. An event handler can be provided, when it is called it is not passed any arguments.
The handlers that we are going to add to the above events are
In order to send a message, we will use the send() method thats available on our socket. The send method accepts 6 arguments:
“msg” – The message to send.
“offset” – The offset in the buffer where the message starts.
“length” – The number of bytes in the message.
“port” – The destination port, this is the port the message will be sent to.
“address” – The destination host name or ip address, this is where the message will be sent on the network.
“callback” – A callback function that is executed on completion of sending the message, the first argument can be an error, if the error is truthy this indicates that an error occurred and should be dealt with appropriately.
Some more info on this method is available here.
Get the drone into SDK mode
Before we can start sending meaningful commands to our drone, such as “takeoff and “land”, we need to get the drone into SDK mode. Once the drone is in SDK mode we can start to issue it with other commands, and it will (hopefully) respond to them. We can do this in the same way as we would send any other command to the drone, by using our socket’s send() method.
To do this, and to keep things nice and tidy we can create a new function called “sendInitCommand”. The function will take the socket created earlier as an argument and will then make use of the socket.send method to send the command over to the drone. The command that we will be sending is the string “command”.
As the socket.send method is async (It takes in a callback function that is executed on completion of the send operation) we will make our sendInitCommand function return a new Promise. Using promises will allow us to use the async/await syntax that we mentioned earlier, and will make our code easier to read and maintain.
The callback function that we will provide to socket.send will take one parameter, and will follow the standard “error-first” convention with node, this means that any argument passed in as the first argument will be an error object. We can perform a quick check on the value of the argument and if the value is ‘truthy’ this means an error occurred while sending the message, conversely if the value is ‘falsy’ then this means there was no error and everything went as expected (The command was sent to the drone successfully).
In the event that the error is ‘falsy’ (No error occurred), we can safely resolve our promise using the resolve function.
As we are using async/await, if the error is ‘truthy’ (Something went wrong) we will just throw the error and handle it later on rather than rejecting our promise as we normally would.
The next step is to actually call the function that we just created. A good place to add this call is under where we added the “listening” event handler.
After all that, our IIFE should now look something like this:
The next steps are to get our drone to take off and land, we can finally start adding the code for sending our takeoff and landing commands to the drone. To do this we can create two new functions, sendTakeOff and sendLand, the implementation of these functions is almost exactly the same as the sendInitCommand that we just created, the only differences being the function names, and the string that they are sending to the drone in the socket.send method. The commands required for takeoff and landing are “takeoff” and “land”.
Once we have these functions, things can start to come together. It’s time to wire them up to the switch statement within the handleInput function that we created earlier, essentially what we will do here is to call either the sendTakeOff function or the sendLand function depending on the value of what the user has submitted.
Now we are finally in a position where we can connect to our drone and get it to fly!
The first step is to turn on the drone and to connect to it via WiFi. The drone will normally use an SSID in the following format TELLO-XXXXX – Where XXXXX is a random set of numbers and characters. My drone uses the SSID “TELLO-D3F981”
Once the drone is connected, as we did previously, we can start our app by running:
> node /src/app.js
If everything Is working as expected we should see the following output in the terminal.
Notice that we are now seeing “Socket is listening” and “Message from drone: ok” in our output. These are messages that have come from the event handlers we added to the “listening” and “message” events earlier on.
When we send commands to our drone, it will sometimes acknowledge that a command has been executed successfully by responding with either the string “ok” or, if the command was not executed successfully a string that represents an error (the value of which will differ depending on the nature of the error).
For other commands the drone will respond with a value, for example, if we send the “battery?” command, the drone will respond with a number between 0 and 100 which is representative of the current battery percentage of the drone.
To get the drone to take off, type “takeoff” and then hit enter. If all has gone as expected the drone should take off and you should see “Message from drone: ok”.
Note: The drone will land automatically if it detects no commands within a 15 second time window.
Once the drone is in the air lets bring it back down to earth. Type “land” and then hit enter, this should make the drone auto-land.
Note: Sometimes after executing a command the drone needs a small amount of time to be ready for the next command. I’m not exactly sure what causes this but normally waiting for a second or so before sending the next command to the drone works.
So now we have mastered sending commands to our drone and taking off and landing, in the next part of this blog I will show you how to send directional commands. This will allow you to fly your drone forward, back, up, down and on trajectories. Happy hacking!
You can read Part 2 here: https://gofore.com/en/fly-a-drone-with-node-js-part-2/
Repository name: new or fork?
If you’re releasing your own package (to e.g. npm or mvn) from the forked repository with your additions then it’s logical to also rename the repository to that package name.
If it’s an npm package and you’re using scoped packages then you could also keep the original repository name.
Keeping master and continuing developing on a branch?
Using master is the sane thing to do. You can always sync your fork with an upstream repository. See: syncing a fork.
Generally, you want to keep your local master branch as a close mirror of the upstream master and execute any work in feature branches (that might become pull requests later).
How you should do versioning?
Suppose that the original repository (origin) is still in active development and does new releases. How should you do versioning in your forked repository as you probably want to bring the changes done in the origin to your fork? And still maintain semantic versioning.
In short, semver doesn’t support prepending or appending strings to version. So adding your tag to the version number from the origin which your version is following breaks the versioning. So, you can’t use something like “firstname.lastname@example.org” or “1.0.0-your-org.1”. This has been discussed i.a. semver #287. The suggestion was to use a build meta tag to encode the other version as shown in semver spec item-10. But the downside is that “Build metadata SHOULD be ignored when determining version precedence. Thus two versions that differ only in the build metadata, have the same precedence.”
If you want to keep relation the original package version and follow semver then your options are short. The only option is to use build meta tag: e.g. “1.0.0+your-org.1”.
It seems that when following semantic versioning your only option is to differ from origin version and continue as you go.
If you don’t need to or want to follow semver you can track upstream version and mark your changes using similar markings as semverpre-releases: e.g. “1.0.0-your-org.1”.
npm package: scoped or unscoped?
Using scoped packages is a good way to signal official packages for organizations. Example of using scoped packages can be seen from Storybook.
It’s more of a preference and naming convention of your packages. If you’re using something like your-org-awesome-times-ahead-package and your-org-patch-the-world-package then using scoped packages seems redundant.
Who should be the author?
At least add yourself to contributors in package.json.
Forking only for patching an npm library?
Don’t fork, use patch-package which lets app authors instantly make and keep fixes to npm dependencies. Patches created by
patch-package are automatically and gracefully applied when you use
yarn. Now you don’t need to wait around for pull requests to be merged and published. No more forking repos just to fix that one tiny thing preventing your app from working.
If you have any other questions, then post them in the comments below.
The fourth incarnation of Disobey, the Nordic security event was held in Helsinki in January. This event is a place for people interested in hacker culture, information security, making and breaking, and to meet like-minded people, learn new things and share knowledge. This was my second time attending. The first one was mostly spent getting to know my way around such an event, the second one was much easier when I knew what to expect. Too bad there’s almost too much to do, so you’ll have to prioritise… So here are my personal experiences from this year’s event and hopefully some tips for the first-timer in 2020.
Photographing or recording video at the event is forbidden (see https://en.wikipedia.org/wiki/Chatham_House_Rule), so no pictures or names, sorry. The speaker list is public though, go check it out, if you want.
There were plenty of great talks, from disclosing 35-year-old vulnerabilities (https://sintonen.fi/advisories/scp-client-multiple-vulnerabilities.txt), Tor anonymity, timing side-channel attacks, hotel room lock security, data breach dump related thingies and browser 0-days to mechanical master-key systems. Lots of cool stuff, but you can’t just sit on your backside for the whole two days! I had picked a few talks I really wanted to see (I really dig mechanical locks, client-side vulns, and breaking things) and tried to remember to attend. And I did. Most of the others fell in the category “oh that’d be cool oh crap it’s already starting and all the seats are taken”. But I’ll surely watch the recordings of most of them later. It’s not like re:Invent-crowded, but if you want to have a good seat where you can concentrate on the content, be early
At the time of writing, the talks are being uploaded to Disobey’s Youtube channel.
Also a new podcast, “We need to talk about InfoSec“, recorded its first episode at Disobey, go check it out.
Workshops and things to do
In addition to talking heads, you get to do things yourself with a more experienced instructor. This year workshops included e.g. hacking Chinese web browsers, using Python for bad (and good), threat modelling and a few other topics. Pick something that interests you, check if there’s a pre-registration needed (this year there wasn’t) and enjoy the ride. I attended only the most interesting one (to me), but would’ve enjoyed many of them, I’m sure.
In addition to shorter (up to three hours) workshops, there was a lockpicking village where you could try picking different types of mechanical locks and learn from a pro.
Capture the flag, or CTF for short is a competition where teams try to solve different types of hacking puzzles for points. The puzzles range from web vulnerabilities (SQL injections, path traversal/local file inclusion, security misconfigurations etc.) to listening to radio broadcasts from a dummy satellite. The proof of success is a flag, which is entered to the competition system. Who gets the most points, wins. And this is fun, for some people at least, myself included. We had a team of seven people which included four Goforeans. Fourth place doesn’t suck that bad when there were two teams of infosec company employees ahead of us. Next year we’ll try harder!
As the CTF network is considered “hostile” and doesn’t offer internet access, I recommend bringing a burner laptop (which you can wipe clean afterwards) or e.g. using 1) a virtual machine (Kali works fine) for the hacking 2) USB tethering (because airwaves are a bit crowded and your keyphrase strength might be tested…) to deliver internet to your host OS 3) a USB ethernet adapter which you present to your virtual machine (and the VM only) so that you can easily search the internet for things from the host and access the CTF network from the VM without extra hassle of switching cables back and forth.
Gofore had some web related challenges (created by me) of their own in the contest. Of course, my team members had to solve those without me. More info about these below, there’s even a virtual machine image you can spin up and try to get the flags yourself.
To everyone I met: I’m glad we met. Let’s do it again sometime. To everyone else, I hope we’ll meet someday. A few of my friends/acquaintances told me afterwards that they kinda forgot most of the program while just mingling so be careful 🙂
Gofore CTF challenges
Download link in the setup instructions. See if you can hack your way in. Encrypted (sic) walkthrough can be found at http://<machine IP>/walkthrough.txt if you’re interested in cheating…
- The flag is somewhere on the filesystem. Find the file’s location on the site and the contents of the flag. URL path: /blog
- Some page is behind closed doors. Find the keys and step in. URL path: /*
- Our customer database might contain more than meets the eye. URL path: /customerdb
- Download the OVA image here. (Avaa: open, Lataa: download)
- Import it in e.g. VirtualBox, connect it to a host-only network
- Find the virtual machine’s IP with for example nmap -sn <your host-only network>
- Point your browser (and other tools) to http://<VM_IP>:80/
- Have fun!