Source: Drone.js

const MiniDroneBtAdapter = require('./MiniDroneBtAdapter');
const EventEmitter = require('events');

/**
 * Drone Class
 *
 * Exposes an API for issuing commands and interfacing
 * with the network adapter.
 * @author Christopher Fetherston <chris@cfetherston.com>
 *
 * @fires Drone#connected
 * @fires Drone#batteryStatusChange
 * @fires Drone#flightStatusChange
 * @fires Drone#batteryStatusChange
 * @fires Drone#maxAltitudeChange
 * @fires Drone#maxTiltChange
 * @fires Drone#maxVerticalSpeedChange
 * @fires Drone#maxRotationSpeedChange
 */
class Drone extends EventEmitter {
    /**
     * Instantiates a new instance of the Drone class
     *
     * @param {Object} options Configuration options object
     * @param {Integer} options.updateMS Frequency of the flight params event loop in ms, default 100ms
     * @param {Boolean} options.autoconnect Connect immediately on instantiation, default false
     * @param {Integer} options.maxAltitude The max height in meters the drone can reach (2-10)
     * @param {Integer} options.maxTilt The max tilt the drone can achieve from 0-100 (100 being the max 20° inclination the drone firmware allows)
     * @param {Float} options.maxVerticalSpeed The max vertical speed in meters/s the drone can reach (0.5 - 2)
     * @param {Integer} options.maxRotationSpeed The rotation speed in °/s the drone can reach (50 - 1000)
     * @param {String} options.droneFilter The name of the drone to restrict connection to
     * @return {Drone} A new instance of the Drone class
     */
    constructor(options) {
        super();
        const defaults = {
            updateMS: 100,
            autoconnect: false,
            maxAltitude: 2,
            maxTilt: 40,
            maxVerticalSpeed: 0.5,
            maxRotationSpeed: 150,
            droneFilter: '',
        };
        this.flightParams = {
            roll: 0,
            pitch: 0,
            yaw: 0,
            altitude: 0,
        };
        this.options = Object.assign({}, defaults, options);
        this.network = null;

        // update loop, writes the flight params to the network every X ms
        this.eventInterval = setInterval(() => this.eventLoop(), this.options.updateMS);

        if (this.options.autoconnect) {
            this.connect();
        }

        this.on('connected', this.onConnected);
    }

    /**
     * Sets the drone's roll, pitch, yaw and altitude
     *
     * @param {Object} flightParams object, all object keys are optional to allow partial updates
     * @param {Integer} flightParams.roll The roll value of the drone -100 to 100(optional)
     * @param {Integer} flightParams.pitch The pitch value of the drone -100 to 100 (optional)
     * @param {Integer} flightParams.yaw The yaw value -100 to 100 (optional)
     * @param {Integer} flightParams.altitude Increase or decrease the altitude (overall rotor RPM) -100 to 100 (optional)
     * @return {undefined}
     */
    setFlightParams(flightParams) {
        this.flightParams = Object.assign({}, this.flightParams, flightParams);
    }

    /**
     * Sets the drone's Max Altitude
     *
     * @param {Integer} altitude Increase or decrease the max altitude 0.5 to 10
     * @return {undefined}
     */
    setMaxAltitude(altitude) {
        this.options.maxAltitude = altitude;
        this.network.writeMaxAltitude(this.options.maxAltitude);
    }

    /**
     * Sets the drone's Max Tilt
     *
     * @param {Integer} tilt set max tilt angle 0-100 (0 = 5° - 100 = 25°)
     * @return {undefined}
     */
    setMaxTilt(tilt) {
        this.options.maxTilt = tilt;
        this.network.writeMaxTilt(this.options.maxTilt);
    }

    /**
     * Sets the drone's MaxVerticalSpeed
     *
     * @param {Integer} speed set max vertical speed in m/s (0.5m/s  to 2m/s)
     * @return {undefined}
     */
    setMaxVerticalSpeed(speed) {
        this.options.maxVerticalSpeed = speed;
        this.network.writeMaxVerticalSpeed(this.options.maxVerticalSpeed);
    }

    /**
     * Sets the drone's MaxRotationSpeed
     *
     * @param {Integer} speed Increase or decrease rotation speed (yaw) in °/sec 0-1000 (50°/s - 360°/s)
     * @return {undefined}
     */
    setMaxRotationSpeed(speed) {
        this.options.maxRotationSpeed = speed;
        this.network.writeMaxRotationSpeed(this.options.maxRotationSpeed);
    }

    /**
     * Sets the filter to connect on a specific drone based on its network name
     *
     * @param {String} name The name of the drone to connect to
     * @return {undefined}
     */
    setDroneFilter(name) {
        this.options.droneFilter = name;
    }

    /**
     * If the drone is in a flight status that is considered flying
     * @return {Boolean} If the drone is flying
     */
    isFlying() {
        if (!this.network) {
            return false;
        }
        const flightStatus = this.network.flightStatus;
        return flightStatus === 'hovering' ||
            flightStatus === 'flying' ||
            flightStatus === 'rolling' ||
            flightStatus === 'taking off';
    }

    /**
     * Toggle the drone's takeoff or land command
     * @return {undefined}
     */
    takeoffOrLand() {
        this.isFlying() ? this.land() : this.takeOff();
    }

    /**
     * Perform the drone's automated takeoff command
     * @return {undefined}
     */
    takeOff() {
        this.network.writeTakeoff();
    }

    /**
     * Perform the drone's automated land command
     * @return {undefined}
     */
    land() {
        this.network.writeLand();
    }

    /**
     * Perform the drone's trim command
     * @return {undefined}
     */
    trim() {
        this.network.writeTrim();
    }

    /**
     * Perform the drone's take a picture command
     * @return {undefined}
     */
    takePicture() {
        this.network.writeTakePicture();
    }

    /**
     * Perform the drone's emergency landing, kills the rotors
     * @return {undefined}
     */
    emergency() {
        this.network.writeEmergency();
    }

    /**
     * Preform the drone's animation routines
     * @param  {String} animation one of the following animation methods:
     *                  flipFront, flipBack, flipRight, flipLeft
     * @return {undefined}
     */
    animate(animation) {
        this.network.writeAnimation(animation);
    }

    /**
     * Pairs with the drone as a BTLE peripheral
     * @return {undefined}
     */
    connect() {
        if (this.network) {
            return;
        }
        this.network = new MiniDroneBtAdapter({
            droneFilter: this.options.droneFilter,
        });

        // TODO: do not love the events stuff :/

        /**
         * Fires when the Drone successfully connected over Bluetooth
         *
         * @event Drone#connected
         * @type {object}
         */
        this.network.on('connected', (...args) => this.emit('connected', ...args));

        /**
         * Fires when a flight param command is written over the Bluetooth network
         *
         * @event Drone#flightParamChange
         * @type {object}
         */
        this.network.on('flightParamChange', (...args) => this.emit('flightParamChange', ...args));

        /**
         * Fires when the drone's flight status has changed
         *
         * @event Drone#flightStatusChange
         * @type {object}
         */
        this.network.on('flightStatusChange', (...args) => this.emit('flightStatusChange', ...args));

        /**
         * Fires when the drone's battery status has changed
         *
         * @event Drone#batteryStatusChange
         * @type {object}
         */
        this.network.on('batteryStatusChange', (...args) => this.emit('batteryStatusChange', ...args));

        /**
         * Fires when the drone's max altitude  has changed
         *
         * @event Drone#maxAltitudeChange
         * @type {object}
         */
        this.network.on('maxAltitudeChange', (...args) => this.emit('maxAltitudeChange', ...args));

        /**
         * Fires when the drone's max tilt  has changed
         *
         * @event Drone#maxTiltChange
         * @type {object}
         */
        this.network.on('maxTiltChange', (...args) => this.emit('maxTiltChange', ...args));

        /**
         * Fires when the drone's max vertical speed has changed
         *
         * @event Drone#maxVerticalSpeed
         * @type {object}
         */
        this.network.on('maxVerticalSpeedChange', (...args) => this.emit('maxVerticalSpeedChange', ...args));

        /**
         * Fires when the drone's max rotation speed has changed
         *
         * @event Drone#maxRotationSpeed
         * @type {object}
         */
        this.network.on('maxRotationSpeedChange', (...args) => this.emit('maxRotationSpeedChange', ...args));

        /**
         * Fires when Rssi update is Received
         *
         */
        this.network.on('rssiUpdate', (...args) => this.emit('rssiUpdate', ...args));
    }

    /**
     * The event loop that updates the drone's flight params every X ms
     * @return {undefined}
     */
    eventLoop() {
        if (!this.network || !this.network.connected) {
            return;
        }
        this.network.writeFlightParams(this.flightParams);
    }

    /**
     * Returns the battery level of the drone
     * @return {integer} The battery level %
     */
    getBatteryLevel() {
        return this.network.batteryLevel;
    }

    /**
     * Gets Rssi value
     * @return {interger} Rssi value
     */
    getRssi() {
        this.network.updateRssi();
    }

    /**
     * Preforms some basic setup immediately after the drone is connected
     * @return {undefined}
     */
    onConnected() {
        this.setMaxAltitude(this.options.maxAltitude);
        this.setMaxTilt(this.options.maxTilt);
        this.setMaxVerticalSpeed(this.options.maxVerticalSpeed);
        this.setMaxRotationSpeed(this.options.maxRotationSpeed);
    }
}

module.exports = Drone;