Source: lib/tello.js

"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