All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.codeminders.ardrone.ARDrone Maven / Gradle / Ivy

The newest version!

package com.codeminders.ardrone;

import java.io.IOException;
import java.net.*;
import java.util.LinkedList;
import java.util.List;

import java.util.logging.Level;
import java.util.logging.Logger;

import com.codeminders.ardrone.NavData.FlyingState;
import com.codeminders.ardrone.commands.*;

public class ARDrone
{
    public enum State
    {
        DISCONNECTED, CONNECTING, BOOTSTRAP, DEMO, ERROR, TAKING_OFF, LANDING
    }

    public enum VideoChannel
    {
        HORIZONTAL_ONLY, VERTICAL_ONLY, VERTICAL_IN_HORIZONTAL, HORIZONTAL_IN_VERTICAL
    }

    public enum Animation
    {
        PHI_M30_DEG(0), PHI_30_DEG(1), THETA_M30_DEG(2), THETA_30_DEG(3), THETA_20DEG_YAW_200DEG(4), THETA_20DEG_YAW_M200DEG(
                5), TURNAROUND(6), TURNAROUND_GODOWN(7), YAW_SHAKE(8), YAW_DANCE(9), PHI_DANCE(10), THETA_DANCE(11), VZ_DANCE(
                12), WAVE(13), PHI_THETA_MIXED(14), DOUBLE_PHI_THETA_MIXED(15), ANIM_MAYDAY(16);

        private int value;

        private Animation(int value)
        {
            this.value = value;
        }

        public int getValue()
        {
            return value;
        }
    }

    public enum LED
    {
        BLINK_GREEN_RED(0), BLINK_GREEN(1), BLINK_RED(2), BLINK_ORANGE(3), SNAKE_GREEN_RED(4), FIRE(5), STANDARD(6), RED(
                7), GREEN(8), RED_SNAKE(9), BLANK(10), RIGHT_MISSILE(11), LEFT_MISSILE(12), DOUBLE_MISSILE(13), FRONT_LEFT_GREEN_OTHERS_RED(
                14), FRONT_RIGHT_GREEN_OTHERS_RED(15), REAR_RIGHT_GREEN_OTHERS_RED(16), REAR_LEFT_GREEN_OTHERS_RED(17), LEFT_GREEN_RIGHT_RED(
                18), LEFT_RED_RIGHT_GREEN(19), BLINK_STANDARD(20);

        private int value;

        private LED(int value)
        {
            this.value = value;
        }

        public int getValue()
        {
            return value;
        }
    }

    public enum ConfigOption
    {
        ACCS_OFFSET("control:accs_offset"), ACCS_GAINS("control:accs_gains"), GYROS_OFFSET("control:gyros_offset"), GYROS_GAINS(
                "control:gyros_gains"), GYROS110_OFFSET("control:gyros110_offset"), GYROS110_GAINS(
                "control:gyros110_gains"), GYRO_OFFSET_THR_X("control:gyro_offset_thr_x"), GYRO_OFFSET_THR_Y(
                "control:gyro_offset_thr_y"), GYRO_OFFSET_THR_Z("control:gyro_offset_thr_z"), PWM_REF_GYROS(
                "control:pwm_ref_gyros"), CONTROL_LEVEL("control:control_level"), SHIELD_ENABLE("control:shield_enable"), EULER_ANGLE_MAX(
                "control:euler_angle_max"), ALTITUDE_MAX("control:altitude_max"), ALTITUDE_MIN("control:altitude_min"), CONTROL_TRIM_Z(
                "control:control_trim_z"), CONTROL_IPHONE_TILT("control:control_iphone_tilt"), CONTROL_VZ_MAX(
                "control:control_vz_max"), CONTROL_YAW("control:control_yaw"), OUTDOOR("control:outdoor"), FLIGHT_WITHOUT_SHELL(
                "control:flight_without_shell"), BRUSHLESS("control:brushless"), AUTONOMOUS_FLIGHT(
                "control:autonomous_flight"), MANUAL_TRIM("control:manual_trim"), INDOOR_EULER_ANGLE_MAX(
                "control:indoor_euler_angle_max"), INDOOR_CONTROL_VZ_MAX("control:indoor_control_vz_max"), INDOOR_CONTROL_YAW(
                "control:indoor_control_yaw"), OUTDOOR_EULER_ANGLE_MAX("control:outdoor_euler_angle_max"), OUTDOOR_CONTROL_VZ_MAX(
                "control:outdoor_control_vz_max"), OUTDOOR_CONTROL_YAW("outdoor_control:control_yaw");

        private String value;

        private ConfigOption(String value)
        {
            this.value = value;
        }

        public String getValue()
        {
            return value;
        }
    }

    private Logger                          log               = Logger.getLogger(getClass().getName());

    private static final int                CMD_QUEUE_SIZE    = 64;
    private State                           state             = State.DISCONNECTED;
    private Object                          state_mutex       = new Object();

    private static final int                NAVDATA_PORT      = 5554;
    private static final int                VIDEO_PORT        = 5555;
    // private static final int CONTROL_PORT = 5559;

    final static byte[]                     DEFAULT_DRONE_IP  = { (byte) 192, (byte) 168, (byte) 1, (byte) 1 };

    private InetAddress                     drone_addr;
    private DatagramSocket                  cmd_socket;
    // private Socket control_socket;

    private CommandQueue                    cmd_queue         = new CommandQueue(CMD_QUEUE_SIZE);

    private NavDataReader                   nav_data_reader;
    private VideoReader                     video_reader;
    private CommandSender                   cmd_sender;

    private Thread                          nav_data_reader_thread;
    private Thread                          cmd_sending_thread;
    private Thread                          video_reader_thread;

    private boolean                         combinedYawMode   = true;

    private boolean                         emergencyMode     = true;
    private Object                          emergency_mutex   = new Object();

    private List status_listeners  = new LinkedList();
    private List        image_listeners   = new LinkedList();
    private List           navdata_listeners = new LinkedList();

    private static int                      navDataReconnectTimeout = 1000; // 1 second

    private static int                      videoReconnectTimeout   = 1000; // 1 second

    public ARDrone() throws UnknownHostException
    {
        this(InetAddress.getByAddress(DEFAULT_DRONE_IP), navDataReconnectTimeout, videoReconnectTimeout);
    }

    public ARDrone(InetAddress drone_addr, int navDataReconnectTimeout, int videoReconnectTimeout)
    {
        this.drone_addr = drone_addr;
        this.navDataReconnectTimeout = navDataReconnectTimeout;
        this.videoReconnectTimeout = videoReconnectTimeout;
    }

    public void addImageListener(DroneVideoListener l)
    {
        synchronized(image_listeners)
        {
            image_listeners.add(l);
        }
    }

    public void removeImageListener(DroneVideoListener l)
    {
        synchronized(image_listeners)
        {
            image_listeners.remove(l);
        }
    }

    public void clearImageListeners()
    {
        synchronized(image_listeners)
        {
            image_listeners.clear();
        }
    }

    public void addStatusChangeListener(DroneStatusChangeListener l)
    {
        synchronized(status_listeners)
        {
            status_listeners.add(l);
        }
    }

    public void removeStatusChangeListener(DroneStatusChangeListener l)
    {
        synchronized(status_listeners)
        {
            status_listeners.remove(l);
        }
    }

    public void clearStatusChangeListeners()
    {
        synchronized(status_listeners)
        {
            status_listeners.clear();
        }
    }

    public void addNavDataListener(NavDataListener l)
    {
        synchronized(navdata_listeners)
        {
            navdata_listeners.add(l);
        }
    }

    public void removeNavDataListener(NavDataListener l)
    {
        synchronized(navdata_listeners)
        {
            navdata_listeners.remove(l);
        }
    }

    public void clearNavDataListeners()
    {
        synchronized(navdata_listeners)
        {
            navdata_listeners.clear();
        }
    }

    private void changeState(State newstate) throws IOException
    {
        if(newstate == State.ERROR)
            changeToErrorState(null);

        synchronized(state_mutex)
        {
            if(state != newstate)
            {
                log.fine("State changed from " + state + " to " + newstate);
                state = newstate;

                // We automatically switch to DEMO from bootstrap
                if(state == State.BOOTSTRAP)
                    sendDemoNavigationData();

                state_mutex.notifyAll();
            }
        }

        if(newstate == State.DEMO)
        {
            synchronized(status_listeners)
            {
                for(DroneStatusChangeListener l : status_listeners)
                    l.ready();
            }
        }
    }

    public void changeToErrorState(Exception ex)
    {
        synchronized(state_mutex)
        {
            try
            {
                if(state != State.DISCONNECTED)
                    doDisconnect();
            } catch(IOException e)
            {
                // Ignoring exceptions on disconnection
            }
            log.log(Level.FINE ,"State changed from " + state + " to " + State.ERROR + " with exception ", ex);
            state = State.ERROR;
            state_mutex.notifyAll();
        }
    }

    public void clearEmergencySignal() throws IOException
    {
        synchronized(emergency_mutex)
        {
            if(isEmergencyMode())
                cmd_queue.add(new EmergencyCommand());
        }
    }

    /**
     * Initiate drone connection procedure.
     * 
     * @throws IOException
     */
    public void connect() throws IOException
    {
        try
        {
            cmd_socket = new DatagramSocket();

            cmd_sender = new CommandSender(cmd_queue, this, drone_addr, cmd_socket);
            cmd_sending_thread = new Thread(cmd_sender);
            cmd_sending_thread.setName("Command Sender");
            cmd_sending_thread.start();

            nav_data_reader = new NavDataReader(this, drone_addr, NAVDATA_PORT, navDataReconnectTimeout);
            nav_data_reader_thread = new Thread(nav_data_reader);
            nav_data_reader_thread.setName("NavData Reader");
            nav_data_reader_thread.start();

            video_reader = new VideoReader(this, drone_addr, VIDEO_PORT, videoReconnectTimeout);
            video_reader_thread = new Thread(video_reader);
            video_reader_thread.setName("Video Reader");
            video_reader_thread.start();

            changeState(State.CONNECTING);

        } catch(IOException ex)
        {
            changeToErrorState(ex);
            throw ex;
        }
    }

    public void disableAutomaticVideoBitrate() throws IOException
    {
        cmd_queue.add(new ConfigureCommand("video:bitrate_control_mode", "0"));
    }

    public void disconnect() throws IOException
    {
        try
        {
            doDisconnect();
        } finally
        {
            changeState(State.DISCONNECTED);
        }
    }

    private void doDisconnect() throws IOException
    {
        if(cmd_queue != null)
            cmd_queue.add(new QuitCommand());

        if(nav_data_reader != null)
            nav_data_reader.finish();

        if(video_reader != null)
            video_reader.finish();

        if(cmd_socket != null)
            cmd_socket.close();

        // Only the following method can throw an exception.
        // We call it last, to ensure it won't prevent other
        // cleanup operations from being completed
        // control_socket.close();
    }

    /**
     * Enables the automatic bitrate control of the video stream. Enabling this
     * configuration will reduce the bandwith used by the video stream under bad
     * Wi-Fi conditions, reducing the commands latency. Note : Before enabling
     * this config, make sure that your video decoder is able to handle the
     * variable bitrate mode !
     * 
     * @throws IOException
     */
    public void enableAutomaticVideoBitrate() throws IOException
    {
        setConfigOption("video:bitrate_control_mode", "1");
    }

    public void hover() throws IOException
    {
        cmd_queue.add(new HoverCommand());
    }

    public boolean isCombinedYawMode()
    {
        return combinedYawMode;
    }

    public boolean isEmergencyMode()
    {
        return emergencyMode;
    }

    public void land() throws IOException
    {
        // TODO: Review of possible race condition
        cmd_queue.add(new LandCommand());
        changeState(State.LANDING);
    }

    /**
     * Move the drone
     * 
     * @param left_right_tilt The left-right tilt (aka. "drone roll" or phi
     *            angle) argument is a percentage of the maximum inclination as
     *            configured here. A negative value makes the drone tilt to its
     *            left, thus flying leftward. A positive value makes the drone
     *            tilt to its right, thus flying rightward.
     * @param front_back_tilt The front-back tilt (aka. "drone pitch" or theta
     *            angle) argument is a percentage of the maximum inclination as
     *            configured here. A negative value makes the drone lower its
     *            nose, thus flying frontward. A positive value makes the drone
     *            raise its nose, thus flying backward. The drone translation
     *            speed in the horizontal plane depends on the environment and
     *            cannot be determined. With roll or pitch values set to 0, the
     *            drone will stay horizontal but continue sliding in the air
     *            because of its inertia. Only the air resistance will then make
     *            it stop.
     * @param vertical_speed The vertical speed (aka. "gaz") argument is a
     *            percentage of the maximum vertical speed as defined here. A
     *            positive value makes the drone rise in the air. A negative
     *            value makes it go down.
     * @param angular_speed The angular speed argument is a percentage of the
     *            maximum angular speed as defined here. A positive value makes
     *            the drone spin right; a negative value makes it spin left.
     * @throws IOException
     */
    public void move(float left_right_tilt, float front_back_tilt, float vertical_speed, float angular_speed)
            throws IOException
    {
        cmd_queue
                .add(new MoveCommand(combinedYawMode, left_right_tilt, front_back_tilt, vertical_speed, angular_speed));
    }

    // Callback used by receiver
    public void navDataReceived(NavData nd)
    {
        if(nd.isBatteryTooLow() || nd.isNotEnoughPower())
        {
            log.severe("Battery pb " + nd.toString());
        }

        synchronized(emergency_mutex)
        {
            emergencyMode = nd.isEmergency();
        }

        try
        {
            synchronized(state_mutex)
            {
                if(state != State.CONNECTING && nd.isControlReceived())
                {
                    log.fine("Control received! ACK!");
                    cmd_queue.add(new ControlCommand(5, 0));
                }

                if(state == State.TAKING_OFF && nd.getFlyingState() == FlyingState.FLYING)
                {
                    log.fine("Take off success");
                    cmd_queue.clear(); // Maybe we should just remove
                                       // LAND/TAKEOFF comand
                                       // instead of nuking the whole queue?
                    changeState(State.DEMO);
                } else if(state == State.LANDING && nd.getFlyingState() == FlyingState.LANDED)
                {
                    log.fine("Landing success");
                    cmd_queue.clear(); // Maybe we should just remove
                                       // LAND/TAKEOFF comand
                                       // instead of nuking the whole queue?
                    changeState(State.DEMO);
                } else if(state != State.BOOTSTRAP && nd.getMode() == NavData.Mode.BOOTSTRAP)
                {
                    changeState(State.BOOTSTRAP);
                } else if(state == State.BOOTSTRAP && nd.getMode() == NavData.Mode.DEMO)
                {
                    changeState(State.DEMO);
                }

                if(state != State.CONNECTING && nd.isCommunicationProblemOccurred())
                {
                    // 50ms communications watchdog has been triggered
                    cmd_queue.add(new KeepAliveCommand());
                }

            }
        } catch(IOException e)
        {
            log.log(Level.SEVERE, "Error changing the state", e);
        }

        if(state == State.DEMO)
        {
            synchronized(navdata_listeners)
            {
                for(NavDataListener l : navdata_listeners)
                    l.navDataReceived(nd);
            }
        }
    }

    public void playAnimation(int animation_no, int duration) throws IOException
    {
        cmd_queue.add(new PlayAnimationCommand(animation_no, duration));
    }

    public void playAnimation(Animation animation, int duration) throws IOException
    {
        cmd_queue.add(new PlayAnimationCommand(animation.getValue(), duration));
    }

    public void playLED(int animation_no, float freq, int duration) throws IOException
    {
        cmd_queue.add(new PlayLEDCommand(animation_no, freq, duration));
    }

    public void playLED(LED animation, float freq, int duration) throws IOException
    {
        cmd_queue.add(new PlayLEDCommand(animation.getValue(), freq, duration));
    }

    public void selectVideoChannel(VideoChannel c) throws IOException
    {
        /*
         * Current implementation supports 4 different channels : -
         * ARDRONE_VIDEO_CHANNEL_HORI - ARDRONE_VIDEO_CHANNEL_VERT -
         * ARDRONE_VIDEO_CHANNEL_LARGE_HORI_SMALL_VERT -
         * ARDRONE_VIDEO_CHANNEL_LARGE_VERT_SMALL_HORI
         * 
         * AT command example : AT*CONFIG=605,"video:video_channel","2"
         */

        String s;
        switch(c)
        {
        case HORIZONTAL_ONLY: // ARDRONE_VIDEO_CHANNEL_HORI
            s = "0";
            break;

        case VERTICAL_ONLY: // ARDRONE_VIDEO_CHANNEL_VERT
            s = "1";
            break;

        case VERTICAL_IN_HORIZONTAL: // ARDRONE_VIDEO_CHANNEL_LARGE_HORI_SMALL_VERT
            s = "2";
            break;

        case HORIZONTAL_IN_VERTICAL: // ARDRONE_VIDEO_CHANNEL_LARGE_VERT_SMALL_HORI
            s = "3";
            break;
        default:
            assert (false);
            return;
        }

        cmd_queue.add(new ConfigureCommand("video:video_channel", s));
    }

    public void sendAllNavigationData() throws IOException
    {
        setConfigOption("general:navdata_demo", "FALSE");
    }

    public void sendDemoNavigationData() throws IOException
    {
        setConfigOption("general:navdata_demo", "TRUE");
    }

    public void sendEmergencySignal() throws IOException
    {
        synchronized(emergency_mutex)
        {
            if(!isEmergencyMode())
                cmd_queue.add(new EmergencyCommand());
        }
    }

    public void setCombinedYawMode(boolean combinedYawMode)
    {
        this.combinedYawMode = combinedYawMode;
    }

    public void setConfigOption(String name, String value) throws IOException
    {
        cmd_queue.add(new ConfigureCommand(name, value));
    }

    public void setConfigOption(ConfigOption option, String value) throws IOException
    {
        cmd_queue.add(new ConfigureCommand(option.getValue(), value));
    }

    public void takeOff() throws IOException
    {
        // TODO: review for possible race condition
    	if (!isEmergencyMode()) {
	        cmd_queue.add(new TakeOffCommand());
	        changeState(State.TAKING_OFF);
    	}
    }

    public void trim() throws IOException
    {
        cmd_queue.add(new FlatTrimCommand());
    }

    // Callback used by VideoReciver
    public void videoFrameReceived(int startX, int startY, int w, int h, int[] rgbArray, int offset, int scansize)
    {
        synchronized(image_listeners)
        {
            for(DroneVideoListener l : image_listeners)
                l.frameReceived(startX, startY, w, h, rgbArray, offset, scansize);
        }
    }

    /**
     * Wait for drone to switch to demo mode. Throw exception if this not
     * succeeded within given timeout. Should be called right after connect().
     * 
     * This is a convenience function. Another way to achieve the same result is
     * using status change callback.
     * 
     * @param how_long
     * @throws IOException
     */
    public void waitForReady(long how_long) throws IOException
    {
        long since = System.currentTimeMillis();
        synchronized(state_mutex)
        {
            while(true)
            {
                if(state == State.DEMO)
                {
                    return; // OK! We are now connected
                } else if(state == State.ERROR || state == State.DISCONNECTED)
                {
                    throw new IOException("Connection Error");
                } else if((System.currentTimeMillis() - since) >= how_long)
                {
                    try
                    {
                        disconnect();
                    } catch(IOException e)
                    {
                    }
                    // Timeout, too late
                    throw new IOException("Timeout connecting to ARDrone");
                } 
                

                long p = Math.min(how_long - (System.currentTimeMillis() - since), how_long);
                if(p > 0)
                {
                    try
                    {
                        state_mutex.wait(p);
                    } catch(InterruptedException e)
                    {
                        // Ignore
                    }
                }
            }
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy