├── telloNoFly.txt ├── telloJustQuery.txt ├── telloVertical.txt ├── telloBounce.txt ├── telloBox.txt ├── LICENSE ├── TelloCommandLine.js ├── README.md ├── TelloMissionFile.js └── TelloConsole.js /telloNoFly.txt: -------------------------------------------------------------------------------- 1 | command 2 | battery? -------------------------------------------------------------------------------- /telloJustQuery.txt: -------------------------------------------------------------------------------- 1 | command 2 | battery? 3 | speed? 4 | time? -------------------------------------------------------------------------------- /telloVertical.txt: -------------------------------------------------------------------------------- 1 | command 2 | battery? 3 | takeoff 4 | down 60 5 | cw 90 6 | up 60 7 | ccw 90 8 | land -------------------------------------------------------------------------------- /telloBounce.txt: -------------------------------------------------------------------------------- 1 | command 2 | battery? 3 | takeoff 4 | down 60 5 | cw 360 6 | up 60 7 | ccw 360 8 | down 60 9 | land -------------------------------------------------------------------------------- /telloBox.txt: -------------------------------------------------------------------------------- 1 | command 2 | battery? 3 | takeoff 4 | forward 100 5 | cw 90 6 | forward 100 7 | cw 90 8 | forward 100 9 | cw 90 10 | forward 100 11 | cw 90 12 | land -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TelloCommandLine.js: -------------------------------------------------------------------------------- 1 | /* 2 | Based On 3 | 4 | Rzye Tello 5 | 6 | Scratch Ext 1.0.0.0 7 | 8 | http://www.ryzerobotics.com 9 | 10 | 1/1/2018 11 | */ 12 | 13 | const readline = require('readline'), 14 | rl = readline.createInterface(process.stdin, process.stdout), 15 | prefix = 'Tello> '; 16 | 17 | 18 | var PORT = 8889; 19 | var HOST = '192.168.10.1'; 20 | 21 | const dgram = require('dgram'); 22 | const client = dgram.createSocket('udp4'); 23 | 24 | client.bind(8001); 25 | 26 | client.on('message', (msg,info) => { 27 | console.log('Data received from server : ' + msg.toString()); 28 | rl.prompt(); 29 | }); 30 | 31 | 32 | console.log('---------------------------------------'); 33 | console.log('Tello Command Console'); 34 | console.log('---------------------------------------'); 35 | 36 | rl.on('line', (input) => { 37 | commandStr = input.trim(); 38 | switch(commandStr) { 39 | case 'quit': 40 | client.close(); 41 | rl.close(); 42 | break; 43 | default: 44 | console.log(`Command: ${commandStr}`); 45 | client.send(commandStr, 0, commandStr.length, PORT, HOST, function(err, bytes) { 46 | if (err) throw err; 47 | }); 48 | break; 49 | } 50 | }).on('close', function() { 51 | console.log('Exiting Command Line Processor'); 52 | process.exit(0); 53 | }); 54 | console.log(prefix + 'Enter a Tello SDK Command.'); 55 | rl.setPrompt(prefix, prefix.length); 56 | rl.prompt(); 57 | 58 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tello-nodejs 2 | Interacting with the DJI/Ryze Tello using only node.js. 3 | 4 | The code in this repo is based on the Tello.js source file distributed along with the Tello SDK. 5 | The keyboard processing logic was adapted from sample code available at: 6 | http://thisdavej.com/making-interactive-node-js-console-apps-that-listen-for-keypress-events/ 7 | 8 | Use at your own risk, especially in low ambiant light conditions. 9 | 10 | Developed and tested using v8.11.1 of node.js 11 | 12 | You may need to import the got package with npm 13 | 14 | 1. Usage: node TelloConsole.js 15 | 16 | You should first connect the computer where you are running node.js to the Tello wireless network for your Tello. 17 | 18 | List of available keyboard commands is printed. 19 | 20 | The first command to execute is c for command mode. 21 | 22 | The land command will be executed when the space bar is pressed. 23 | 24 | To quit the program, enter ctrl-c 25 | 26 | See the Tello SDK documentation for explanation of the commands. 27 | 28 | The commands that need a numeric parameter have a fixed value that is used when the command is sent to the Tello. This behavior may be improved in future versions of the TelloConsole.js file. Only the Flip forward version of the Flip command is currently supported. 29 | 30 | *This is just a getting started version of the program.* 31 | 32 | Extensions and improvements encouraged. 33 | 34 | A new full command line script is also available. Any SDK command can be entered just like for the Tello3.py file distributed by DJI/Ryze for the Tello SDK. 35 | 36 | 2. Usage: node TelloCommandLine.js 37 | 38 | As an added bonus, the readline feature used here has command history so that the up and down arrow keys can be used to find and then re-execute commands in the current session. 39 | 40 | A video demo is available at: https://www.youtube.com/watch?v=BNNRNsa2a3o 41 | 42 | A nice next step would be to add game controller support. node.js seems to support PS3 and PS4 controller types. 43 | 44 | **Just Added:** 45 | 46 | Added a mission file interpreter that reads in mission files (samples included in project) and flies them. 47 | 48 | 3. node TelloMissionFile.js 49 | 50 | This code took some patient experimentation to get working and is **even more experimental**. It relies on the async/await feature of node.js to allow tello commands to be sent to the tello via the normal udp socket and then check for a response. There is a delay pattern built in as well so that the next command is not sent to the tello until a delay of about 5 seconds has elapsed. This makes it more likely that the tello has had enough time to execute the command. The user is prompted for a tello Mission file name and you can run multiple missions without turning off the tello. 51 | 52 | *ToDo: Make the mission file language more expressive with at least loop support* 53 | -------------------------------------------------------------------------------- /TelloMissionFile.js: -------------------------------------------------------------------------------- 1 | /* 2 | Based On 3 | 4 | Rzye Tello 5 | 6 | Scratch Ext 1.0.0.0 7 | 8 | http://www.ryzerobotics.com 9 | 10 | 1/1/2018 11 | */ 12 | 13 | const readline = require('readline'), 14 | rl = readline.createInterface(process.stdin, process.stdout), 15 | prefix = 'Tello Mission> '; 16 | 17 | 18 | const trimNewlines = require('trim-newlines'); 19 | const fs = require('fs') 20 | const commandErr = new Error('Tello Command Error'); 21 | 22 | const PORT = 8889; 23 | const HOST = '192.168.10.1'; 24 | 25 | var commandDelays = new Map([ 26 | ['command', 500], 27 | ['takeoff', 5000], 28 | ['land', 5000], 29 | ['up', 7000], 30 | ['down', 7000], 31 | ['left', 5000], 32 | ['go', 7000], 33 | ['right', 5000], 34 | ['forward', 5000], 35 | ['back', 5000], 36 | ['cw', 5000], 37 | ['ccw', 5000], 38 | ['flip', 3000], 39 | ['speed', 3000], 40 | ['battery?', 500], 41 | ['speed?', 500], 42 | ['time?', 500] 43 | ]); 44 | 45 | var dgram = require('dgram'); 46 | 47 | function telloMessage (message) { 48 | return new Promise(resolve => { 49 | let rx; 50 | var client = dgram.createSocket({type: 'udp4', reuseAddr: true}).bind(8001); 51 | 52 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 53 | if (err) throw err; 54 | }); 55 | 56 | client.on('error', function(e) { 57 | throw e; 58 | }); 59 | 60 | client.on('message', (msg,info) => { 61 | rx = trimNewlines (msg.toString()); 62 | console.log('Data received from server: ' + rx); 63 | resolve(rx); 64 | client.close() 65 | }); 66 | }); 67 | } 68 | 69 | async function doTelloCommand (commandStr) { 70 | 71 | try { 72 | var result = await telloMessage(commandStr); 73 | console.log('Resolved to ' + result + ' for command ' + commandStr); 74 | if (result === 'error') { throw commandErr; } 75 | return result; 76 | } catch (err) { 77 | throw err; 78 | } 79 | 80 | } 81 | 82 | function wait (timeout) { 83 | return new Promise((resolve) => { 84 | setTimeout(() => { 85 | resolve() 86 | }, timeout) 87 | }) 88 | } 89 | 90 | async function doTelloCommandWithRetry (command) { 91 | const MAX_RETRIES = 3; 92 | for (let i = 0; i <= MAX_RETRIES; i++) { 93 | try { 94 | if (i === 0) { 95 | console.log('Trying', command); } else { 96 | console.log('Re-Trying', command, i); 97 | } 98 | var message = await doTelloCommand(new Buffer(command)); 99 | // console.log(message); 100 | break; 101 | } catch (err) { 102 | console.log(err.message); 103 | const timeout = 500 * Math.pow(2, i); 104 | console.log('Waiting', timeout, 'ms'); 105 | await wait(timeout); 106 | } 107 | } 108 | } 109 | 110 | async function promptAfterDelay (delay) { 111 | await wait(delay); 112 | rl.prompt(); 113 | } 114 | 115 | console.log('---------------------------------------'); 116 | console.log('Tello Command File Processor'); 117 | console.log('Enter a File name to Run a Mission'); 118 | console.log('Enter quit or ctrl-c to quit'); 119 | console.log('---------------------------------------'); 120 | 121 | function doMission (filename) { 122 | var commands = require('fs').readFileSync(filename, 'utf-8') 123 | .split('\n') 124 | .filter(Boolean); 125 | 126 | console.log(commands); 127 | 128 | var delay, commandCode, commandTimeEst; 129 | delay = 0; 130 | commandCode = commands[0].split(' ',1)[0]; 131 | commandTimeEst = commandDelays.get(commandCode); 132 | doTelloCommandWithRetry (commands[0]); 133 | for (let i = 1, len = commands.length; i < len; i++) { 134 | delay = delay + commandTimeEst; 135 | setTimeout(doTelloCommandWithRetry,delay,commands[i]); 136 | commandCode = commands[i].split(' ',1)[0]; 137 | commandTimeEst = commandDelays.get(commandCode); 138 | console.log('Estimated Delay ', delay); 139 | } 140 | promptAfterDelay(delay + commandTimeEst); //give time for last command to finish 141 | } 142 | 143 | //doMission ('./telloNoFly.txt'); 144 | 145 | rl.on('line', (input) => { 146 | fileName = input.trim(); 147 | switch(fileName) { 148 | case 'quit': 149 | case 'Quit': 150 | rl.close(); 151 | break; 152 | default: 153 | console.log(`File Name: ${fileName}`); 154 | doMission (fileName); 155 | break; 156 | } 157 | // rl.prompt(); 158 | }).on('close', function() { 159 | console.log('Exiting Mission Processor'); 160 | process.exit(0); 161 | }).on('resume', function () { 162 | rl.prompt(); 163 | }); 164 | console.log(prefix + 'Enter a name of a mission file.'); 165 | rl.setPrompt(prefix, prefix.length); 166 | rl.prompt(); 167 | 168 | 169 | -------------------------------------------------------------------------------- /TelloConsole.js: -------------------------------------------------------------------------------- 1 | /* 2 | Based On 3 | 4 | Rzye Tello 5 | 6 | Scratch Ext 1.0.0.0 7 | 8 | http://www.ryzerobotics.com 9 | 10 | 1/1/2018 11 | */ 12 | 13 | const got = require('got'); 14 | const eol = require('os').EOL; 15 | const readline = require('readline'); 16 | readline.emitKeypressEvents(process.stdin); 17 | process.stdin.setRawMode(true); 18 | 19 | var dataToTrack_keys = ["battery", "x", "y", "z", "speed"]; 20 | var lastDataReceived = null; 21 | 22 | var fs = require('fs'); 23 | 24 | var PORT = 8889; 25 | var HOST = '192.168.10.1'; 26 | 27 | const dgram = require('dgram'); 28 | const client = dgram.createSocket('udp4'); 29 | 30 | client.bind(8001); 31 | 32 | function respondToPoll(response){ 33 | 34 | var noDataReceived = false; 35 | 36 | var resp = ""; 37 | var i; 38 | for (i = 0; i < dataToTrack_keys.length; i++){ 39 | resp += dataToTrack_keys[i] + " "; 40 | resp += (i+10); 41 | resp += "\n"; 42 | } 43 | response.end(resp); 44 | } 45 | 46 | function CommandRequest(){ 47 | 48 | var message = new Buffer('command'); 49 | 50 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 51 | if (err) throw err; 52 | }); 53 | 54 | client.on('message', (msg,info) => { 55 | console.log('Data received from server : ' + msg.toString()); 56 | console.log('Received %d bytes from %s:%d\n',msg.length, info.address, info.port); 57 | }); 58 | 59 | } 60 | 61 | function TakeoffRequest(){ 62 | 63 | var message = new Buffer('takeoff'); 64 | 65 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 66 | if (err) throw err; 67 | }); 68 | } 69 | 70 | function LandRequest(){ 71 | 72 | var message = new Buffer('land'); 73 | 74 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 75 | if (err) throw err; 76 | }); 77 | } 78 | 79 | function sendCommand (telloCommand) { 80 | 81 | switch (telloCommand){ 82 | 83 | case 'poll': 84 | respondToPoll(response); 85 | break; 86 | 87 | case 'command': 88 | console.log('command'); 89 | CommandRequest(); 90 | break; 91 | 92 | case 'takeoff': 93 | console.log('takeoff'); 94 | TakeoffRequest(); 95 | break; 96 | 97 | case 'land': 98 | console.log('land'); 99 | LandRequest(); 100 | break; 101 | 102 | case 'up': 103 | dis = 60; 104 | console.log('up ' + dis); 105 | var message = new Buffer( 'up '+ dis ); 106 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 107 | if (err) throw err; 108 | }); 109 | break; 110 | 111 | case 'down': 112 | dis = 60; 113 | console.log('down ' + dis); 114 | var message = new Buffer( 'down '+ dis ); 115 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 116 | if (err) throw err; 117 | }); 118 | break; 119 | 120 | case 'left': 121 | dis = 100; 122 | console.log('left ' + dis); 123 | var message = new Buffer( 'left '+ dis ); 124 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 125 | if (err) throw err; 126 | }); 127 | break; 128 | 129 | case 'right': 130 | dis = 100; 131 | console.log('right ' + dis); 132 | var message = new Buffer( 'right '+ dis ); 133 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 134 | if (err) throw err; 135 | }); 136 | break; 137 | 138 | case 'forward': 139 | dis = 100; 140 | console.log('forward ' + dis); 141 | var message = new Buffer( 'forward '+ dis ); 142 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 143 | if (err) throw err; 144 | }); 145 | break; 146 | 147 | case 'back': 148 | dis = 100; 149 | console.log('back ' + dis); 150 | var message = new Buffer( 'back '+ dis ); 151 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 152 | if (err) throw err; 153 | }); 154 | break; 155 | 156 | case 'cw': 157 | dis = 360; 158 | console.log('cw ' + dis); 159 | var message = new Buffer( 'cw '+ dis ); 160 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 161 | if (err) throw err; 162 | }); 163 | break; 164 | 165 | case 'flip': 166 | dis = 'f'; 167 | console.log('flip ' + dis); 168 | var message = new Buffer( 'flip '+ dis ); 169 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 170 | if (err) throw err; 171 | }); 172 | break; 173 | 174 | case 'ccw': 175 | dis = 360; 176 | console.log('ccw ' + dis); 177 | var message = new Buffer( 'ccw '+ dis ); 178 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 179 | if (err) throw err; 180 | }); 181 | break; 182 | 183 | case 'battery?': 184 | console.log('battery strength?'); 185 | var message = new Buffer( 'battery?'); 186 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 187 | if (err) throw err; 188 | }); 189 | break; 190 | 191 | case 'time?': 192 | console.log('flight time?'); 193 | var message = new Buffer( 'time?'); 194 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 195 | if (err) throw err; 196 | }); 197 | break; 198 | 199 | case 'speed?': 200 | console.log('current speed?'); 201 | var message = new Buffer( 'speed?'); 202 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 203 | if (err) throw err; 204 | }); 205 | break; 206 | 207 | case 'setspeed': 208 | dis = 20; 209 | console.log('setspeed ' + dis); 210 | var message = new Buffer( 'speed '+ dis ); 211 | client.send(message, 0, message.length, PORT, HOST, function(err, bytes) { 212 | if (err) throw err; 213 | }); 214 | break; 215 | 216 | } 217 | } 218 | 219 | const keyMap = new Map(); 220 | keyMap.set('h', 'help'); 221 | keyMap.set('c', 'command'); 222 | keyMap.set('t', 'takeoff'); 223 | keyMap.set(' ', 'land'); 224 | keyMap.set('f', 'forward'); 225 | keyMap.set('b', 'back'); 226 | keyMap.set('l', 'left'); 227 | keyMap.set('r', 'right'); 228 | keyMap.set('u', 'up'); 229 | keyMap.set('d', 'down'); 230 | keyMap.set('a', 'cw'); 231 | keyMap.set('s', 'ccw'); 232 | keyMap.set('w', 'battery?'); 233 | keyMap.set('z', 'time?'); 234 | keyMap.set('x', 'speed?'); 235 | keyMap.set('q', 'flip'); 236 | 237 | function listKeys() { 238 | console.log(`${eol}keys`); 239 | keyMap.forEach((value, key) => { 240 | console.log(`${key} - ${value}`); 241 | }); 242 | console.log(); 243 | } 244 | process.stdin.on('keypress', (str, key) => { 245 | if (key.ctrl && key.name === 'c') { 246 | client.close() 247 | process.exit(); // eslint-disable-line no-process-exit 248 | } else if (key.name === 'h') { 249 | listKeys(); 250 | } else { 251 | if (keyMap.has(str)) { 252 | sendCommand(keyMap.get(str)); 253 | } else { 254 | console.log(`No symbol defined for "${str}" key.`); 255 | } 256 | } 257 | }); 258 | 259 | 260 | console.log('---------------------------------------'); 261 | console.log('Tello Command Console'); 262 | console.log('---------------------------------------'); 263 | 264 | 265 | 266 | console.log('Press a key to send a command to Tello'); 267 | listKeys(); --------------------------------------------------------------------------------