"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Tello = void 0;
const child_process_1 = require("child_process");
const events_1 = require("events");
const logger_1 = require("./logger");
const webServer_1 = require("../servers/webServer");
const telemetry_1 = require("../servers/telemetry");
const dgram_1 = require("dgram");
const open_1 = __importDefault(require("open"));
const HOST = '192.168.10.1';
const PORT = 8889;
const STATE_PORT = 8890;
const VIDEO_PORT = 11111;
const LOCAL_PORT = 50602;
/** Main drone interface. Wraps Tello's UDP command protocol, video stream, and telemetry into a fluent Promise API. */
class Tello {
constructor() {
this.isMock = false;
this.isStreaming = false;
this.hasTelemetry = false;
this.emitter = new events_1.EventEmitter();
this.osdData = {};
this.deviceIP = HOST;
this.telloWebServer = new webServer_1.TelloWebServer();
this.telloTelemetry = new telemetry_1.TelloTelemetry();
}
/** Wait for a given number of milliseconds, then resolve. */
wait(time) {
return new Promise(resolve => setTimeout(() => resolve(this), time));
}
/** Stop the video stream and kill the mplayer subprocess. */
stopStream() {
return new Promise(resolve => {
this.sendCmd('streamoff').then(() => {
var _a, _b;
(_a = this.telloVideo) === null || _a === void 0 ? void 0 : _a.close();
this.telloVideo = undefined;
(_b = this.h264encoder) === null || _b === void 0 ? void 0 : _b.kill();
this.isStreaming = false;
resolve(this);
});
});
}
/** Start the video stream. Opens a UDP socket on port 11111 and pipes H.264 data to mplayer. */
startStream() {
return new Promise(resolve => {
this.sendCmd('streamon').then(() => {
this.telloVideo = (0, dgram_1.createSocket)('udp4');
this.h264encoder = (0, child_process_1.spawn)('mplayer', ['-gui', '-nolirc', '-fps', '35', '-really-quiet', '-']);
this.h264encoder.stderr.on('data', data => {
logger_1.Logger.error('[Tello]', 'mplayer error', data.toString());
});
this.telloVideo.on('message', msg => { var _a; return (_a = this.h264encoder) === null || _a === void 0 ? void 0 : _a.stdin.write(msg); });
this.telloVideo.on('listening', () => {
const addr = this.telloVideo.address();
logger_1.Logger.info('[Tello]', `tello_video listening ${addr.address}:${addr.port}`);
this.isStreaming = true;
resolve(this);
});
this.telloVideo.bind(VIDEO_PORT, '0.0.0.0');
});
});
}
/** Switch to mock mode: send commands to 127.0.0.1 instead of the drone. */
mock() {
this.deviceIP = '127.0.0.1';
this.isMock = true;
return this;
}
/** Start the telemetry dashboard and WebSocket server. Opens the browser unless BROWSER=none. */
startTelemetry(options) {
if (this.isMock)
return Promise.resolve(this);
return new Promise(resolve => {
this.telloWebServer.start().then(() => {
this.runTelemetry(options).then(() => resolve(this));
});
});
}
runTelemetry(options) {
return new Promise(resolve => {
this.telloTelemetry.start(options).then(() => {
this.hasTelemetry = true;
const finish = () => {
logger_1.Logger.info('[Tello]', 'Telemetry started');
resolve(this);
};
if (process.env.BROWSER === 'none') {
finish();
}
else {
(0, open_1.default)('http://127.0.0.1:3000/telemetry.html').then(finish);
}
});
});
}
/** Stop the telemetry server and web server. */
stopTelemetry() {
return new Promise(resolve => {
this.shutDownTelemetry().then(() => {
this.telloTelemetry.stop().then(() => {
logger_1.Logger.info('[Tello]', 'Stopping telemetry');
resolve(this);
});
});
});
}
shutDownTelemetry() {
if (this.isMock)
return Promise.resolve(this);
return new Promise(resolve => {
this.telloWebServer.stop().then(() => resolve(this));
});
}
/** Read a single OSD telemetry field by name (e.g. 'bat', 'h', 'templ'). */
get(value) {
return Promise.resolve({ value: this.osdData[value], tello: this });
}
listenState() {
if (this.hasTelemetry) {
this.UDPServer.on('message', msg => {
const strMsg = msg.toString().trim();
strMsg.split(';').forEach(field => {
const [key, val] = field.split(':');
this.osdData[key] = val;
});
this.telloTelemetry.send(this.osdData);
});
this.UDPServer.on('listening', () => {
const address = this.UDPServer.address();
logger_1.Logger.info('[Tello]', 'server listening', address.address, address.port);
});
this.UDPServer.bind(STATE_PORT, '0.0.0.0');
}
}
sendRaw(cmd) {
return new Promise((resolve, reject) => {
const message = Buffer.from(cmd);
logger_1.Logger.info('[Tello]', 'send:', cmd);
this.UDPClient.send(message, 0, message.length, PORT, this.deviceIP, err => {
if (err)
reject(err);
else
resolve(this);
});
});
}
/** Low-level command: send a raw string to the drone and wait for 'ok' response. */
sendCmd(cmd) {
return new Promise((resolve, reject) => {
this.sendRaw(cmd).then(() => {
this.emitter.once('status', () => resolve(this));
}).catch(reject);
});
}
/** Stop all motors immediately. */
emergencyStop() { return this.sendCmd('emergency'); }
/** Auto-takeoff. */
takeoff() { return this.sendCmd('takeoff'); }
/** Auto-land. */
land() { return this.sendCmd('land'); }
/** Fly a curve between two points (x1,y1,z1 → x2,y2,z2) at the given speed (cm/s). */
curve(x1 = 20, y1 = 20, z1 = 20, x2 = 60, y2 = 40, z2 = 0, speed = 60) {
return this.sendCmd(`curve ${x1} ${y1} ${z1} ${x2} ${y2} ${z2} ${speed} `);
}
/** Fly forward by distance (cm). */
forward(distance = 50) { return this.sendCmd(`forward ${distance}`); }
/** Fly backward by distance (cm). */
backward(distance = 50) { return this.sendCmd(`back ${distance}`); }
/** Fly left by distance (cm). */
left(distance = 50) { return this.sendCmd(`left ${distance}`); }
/** Fly right by distance (cm). */
right(distance = 50) { return this.sendCmd(`right ${distance}`); }
/** Fly up by height (cm). */
up(height = 50) { return this.sendCmd(`up ${height}`); }
/** Fly down by height (cm). */
down(height = 50) { return this.sendCmd(`down ${height}`); }
/** Set drone speed (cm/s). */
speed(speed = 50) { return this.sendCmd(`speed ${speed}`); }
/** Rotate clockwise by angle (degrees). */
rotateCW(angle = 90) { return this.sendCmd(`cw ${angle}`); }
/** Rotate counter-clockwise by angle (degrees). */
rotateCCW(angle = 90) { return this.sendCmd(`ccw ${angle}`); }
/** Perform a flip in the given direction: 'f' (forward), 'b' (back), 'l' (left), 'r' (right). */
flip(orientation = 'f') { return this.sendCmd(`flip ${orientation}`); }
/** Fly to coordinates (x, y, z) at speed (cm/s). */
flyTo(x = 50, y = 50, z = 0, speed = 100) {
return this.sendCmd(`go ${x} ${y} ${z} ${speed} `);
}
/** Initialise the UDP connection to the drone and enter SDK mode. */
start() {
this.emitter.setMaxListeners(20);
return new Promise(resolve => {
this.UDPClient = (0, dgram_1.createSocket)('udp4');
if (this.hasTelemetry) {
this.UDPServer = (0, dgram_1.createSocket)('udp4');
}
this.UDPClient.bind(LOCAL_PORT, '0.0.0.0', () => {
logger_1.Logger.info('[Tello]', 'connected');
this.sendCmd('command').then(() => resolve(this));
});
process.on('SIGINT', () => this.stop());
this.UDPClient.on('message', msg => {
if (msg.toString() === 'ok') {
logger_1.Logger.info('[Tello]', 'Data received from drone:', msg.toString());
}
else {
logger_1.Logger.error('[Tello]', 'unexpected response', msg);
}
this.emitter.emit('status');
});
this.listenState();
});
}
/** Close all connections, kill subprocesses, and exit. */
async stop() {
var _a, _b;
this.UDPClient.close();
if (this.hasTelemetry)
this.UDPServer.close();
(_a = this.telloVideo) === null || _a === void 0 ? void 0 : _a.close();
(_b = this.h264encoder) === null || _b === void 0 ? void 0 : _b.kill();
if (!this.isMock && this.hasTelemetry)
await this.stopTelemetry();
logger_1.Logger.info('[Tello]', 'Goodbye!');
process.exit(0);
}
}
exports.Tello = Tello;
//# sourceMappingURL=tello.js.map