hybridgroup-argus 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/MIT-LICENSE +20 -0
- data/README.md +27 -0
- data/Rakefile +9 -0
- data/lib/argus/at_commander.rb +76 -0
- data/lib/argus/controller.rb +130 -0
- data/lib/argus/drone.rb +36 -0
- data/lib/argus/nav_data.rb +61 -0
- data/lib/argus/nav_streamer.rb +18 -0
- data/lib/argus/pave_parser.rb +39 -0
- data/lib/argus/tcp_video_streamer.rb +20 -0
- data/lib/argus/udp_nav_streamer.rb +24 -0
- data/lib/argus/udp_sender.rb +13 -0
- data/lib/argus/version.rb +10 -0
- data/lib/argus.rb +9 -0
- data/spec/argus/at_commander_spec.rb +54 -0
- data/spec/argus/controller_spec.rb +95 -0
- data/spec/argus/udp_sender_spec.rb +20 -0
- data/spec/spec_helper.rb +10 -0
- metadata +91 -0
    
        data/MIT-LICENSE
    ADDED
    
    | @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            Copyright (c) 2012 Jim Weirich
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            Permission is hereby granted, free of charge, to any person obtaining
         | 
| 4 | 
            +
            a copy of this software and associated documentation files (the
         | 
| 5 | 
            +
            "Software"), to deal in the Software without restriction, including
         | 
| 6 | 
            +
            without limitation the rights to use, copy, modify, merge, publish,
         | 
| 7 | 
            +
            distribute, sublicense, and/or sell copies of the Software, and to
         | 
| 8 | 
            +
            permit persons to whom the Software is furnished to do so, subject to
         | 
| 9 | 
            +
            the following conditions:
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            The above copyright notice and this permission notice shall be
         | 
| 12 | 
            +
            included in all copies or substantial portions of the Software.
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
         | 
| 15 | 
            +
            EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
         | 
| 16 | 
            +
            MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
         | 
| 17 | 
            +
            NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
         | 
| 18 | 
            +
            LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
         | 
| 19 | 
            +
            OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
         | 
| 20 | 
            +
            WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
         | 
    
        data/README.md
    ADDED
    
    | @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            # Argus -- Parrot AR Drone Ruby API
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            ## Current Status
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            * Experimental
         | 
| 6 | 
            +
            * Subject to change
         | 
| 7 | 
            +
            * Use at your own risk
         | 
| 8 | 
            +
            * May cause cancer
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            ## Example
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            <pre>
         | 
| 13 | 
            +
              require 'argus'
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              drone = Argus::Drone.new
         | 
| 16 | 
            +
              drone.start
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              drone.take_off
         | 
| 19 | 
            +
              sleep 5
         | 
| 20 | 
            +
              drone.turn_right(1.0)
         | 
| 21 | 
            +
              sleep 5
         | 
| 22 | 
            +
              drone.turn_left(1.0)
         | 
| 23 | 
            +
              sleep 5
         | 
| 24 | 
            +
              drone.hover.land
         | 
| 25 | 
            +
              sleep 5
         | 
| 26 | 
            +
              drone.stop
         | 
| 27 | 
            +
            </pre>
         | 
    
        data/Rakefile
    ADDED
    
    
| @@ -0,0 +1,76 @@ | |
| 1 | 
            +
            require 'thread'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Argus
         | 
| 4 | 
            +
              class ATCommander
         | 
| 5 | 
            +
                def initialize(sender)
         | 
| 6 | 
            +
                  @sender = sender
         | 
| 7 | 
            +
                  @seq = 0
         | 
| 8 | 
            +
                  @ref_data = "0"
         | 
| 9 | 
            +
                  @pcmd_data = "0,0,0,0,0"
         | 
| 10 | 
            +
                  @buffer = ""
         | 
| 11 | 
            +
                  @thread = nil
         | 
| 12 | 
            +
                  @interval = 0.020
         | 
| 13 | 
            +
                  @mutex = Mutex.new
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def start
         | 
| 17 | 
            +
                  @running = true
         | 
| 18 | 
            +
                  @thread = Thread.new do
         | 
| 19 | 
            +
                    while @running
         | 
| 20 | 
            +
                      tick
         | 
| 21 | 
            +
                      sleep @interval
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                def tick
         | 
| 27 | 
            +
                  @mutex.synchronize do
         | 
| 28 | 
            +
                    packet do
         | 
| 29 | 
            +
                      command("REF", @ref_data)
         | 
| 30 | 
            +
                      command("PCMD", @pcmd_data)
         | 
| 31 | 
            +
                    end
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def stop
         | 
| 36 | 
            +
                  @running = false
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                def join
         | 
| 40 | 
            +
                  @thread.join if @thread
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def interval=(new_interval)
         | 
| 44 | 
            +
                  @mutex.synchronize do @interval = new_interval end
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                def ref(data)
         | 
| 48 | 
            +
                  @mutex.synchronize do @ref_data = data end
         | 
| 49 | 
            +
                end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                def pcmd(data)
         | 
| 52 | 
            +
                  @mutex.synchronize do @pcmd_data = data end
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                def config(key, value)
         | 
| 56 | 
            +
                  @buffer << "AT*CONFIG=605,\"#{key}\",\"#{value}\"\r"
         | 
| 57 | 
            +
                end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                private
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                def packet
         | 
| 62 | 
            +
                  yield self
         | 
| 63 | 
            +
                  flush
         | 
| 64 | 
            +
                end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                def flush
         | 
| 67 | 
            +
                  @sender.send_packet(@buffer)
         | 
| 68 | 
            +
                  @buffer = ""
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
             | 
| 71 | 
            +
                def command(name, args)
         | 
| 72 | 
            +
                  @seq += 1
         | 
| 73 | 
            +
                  @buffer << "AT*#{name}=#{@seq},#{args}\r"
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
            end
         | 
| @@ -0,0 +1,130 @@ | |
| 1 | 
            +
            module Argus
         | 
| 2 | 
            +
              class Controller
         | 
| 3 | 
            +
             | 
| 4 | 
            +
                def initialize(at_commander)
         | 
| 5 | 
            +
                  @at_commander = at_commander
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                  @emergency = false
         | 
| 8 | 
            +
                  land
         | 
| 9 | 
            +
                  hover
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def take_off
         | 
| 13 | 
            +
                  @flying = true
         | 
| 14 | 
            +
                  @emergency = false
         | 
| 15 | 
            +
                  update_ref
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                def land
         | 
| 19 | 
            +
                  @flying = false
         | 
| 20 | 
            +
                  update_ref
         | 
| 21 | 
            +
                end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                def emergency
         | 
| 24 | 
            +
                  @flying = false
         | 
| 25 | 
            +
                  @emergency = true
         | 
| 26 | 
            +
                  update_ref
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                def hover
         | 
| 30 | 
            +
                  @moving = false
         | 
| 31 | 
            +
                  @roll = 0.0
         | 
| 32 | 
            +
                  @pitch = 0.0
         | 
| 33 | 
            +
                  @gaz = 0.0
         | 
| 34 | 
            +
                  @yaw = 0.0
         | 
| 35 | 
            +
                  update_pcmd
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def forward(amount)
         | 
| 39 | 
            +
                  @moving = true
         | 
| 40 | 
            +
                  @pitch = -amount
         | 
| 41 | 
            +
                  update_pcmd
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def backward(amount)
         | 
| 45 | 
            +
                  @moving = true
         | 
| 46 | 
            +
                  @pitch = amount
         | 
| 47 | 
            +
                  update_pcmd
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def left(amount)
         | 
| 51 | 
            +
                  @moving = true
         | 
| 52 | 
            +
                  @roll = -amount
         | 
| 53 | 
            +
                  update_pcmd
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                def right(amount)
         | 
| 57 | 
            +
                  @moving = true
         | 
| 58 | 
            +
                  @roll = amount
         | 
| 59 | 
            +
                  update_pcmd
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def up(amount)
         | 
| 63 | 
            +
                  @moving = true
         | 
| 64 | 
            +
                  @gaz = amount
         | 
| 65 | 
            +
                  update_pcmd
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                def down(amount)
         | 
| 69 | 
            +
                  @moving = true
         | 
| 70 | 
            +
                  @gaz = -amount
         | 
| 71 | 
            +
                  update_pcmd
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                def turn_left(amount)
         | 
| 75 | 
            +
                  @moving = true
         | 
| 76 | 
            +
                  @yaw = -amount
         | 
| 77 | 
            +
                  update_pcmd
         | 
| 78 | 
            +
                end
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                def turn_right(amount)
         | 
| 81 | 
            +
                  @moving = true
         | 
| 82 | 
            +
                  @yaw = amount
         | 
| 83 | 
            +
                  update_pcmd
         | 
| 84 | 
            +
                end
         | 
| 85 | 
            +
             | 
| 86 | 
            +
                def front_camera
         | 
| 87 | 
            +
                  @at_commander.config("video:video_channel", "2")
         | 
| 88 | 
            +
                end
         | 
| 89 | 
            +
                
         | 
| 90 | 
            +
                def bottom_camera
         | 
| 91 | 
            +
                  @at_commander.config("video:video_channel", "1")
         | 
| 92 | 
            +
                end
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                private
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                REF_BASE = [18, 20, 22, 24, 28].
         | 
| 97 | 
            +
                  inject(0) { |flag, bitnum| flag | (1 << bitnum) }
         | 
| 98 | 
            +
                REF_FLY_BIT = (1 << 9)
         | 
| 99 | 
            +
                REF_EMERGENCY_BIT = (1 << 8)
         | 
| 100 | 
            +
             | 
| 101 | 
            +
                def update_ref
         | 
| 102 | 
            +
                  n = REF_BASE
         | 
| 103 | 
            +
                  n |= REF_FLY_BIT if @flying
         | 
| 104 | 
            +
                  n |= REF_EMERGENCY_BIT if @emergency
         | 
| 105 | 
            +
                  @at_commander.ref(n.to_s)
         | 
| 106 | 
            +
                  self
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                def update_pcmd
         | 
| 110 | 
            +
                  flags = 0
         | 
| 111 | 
            +
                  if @moving
         | 
| 112 | 
            +
                    flags = 1
         | 
| 113 | 
            +
                    iroll = encode_float(@roll)
         | 
| 114 | 
            +
                    ipitch = encode_float(@pitch)
         | 
| 115 | 
            +
                    igaz = encode_float(@gaz)
         | 
| 116 | 
            +
                    iyaw = encode_float(@yaw)
         | 
| 117 | 
            +
                    data = "#{flags},#{iroll},#{ipitch},#{igaz},#{iyaw}"
         | 
| 118 | 
            +
                  else
         | 
| 119 | 
            +
                    data = "0,0,0,0,0"
         | 
| 120 | 
            +
                  end
         | 
| 121 | 
            +
                  @at_commander.pcmd(data)
         | 
| 122 | 
            +
                  self
         | 
| 123 | 
            +
                end
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                def encode_float(float)
         | 
| 126 | 
            +
                  [float].pack('g').unpack("l>").first
         | 
| 127 | 
            +
                end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
              end
         | 
| 130 | 
            +
            end
         | 
    
        data/lib/argus/drone.rb
    ADDED
    
    | @@ -0,0 +1,36 @@ | |
| 1 | 
            +
            require 'socket'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Argus
         | 
| 4 | 
            +
              class Drone
         | 
| 5 | 
            +
                def initialize(socket=nil, host='192.168.1.1', port='5556')
         | 
| 6 | 
            +
                  @socket = socket || UDPSocket.new
         | 
| 7 | 
            +
                  @sender = Argus::UdpSender.new(@socket, host, port)
         | 
| 8 | 
            +
                  @at = Argus::ATCommander.new(@sender)
         | 
| 9 | 
            +
                  @controller = Argus::Controller.new(@at)
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                def start
         | 
| 13 | 
            +
                  @at.start
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def stop
         | 
| 17 | 
            +
                  @controller.land
         | 
| 18 | 
            +
                  @at.stop
         | 
| 19 | 
            +
                  @at.join
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                %w(
         | 
| 23 | 
            +
                   take_off land hover emergency
         | 
| 24 | 
            +
                   forward backward
         | 
| 25 | 
            +
                   left right
         | 
| 26 | 
            +
                   up down
         | 
| 27 | 
            +
                   turn_left turn_right
         | 
| 28 | 
            +
                   front_camera bottom_camera
         | 
| 29 | 
            +
                ).each do |meth|
         | 
| 30 | 
            +
                  define_method(meth) { |*args|
         | 
| 31 | 
            +
                    @controller.send(meth, *args)
         | 
| 32 | 
            +
                  }
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
            end
         | 
| @@ -0,0 +1,61 @@ | |
| 1 | 
            +
            module Argus
         | 
| 2 | 
            +
              class NavData
         | 
| 3 | 
            +
                attr_accessor :drone_state, :options, :vision_flag, :sequence_number
         | 
| 4 | 
            +
                def initialize(data)
         | 
| 5 | 
            +
                  @data = data
         | 
| 6 | 
            +
                  parse_nav_data(@data)
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                private 
         | 
| 10 | 
            +
                def parse_nav_data(data)
         | 
| 11 | 
            +
                  state = data.unpack("V").first
         | 
| 12 | 
            +
                  @drone_state = {
         | 
| 13 | 
            +
                    :flying=>state[0],  #/*!< FLY MASK : (0) ardrone is landed, (1) ardrone is flying */
         | 
| 14 | 
            +
                    :videoEnabled=>state[1],  #/*!< VIDEO MASK : (0) video disable, (1) video enable */
         | 
| 15 | 
            +
                    :visionEnabled=>state[2],  #/*!< VISION MASK : (0) vision disable, (1) vision enable */
         | 
| 16 | 
            +
                    :controlAlgorithm=>state[3],  #/*!< CONTROL ALGO : (0) euler angles control, (1) angular speed control */
         | 
| 17 | 
            +
                    :altitudeControlAlgorithm=>state[4],  #/*!< ALTITUDE CONTROL ALGO : (0) altitude control inactive (1) altitude control active */
         | 
| 18 | 
            +
                    :startButtonState=>state[5],  #/*!< USER feedback : Start button state */
         | 
| 19 | 
            +
                    :controlCommandAck=>state[6],  #/*!< Control command ACK : (0) None, (1) one received */
         | 
| 20 | 
            +
                    :cameraReady=>state[7],  #/*!< CAMERA MASK : (0) camera not ready, (1) Camera ready */
         | 
| 21 | 
            +
                    :travellingEnabled=>state[8],  #/*!< Travelling mask : (0) disable, (1) enable */
         | 
| 22 | 
            +
                    :usbReady=>state[9],  #/*!< USB key : (0) usb key not ready, (1) usb key ready */
         | 
| 23 | 
            +
                    :navdataDemo=>state[10], #/*!< Navdata demo : (0) All navdata, (1) only navdata demo */
         | 
| 24 | 
            +
                    :navdataBootstrap=>state[11], #/*!< Navdata bootstrap : (0) options sent in all or demo mode, (1) no navdata options sent */
         | 
| 25 | 
            +
                    :motorProblem=>state[12], #/*!< Motors status : (0) Ok, (1) Motors problem */
         | 
| 26 | 
            +
                    :communicationLost=>state[13], #/*!< Communication Lost : (1) com problem, (0) Com is ok */
         | 
| 27 | 
            +
                    :softwareFault=>state[14], #/*!< Software fault detected - user should land as quick as possible (1) */
         | 
| 28 | 
            +
                    :lowBattery=>state[15], #/*!< VBat low : (1) too low, (0) Ok */
         | 
| 29 | 
            +
                    :userEmergencyLanding=>state[16], #/*!< User Emergency Landing : (1) User EL is ON, (0) User EL is OFF*/
         | 
| 30 | 
            +
                    :timerElapsed=>state[17], #/*!< Timer elapsed : (1) elapsed, (0) not elapsed */
         | 
| 31 | 
            +
                    :MagnometerNeedsCalibration=>state[18], #/*!< Magnetometer calibration state : (0) Ok, no calibration needed, (1) not ok, calibration needed */
         | 
| 32 | 
            +
                    :anglesOutOfRange=>state[19], #/*!< Angles : (0) Ok, (1) out of range */
         | 
| 33 | 
            +
                    :tooMuchWind=>state[20], #/*!< WIND MASK: (0) ok, (1) Too much wind */
         | 
| 34 | 
            +
                    :ultrasonicSensorDeaf=>state[21], #/*!< Ultrasonic sensor : (0) Ok, (1) deaf */
         | 
| 35 | 
            +
                    :cutoutDetected=>state[22], #/*!< Cutout system detection : (0) Not detected, (1) detected */
         | 
| 36 | 
            +
                    :picVersionNumberOk=>state[23], #/*!< PIC Version number OK : (0) a bad version number, (1) version number is OK */
         | 
| 37 | 
            +
                    :atCodecThreadOn=>state[24], #/*!< ATCodec thread ON : (0) thread OFF (1) thread ON */
         | 
| 38 | 
            +
                    :navdataThreadOn=>state[25], #/*!< Navdata thread ON : (0) thread OFF (1) thread ON */
         | 
| 39 | 
            +
                    :videoThreadOn=>state[26], #/*!< Video thread ON : (0) thread OFF (1) thread ON */
         | 
| 40 | 
            +
                    :acquisitionThreadOn=>state[27], #/*!< Acquisition thread ON : (0) thread OFF (1) thread ON */
         | 
| 41 | 
            +
                    :controlWatchdogDelay=>state[28], #/*!< CTRL watchdog : (1) delay in control execution (> 5ms), (0) control is well scheduled */
         | 
| 42 | 
            +
                    :adcWatchdogDelay=>state[29], #/*!< ADC Watchdog : (1) delay in uart2 dsr (> 5ms), (0) uart2 is good */
         | 
| 43 | 
            +
                    :comWatchdogProblem=>state[30], #/*!< Communication Watchdog : (1) com problem, (0) Com is ok */
         | 
| 44 | 
            +
                    :emergencyLanding=>state[31]  #/*!< Emergency landing : (0) no emergency, (1) emergency */
         | 
| 45 | 
            +
                  }
         | 
| 46 | 
            +
                  data.slice!(0..3)
         | 
| 47 | 
            +
                  @sequence_number = data.unpack("V").first
         | 
| 48 | 
            +
                  data.slice!(0..3)
         | 
| 49 | 
            +
                  @vision_flag = data.unpack("V").first
         | 
| 50 | 
            +
                  @options = []
         | 
| 51 | 
            +
                  option1 = {}
         | 
| 52 | 
            +
                  data.slice!(0..3)
         | 
| 53 | 
            +
                  option1[:id] = data.unpack("v").first
         | 
| 54 | 
            +
                  data.slice!(0..1)
         | 
| 55 | 
            +
                  option1[:size] = data.unpack("v").first
         | 
| 56 | 
            +
                  data.slice!(0..1)
         | 
| 57 | 
            +
                  option1[:data] = data.unpack("V").first
         | 
| 58 | 
            +
                  @options.push(option1)
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            require 'socket'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module Argus
         | 
| 4 | 
            +
              class NavStreamer 
         | 
| 5 | 
            +
                def initialize(socket=nil, host='192.168.1.1', port='5554')
         | 
| 6 | 
            +
                  @socket = socket || UDPSocket.new
         | 
| 7 | 
            +
                  @streamer = Argus::UdpNavStreamer.new(@socket, host, port)
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def start
         | 
| 11 | 
            +
                  @streamer.start_stream
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def receive_data
         | 
| 15 | 
            +
                  @streamer.receive_packet
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,39 @@ | |
| 1 | 
            +
            module Argus
         | 
| 2 | 
            +
              class PaVEParser
         | 
| 3 | 
            +
                def initialize(tcp_video_streamer)
         | 
| 4 | 
            +
                  @tcp_video_streamer = tcp_video_streamer
         | 
| 5 | 
            +
                end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def get_frame
         | 
| 8 | 
            +
                  frame = {
         | 
| 9 | 
            +
                    :signature=>(@tcp_video_streamer.read(4)).unpack("A*").first,
         | 
| 10 | 
            +
                    :version=>(@tcp_video_streamer.read(1)).unpack("C").first,
         | 
| 11 | 
            +
                    :video_codec=>(@tcp_video_streamer.read(1)).unpack("C").first,
         | 
| 12 | 
            +
                    :header_size=>(@tcp_video_streamer.read(2)).unpack("v").first,
         | 
| 13 | 
            +
                    :payload_size=>(@tcp_video_streamer.read(4)).unpack("V").first,
         | 
| 14 | 
            +
                    :encoded_stream_width=>(@tcp_video_streamer.read(2)).unpack("v").first,
         | 
| 15 | 
            +
                    :encoded_stream_height=>(@tcp_video_streamer.read(2)).unpack("v").first,
         | 
| 16 | 
            +
                    :display_width=>(@tcp_video_streamer.read(2)).unpack("v").first,
         | 
| 17 | 
            +
                    :display_height=>(@tcp_video_streamer.read(2)).unpack("v").first,
         | 
| 18 | 
            +
                    :frame_number=>(@tcp_video_streamer.read(4)).unpack("V").first,
         | 
| 19 | 
            +
                    :timestamp=>(@tcp_video_streamer.read(4)).unpack("V").first,
         | 
| 20 | 
            +
                    :total_chunks=>(@tcp_video_streamer.read(1)).unpack("C").first,
         | 
| 21 | 
            +
                    :chunk_index=>(@tcp_video_streamer.read(1)).unpack("C").first,
         | 
| 22 | 
            +
                    :frame_type=>(@tcp_video_streamer.read(1)).unpack("C").first,
         | 
| 23 | 
            +
                    :control=>(@tcp_video_streamer.read(1)).unpack("C").first,
         | 
| 24 | 
            +
                    :stream_byte_position_lw=>(@tcp_video_streamer.read(4)).unpack("V").first,
         | 
| 25 | 
            +
                    :stream_byte_position_uw=>(@tcp_video_streamer.read(4)).unpack("V").first,
         | 
| 26 | 
            +
                    :stream_id=>(@tcp_video_streamer.read(2)).unpack("v").first,
         | 
| 27 | 
            +
                    :total_slices=>(@tcp_video_streamer.read(1)).unpack("C").first,
         | 
| 28 | 
            +
                    :slice_index=>(@tcp_video_streamer.read(1)).unpack("C").first,
         | 
| 29 | 
            +
                    :header1_size=>(@tcp_video_streamer.read(1)).unpack("C").first,
         | 
| 30 | 
            +
                    :header2_size=>(@tcp_video_streamer.read(1)).unpack("C").first,
         | 
| 31 | 
            +
                    :reserved[2]=>(@tcp_video_streamer.read(2)).unpack("H*").first,
         | 
| 32 | 
            +
                    :advertised_size=>(@tcp_video_streamer.read(4)).unpack("V").first,
         | 
| 33 | 
            +
                    :reserved[12]=>(@tcp_video_streamer.read(12)).unpack("H*").first
         | 
| 34 | 
            +
                  }
         | 
| 35 | 
            +
                  @tcp_video_streamer.read(4)
         | 
| 36 | 
            +
                  return @tcp_video_streamer.read(frame[:payload_size])
         | 
| 37 | 
            +
                end
         | 
| 38 | 
            +
              end
         | 
| 39 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            require 'socket'
         | 
| 2 | 
            +
            module Argus
         | 
| 3 | 
            +
              class TcpVideoStreamer
         | 
| 4 | 
            +
                def initialize(tcp_socket=nil, host='192.168.1.1', port=5555)
         | 
| 5 | 
            +
                  @tcp_socket = tcp_socket || TCPSocket.new(host, port)
         | 
| 6 | 
            +
                  @host = host
         | 
| 7 | 
            +
                  @port = port
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def start_stream(udp_socket=nil)
         | 
| 11 | 
            +
                  sock = udp_socket || UDPSocket.new
         | 
| 12 | 
            +
                  sock.send("\x01\x00\x00\x00", 0, @host, @port)
         | 
| 13 | 
            +
                  sock.close
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def read(n)
         | 
| 17 | 
            +
                  @tcp_socket.read(n)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -0,0 +1,24 @@ | |
| 1 | 
            +
            module Argus
         | 
| 2 | 
            +
              class UdpNavStreamer
         | 
| 3 | 
            +
                def initialize(udp_socket, host='192.168.1.1', port=5554)
         | 
| 4 | 
            +
                  @udp_socket = udp_socket
         | 
| 5 | 
            +
                  @host = host
         | 
| 6 | 
            +
                  @port = port
         | 
| 7 | 
            +
                  @udp_socket.bind("0.0.0.0", port)
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def start_stream
         | 
| 11 | 
            +
                  @udp_socket.send("\x01\x00\x00\x00", 0, @host, @port)
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                def receive_packet
         | 
| 15 | 
            +
                  data, from = @udp_socket.recvfrom(1024)
         | 
| 16 | 
            +
                  if data.unpack("V").first == "0x55667788".hex
         | 
| 17 | 
            +
                    data.slice!(0..3)
         | 
| 18 | 
            +
                    return NavData.new(data)
         | 
| 19 | 
            +
                  else
         | 
| 20 | 
            +
                    return "Not nav data!"
         | 
| 21 | 
            +
                  end
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
            end
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            module Argus
         | 
| 2 | 
            +
              class UdpSender
         | 
| 3 | 
            +
                def initialize(udp_socket, host=nil, port=nil)
         | 
| 4 | 
            +
                  @udp_socket = udp_socket
         | 
| 5 | 
            +
                  @host = host || '192.168.1.1'
         | 
| 6 | 
            +
                  @port = port || 5556
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def send_packet(data)
         | 
| 10 | 
            +
                  @udp_socket.send(data, 0, @host, @port)
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
            end
         | 
    
        data/lib/argus.rb
    ADDED
    
    | @@ -0,0 +1,9 @@ | |
| 1 | 
            +
            require 'argus/at_commander'
         | 
| 2 | 
            +
            require 'argus/controller'
         | 
| 3 | 
            +
            require 'argus/udp_sender'
         | 
| 4 | 
            +
            require 'argus/udp_nav_streamer'
         | 
| 5 | 
            +
            require 'argus/nav_data'
         | 
| 6 | 
            +
            require 'argus/nav_streamer'
         | 
| 7 | 
            +
            require 'argus/drone'
         | 
| 8 | 
            +
            require 'argus/tcp_video_streamer'
         | 
| 9 | 
            +
            require 'argus/pave_parser'
         | 
| @@ -0,0 +1,54 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Argus::ATCommander do
         | 
| 4 | 
            +
              Given(:sender) { flexmock(:on, Argus::UdpSender) }
         | 
| 5 | 
            +
              Given(:cmdr) { Argus::ATCommander.new(sender) }
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              describe "#tick" do
         | 
| 8 | 
            +
                When { cmdr.send(:tick) }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                context "with no commands" do
         | 
| 11 | 
            +
                  Then { sender.should have_received(:send_packet).with(/AT\*REF=\d+,0.*AT\*PCMD=\d+,0,0,0,0,0/) }
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                context "with a ref command" do
         | 
| 15 | 
            +
                  Given { cmdr.ref("512") }
         | 
| 16 | 
            +
                  Then { sender.should have_received(:send_packet).with(/AT\*REF=\d+,512.*AT\*PCMD=\d+,0,0,0,0,0/) }
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                context "with a pcmd command" do
         | 
| 20 | 
            +
                  Given { cmdr.pcmd("1,2,3,4,5") }
         | 
| 21 | 
            +
                  Then { sender.should have_received(:send_packet).with(/AT\*REF=\d+,0.*AT\*PCMD=\d+,1,2,3,4,5/) }
         | 
| 22 | 
            +
                end
         | 
| 23 | 
            +
              end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
              describe "increasing sequence numbers" do
         | 
| 26 | 
            +
                Given(:seq_numbers) { [ ] }
         | 
| 27 | 
            +
                Given {
         | 
| 28 | 
            +
                  sender.should_receive(:send_packet).and_return { |data|
         | 
| 29 | 
            +
                    data.scan(%r{=(\d+),}) { |seq|
         | 
| 30 | 
            +
                      seq_numbers << seq.first.to_i }
         | 
| 31 | 
            +
                  }
         | 
| 32 | 
            +
                }
         | 
| 33 | 
            +
                When { 2.times { cmdr.tick } }
         | 
| 34 | 
            +
                Then { seq_numbers.should == [1, 2, 3, 4] }
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
              describe "sending thread" do
         | 
| 38 | 
            +
                Given {
         | 
| 39 | 
            +
                  @count = 0
         | 
| 40 | 
            +
                  sender.should_receive(:send_packet).and_return {
         | 
| 41 | 
            +
                    @count += 1
         | 
| 42 | 
            +
                    cmdr.stop if @count > 5
         | 
| 43 | 
            +
                  }
         | 
| 44 | 
            +
                }
         | 
| 45 | 
            +
                When {
         | 
| 46 | 
            +
                  cmdr.start
         | 
| 47 | 
            +
                  cmdr.join
         | 
| 48 | 
            +
                }
         | 
| 49 | 
            +
                Then { sender.should have_received(:send_packet) }
         | 
| 50 | 
            +
              end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
             | 
| 53 | 
            +
             | 
| 54 | 
            +
            end
         | 
| @@ -0,0 +1,95 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Argus::Controller do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              REF_BASE = 0x11540000
         | 
| 6 | 
            +
             | 
| 7 | 
            +
              def ref_bits(n=nil)
         | 
| 8 | 
            +
                if n.nil?
         | 
| 9 | 
            +
                  REF_BASE.to_s
         | 
| 10 | 
            +
                else
         | 
| 11 | 
            +
                  (REF_BASE | (1 << n)).to_s
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              Given(:at) { flexmock(:on, Argus::ATCommander) }
         | 
| 16 | 
            +
              Given(:controller) { Argus::Controller.new(at) }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
              context "navigating commands" do
         | 
| 19 | 
            +
                Invariant { result.should == controller }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                context "when taking off" do
         | 
| 22 | 
            +
                  When(:result) { controller.take_off }
         | 
| 23 | 
            +
                  Then { at.should have_received(:ref).with(ref_bits(9)) }
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                context "when landing" do
         | 
| 27 | 
            +
                  When(:result) { controller.land }
         | 
| 28 | 
            +
                  Then { at.should have_received(:ref).with(ref_bits).twice }
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                context "when emergency" do
         | 
| 32 | 
            +
                  Given { controller.take_off }
         | 
| 33 | 
            +
                  When(:result) { controller.emergency }
         | 
| 34 | 
            +
                  Then { at.should have_received(:ref).with(ref_bits(8)) }
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                context "when taking off after an emergency" do
         | 
| 38 | 
            +
                  Given { controller.emergency }
         | 
| 39 | 
            +
                  When(:result) { controller.take_off }
         | 
| 40 | 
            +
                  Then { at.should have_received(:ref).with(ref_bits(9)) }
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                context "when hovering" do
         | 
| 44 | 
            +
                  When(:result) { controller.hover }
         | 
| 45 | 
            +
                  Then { at.should have_received(:pcmd).with("0,0,0,0,0") }
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                context "when moving forward" do
         | 
| 49 | 
            +
                  When(:result) { controller.forward(0.80) }
         | 
| 50 | 
            +
                  Then { at.should have_received(:pcmd).with("1,0,-1085485875,0,0") }
         | 
| 51 | 
            +
                end
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                context "when moving backward" do
         | 
| 54 | 
            +
                  When(:result) { controller.backward(0.80) }
         | 
| 55 | 
            +
                  Then { at.should have_received(:pcmd).with("1,0,1061997773,0,0") }
         | 
| 56 | 
            +
                end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                context "when moving to the left" do
         | 
| 59 | 
            +
                  When(:result) { controller.left(0.80) }
         | 
| 60 | 
            +
                  Then { at.should have_received(:pcmd).with("1,-1085485875,0,0,0") }
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
                context "when moving to the right" do
         | 
| 64 | 
            +
                  When(:result) { controller.right(0.80) }
         | 
| 65 | 
            +
                  Then { at.should have_received(:pcmd).with("1,1061997773,0,0,0") }
         | 
| 66 | 
            +
                end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                context "when moving up" do
         | 
| 69 | 
            +
                  When(:result) { controller.up(0.80) }
         | 
| 70 | 
            +
                  Then { at.should have_received(:pcmd).with("1,0,0,1061997773,0") }
         | 
| 71 | 
            +
                end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                context "when moving down" do
         | 
| 74 | 
            +
                  When(:result) { controller.down(0.80) }
         | 
| 75 | 
            +
                  Then { at.should have_received(:pcmd).with("1,0,0,-1085485875,0") }
         | 
| 76 | 
            +
                end
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                context "when turning left" do
         | 
| 79 | 
            +
                  When(:result) { controller.turn_left(0.80) }
         | 
| 80 | 
            +
                  Then { at.should have_received(:pcmd).with("1,0,0,0,-1085485875") }
         | 
| 81 | 
            +
                end
         | 
| 82 | 
            +
             | 
| 83 | 
            +
                context "when turning right" do
         | 
| 84 | 
            +
                  When(:result) { controller.turn_right(0.80) }
         | 
| 85 | 
            +
                  Then { at.should have_received(:pcmd).with("1,0,0,0,1061997773") }
         | 
| 86 | 
            +
                end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                context "when executing several directions" do
         | 
| 89 | 
            +
                  When(:result) {
         | 
| 90 | 
            +
                    controller.forward(1.0).left(0.5).up(0.2).turn_left(0.8)
         | 
| 91 | 
            +
                  }
         | 
| 92 | 
            +
                  Then { at.should have_received(:pcmd).with("1,-1090519040,-1082130432,1045220557,-1085485875") }
         | 
| 93 | 
            +
                end
         | 
| 94 | 
            +
              end
         | 
| 95 | 
            +
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'socket'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Argus::UdpSender do
         | 
| 5 | 
            +
              Given(:port) { 5500 }
         | 
| 6 | 
            +
              Given(:socket) { UDPSocket.new }
         | 
| 7 | 
            +
              Given(:sender) { Argus::UdpSender.new(socket, 'localhost', port) }
         | 
| 8 | 
            +
              Given!(:server) {
         | 
| 9 | 
            +
                server = UDPSocket.new
         | 
| 10 | 
            +
                server.bind(nil, port)
         | 
| 11 | 
            +
                Thread.new {
         | 
| 12 | 
            +
                  @data, _ = server.recvfrom(1000)
         | 
| 13 | 
            +
                }
         | 
| 14 | 
            +
              }
         | 
| 15 | 
            +
              When {
         | 
| 16 | 
            +
                sender.send_packet("HI")
         | 
| 17 | 
            +
                server.join
         | 
| 18 | 
            +
              }
         | 
| 19 | 
            +
              Then { @data.should == "HI" }
         | 
| 20 | 
            +
            end
         | 
    
        data/spec/spec_helper.rb
    ADDED
    
    
    
        metadata
    ADDED
    
    | @@ -0,0 +1,91 @@ | |
| 1 | 
            +
            --- !ruby/object:Gem::Specification
         | 
| 2 | 
            +
            name: hybridgroup-argus
         | 
| 3 | 
            +
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            +
              version: 0.2.0
         | 
| 5 | 
            +
              prerelease: 
         | 
| 6 | 
            +
            platform: ruby
         | 
| 7 | 
            +
            authors:
         | 
| 8 | 
            +
            - Jim Weirich
         | 
| 9 | 
            +
            - Ron Evans
         | 
| 10 | 
            +
            - Adrian Zankich
         | 
| 11 | 
            +
            autorequire: 
         | 
| 12 | 
            +
            bindir: bin
         | 
| 13 | 
            +
            cert_chain: []
         | 
| 14 | 
            +
            date: 2013-02-23 00:00:00.000000000 Z
         | 
| 15 | 
            +
            dependencies:
         | 
| 16 | 
            +
            - !ruby/object:Gem::Dependency
         | 
| 17 | 
            +
              name: rspec-given
         | 
| 18 | 
            +
              requirement: !ruby/object:Gem::Requirement
         | 
| 19 | 
            +
                none: false
         | 
| 20 | 
            +
                requirements:
         | 
| 21 | 
            +
                - - ~>
         | 
| 22 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 23 | 
            +
                    version: '2.1'
         | 
| 24 | 
            +
              type: :development
         | 
| 25 | 
            +
              prerelease: false
         | 
| 26 | 
            +
              version_requirements: !ruby/object:Gem::Requirement
         | 
| 27 | 
            +
                none: false
         | 
| 28 | 
            +
                requirements:
         | 
| 29 | 
            +
                - - ~>
         | 
| 30 | 
            +
                  - !ruby/object:Gem::Version
         | 
| 31 | 
            +
                    version: '2.1'
         | 
| 32 | 
            +
            description: Argus is a Ruby interface to a Parrot AR Drone quadcopter.Argus is extremely
         | 
| 33 | 
            +
              experimental at this point.  Use at your own risk.
         | 
| 34 | 
            +
            email:
         | 
| 35 | 
            +
            - jim.weirich@gmail.com
         | 
| 36 | 
            +
            - ron dot evans at gmail dot com
         | 
| 37 | 
            +
            executables: []
         | 
| 38 | 
            +
            extensions: []
         | 
| 39 | 
            +
            extra_rdoc_files:
         | 
| 40 | 
            +
            - README.md
         | 
| 41 | 
            +
            - MIT-LICENSE
         | 
| 42 | 
            +
            files:
         | 
| 43 | 
            +
            - README.md
         | 
| 44 | 
            +
            - Rakefile
         | 
| 45 | 
            +
            - lib/argus.rb
         | 
| 46 | 
            +
            - lib/argus/at_commander.rb
         | 
| 47 | 
            +
            - lib/argus/controller.rb
         | 
| 48 | 
            +
            - lib/argus/drone.rb
         | 
| 49 | 
            +
            - lib/argus/nav_data.rb
         | 
| 50 | 
            +
            - lib/argus/nav_streamer.rb
         | 
| 51 | 
            +
            - lib/argus/pave_parser.rb
         | 
| 52 | 
            +
            - lib/argus/tcp_video_streamer.rb
         | 
| 53 | 
            +
            - lib/argus/udp_nav_streamer.rb
         | 
| 54 | 
            +
            - lib/argus/udp_sender.rb
         | 
| 55 | 
            +
            - lib/argus/version.rb
         | 
| 56 | 
            +
            - spec/spec_helper.rb
         | 
| 57 | 
            +
            - spec/argus/at_commander_spec.rb
         | 
| 58 | 
            +
            - spec/argus/controller_spec.rb
         | 
| 59 | 
            +
            - spec/argus/udp_sender_spec.rb
         | 
| 60 | 
            +
            - MIT-LICENSE
         | 
| 61 | 
            +
            homepage: http://github.com/hybridgroup/argus
         | 
| 62 | 
            +
            licenses: []
         | 
| 63 | 
            +
            post_install_message: 
         | 
| 64 | 
            +
            rdoc_options:
         | 
| 65 | 
            +
            - --line-numbers
         | 
| 66 | 
            +
            - --show-hash
         | 
| 67 | 
            +
            - --main
         | 
| 68 | 
            +
            - README.md
         | 
| 69 | 
            +
            - --title
         | 
| 70 | 
            +
            - Argus -- Parrot AR Drone Ruby API
         | 
| 71 | 
            +
            require_paths:
         | 
| 72 | 
            +
            - lib
         | 
| 73 | 
            +
            required_ruby_version: !ruby/object:Gem::Requirement
         | 
| 74 | 
            +
              none: false
         | 
| 75 | 
            +
              requirements:
         | 
| 76 | 
            +
              - - ! '>='
         | 
| 77 | 
            +
                - !ruby/object:Gem::Version
         | 
| 78 | 
            +
                  version: '1.9'
         | 
| 79 | 
            +
            required_rubygems_version: !ruby/object:Gem::Requirement
         | 
| 80 | 
            +
              none: false
         | 
| 81 | 
            +
              requirements:
         | 
| 82 | 
            +
              - - ! '>='
         | 
| 83 | 
            +
                - !ruby/object:Gem::Version
         | 
| 84 | 
            +
                  version: '1.0'
         | 
| 85 | 
            +
            requirements: []
         | 
| 86 | 
            +
            rubyforge_project: n/a
         | 
| 87 | 
            +
            rubygems_version: 1.8.23
         | 
| 88 | 
            +
            signing_key: 
         | 
| 89 | 
            +
            specification_version: 3
         | 
| 90 | 
            +
            summary: Ruby API for a Parrot AD Drone Quadcopter
         | 
| 91 | 
            +
            test_files: []
         |