├── .ruby-version ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── scratchrsc.rb ├── README.md └── scratch2sphero.rb /.ruby-version: -------------------------------------------------------------------------------- 1 | ruby-1.9.3 2 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gem 'hybridgroup-serialport' 4 | gem 'sphero' 5 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | hybridgroup-serialport (1.2.1) 5 | sphero (1.4.1) 6 | 7 | PLATFORMS 8 | ruby 9 | 10 | DEPENDENCIES 11 | hybridgroup-serialport 12 | sphero 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013 Junya Ishihara https://github.com/champierre 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /scratchrsc.rb: -------------------------------------------------------------------------------- 1 | # 2 | # scratchrsc.rb is based on the one file library(http://pastebin.com/XCqPeeyW) 3 | # created by Scratcher logiblocs(http://scratch.mit.edu/users/logiblocs/). 4 | # 5 | # it uses the Remote Sensor Connections protocol (http://wiki.scratch.mit.edu/wiki/Remote_Sensors_Protocol) to talk to Scratch over sockets 6 | 7 | require "socket" 8 | require "scanf" 9 | 10 | class RSCWatcher 11 | def initialize(host="127.0.0.1") 12 | puts "initialize" 13 | @socket = TCPSocket.open(host, 42001) 14 | onConnect 15 | end 16 | 17 | def sendCommand(cmd) 18 | i = cmd.length 19 | @socket.write [i].pack("I").reverse + cmd 20 | end 21 | 22 | def socket 23 | @socket 24 | end 25 | 26 | def handle_command 27 | message = @socket.recv(@socket.recv(4).reverse.unpack("I")[0]) 28 | if message.include? "sensor-update" 29 | split = message.split 30 | a = "" 31 | split.length.times do |i| 32 | unless i == 0 33 | if i % 2 == 1 34 | a = split[i] 35 | end 36 | if i % 2 == 0 37 | on_sensor_update a.gsub(/"/, ''), split[i].gsub(/"/, '') 38 | end 39 | end 40 | end 41 | end 42 | 43 | if message.include? "broadcast" 44 | puts message 45 | split = message.split 46 | if split.length == 2 47 | __on_broadcast split[1].gsub(/"/, '') 48 | end 49 | end 50 | end 51 | 52 | def __on_broadcast(name) 53 | method = "broadcast_#{name}" 54 | if self.respond_to? method 55 | self.send method 56 | else 57 | on_broadcast(name) 58 | end 59 | end 60 | 61 | def on_broadcast(name) 62 | end 63 | 64 | def on_sensor_update(name, value) 65 | end 66 | 67 | def broadcast(name) 68 | sendCommand("broadcast \"#{name}\"") 69 | end 70 | 71 | def sensor_update(name,value) 72 | sendCommand("sensor-update \"#{name}\" \"#{value}\"") 73 | end 74 | 75 | private 76 | def onConnect 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Scratch2Sphero 2 | 3 | With Scratch2Sphero, you can control [Sphero 2.0](http://www.gosphero.com/) from [Scratch 1.4](http://scratch.mit.edu). Tested on MacOS X(10.8.5, 10.9, and 10.9.1). 4 | 5 | ## Requirements 6 | 7 | - [Sphero](http://www.gosphero.com/) - This is the physical robot ball that you'll be controlling 8 | - [Scratch 1.4](http://scratch.mit.edu/scratch_1.4/) - Programing environment 9 | - [hybridgroup-serialport](https://github.com/hybridgroup/ruby-serialport/) - Used by the sphero gem to talk bluetooth 10 | - [sphero gem](https://github.com/hybridgroup/sphero/) - Used by the script to talk to the sphero api 11 | 12 | ## Installation 13 | 14 | ``` 15 | % git clone https://github.com/champierre/scratch2sphero.git 16 | % bundle install 17 | ``` 18 | 19 | or 20 | 21 | ``` 22 | % git clone https://github.com/champierre/scratch2sphero.git 23 | % ('sudo' if necessary) xcode-select --install # optional step depending on the state of your development tools. if you get errors building the gems, try this 24 | % ('sudo' if necessary) gem install hybridgroup-serialport 25 | % ('sudo' if necessary) gem install sphero 26 | ``` 27 | 28 | If you cannot use git, you can download the source code from "Download ZIP" button appeared on the right side of the README page. 29 | 30 | ## Getting Started 31 | 32 | ### Getting Scratch and Sphero to talk to each other on your Mac 33 | 34 | 1. Pair your computer with your Sphero. Do this via the [Bluetooth](https://www.apple.com/support/bluetooth/) pane in settings. Remember, it can only be paired to one device at a time, so turn off bluetooth on any other devices you might be using. 35 | 2. On Scratch, right-click on the () Sensor Value block, found in the Sensing category, and 36 | select the ["Enable remote sensor connections"](http://wiki.scratch.mit.edu/wiki/Remote_Sensors_Protocol) option. 37 | 3. On the terminal, run scratch2sphero.rb. You can then monitor this terminal window for any debug information. 38 | 39 | ### Movement 40 | 41 | 1. Use Scratch variable "steps" and Broadcast "move" to make Sphero roll. The number set for "steps" is the duration that Sphero keeps moving. 42 | 2. Use Scratch variable "degrees" and Broadcast "turn" to make Sphero turn. The number set for "degrees" is the degree that Sphero turns. 43 | 3. (Alternative) Use Broadcast backward/forward/left/right block, found in the Control category, to make Sphero roll to the direction specified. 44 | 4. You can use the following Scratch variables to change the behavior of Sphero. 45 | 46 | - speed(default: 20) - roll speed 47 | - initial_heading(default: 0) - initial heading in degree 48 | 49 | ### LED Control 50 | 51 | #### Back LED 52 | 53 | To help your users know which direction the ball is facing, you might want to toggle the back LED 54 | 55 | - Use the "back_led_on" broadcast to turn on the little light to tell you were the back of the ball is 56 | - Use the "back_led_off" broadcast to turn the light off 57 | 58 | #### Color 59 | 60 | Who doesn't like rainbows? Especially blinking double rainbows. You can control the main LED color of Sphero via the color broadcast. 61 | 62 | - create a global variable named color_name and assign it a [color name](http://www.w3schools.com/html/html_colornames.asp), then broadcast "color". 63 | - you can also define new broadcasts with color_name where name is one of the valid color names, e.g. broadcast "color_cadetblue" or broadcast "color_crimson" 64 | - if you send it an invalid, or blank, color name, it will restore to the saved default color for your Sphero 65 | 66 | ## Demo Scripts 67 | 68 | ### Sphero draws square 69 | 70 | ![Sphero draws square](https://dl.dropboxusercontent.com/u/385564/scratch2sphero/sphero_square.png) 71 | 72 | ### Sphero makes a rainbow 73 | 74 | ![Sphero Colors](https://dl.dropboxusercontent.com/s/aghyq2h02mt8vnp/sphero_colors_screenshot.png) 75 | 76 | ## Demo Movies 77 | 78 | ### [Scratch x Sphero](https://www.youtube.com/watch?v=aHL03UHULm0) 79 | 80 | [![Scratch x Sphero](http://img.youtube.com/vi/aHL03UHULm0/0.jpg)](https://www.youtube.com/watch?v=aHL03UHULm0) 81 | 82 | ### [Sphero 2.0 controlled by Scratch(Japanese + English caption)](https://www.youtube.com/watch?v=qCeJ6_UKnk4) 83 | 84 | [![Sphero 2.0 controlled by Scratch(Japanese + English caption)](http://img.youtube.com/vi/qCeJ6_UKnk4/0.jpg)](https://www.youtube.com/watch?v=qCeJ6_UKnk4) 85 | 86 | ### [Sphero colors via Scratch](https://www.youtube.com/watch?v=UoYA4e8f9Ns) 87 | 88 | [![Sphero colors via Scratch](http://img.youtube.com/vi/UoYA4e8f9Ns/0.jpg)](https://www.youtube.com/watch?v=UoYA4e8f9Ns) 89 | 90 | ## Additional Resources 91 | 92 | - [Discuss scratch2sphero on the Scratch Forums](http://scratch.mit.edu/discuss/topic/21808/) 93 | - [Scratch's Remote Sensor Control Protocol](http://wiki.scratch.mit.edu/wiki/Remote_Sensors_Protocol) 94 | - [Sphero API](http://orbotixinc.github.io/Sphero-Docs/docs/sphero-api/) 95 | - [Color Names](http://www.w3schools.com/html/html_colornames.asp) 96 | -------------------------------------------------------------------------------- /scratch2sphero.rb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative "scratchrsc" 3 | require "sphero" 4 | class PrintRSC < RSCWatcher 5 | 6 | def initialize 7 | super 8 | 9 | sphero_tty = Dir.glob("/dev/tty.Sphero*").first 10 | @sphero = Sphero.new sphero_tty 11 | @sphero.stop 12 | @speed = 20 13 | @initial_heading = 0 14 | @current_heading = 0 15 | @interval = 2 16 | @steps = 10 17 | @degrees = 15 18 | @color_name = '' 19 | 20 | broadcast "move" 21 | broadcast "move_10" 22 | 23 | broadcast "turn" 24 | broadcast "turn_90" 25 | 26 | broadcast "forward" 27 | broadcast "right" 28 | broadcast "left" 29 | broadcast "backward" 30 | 31 | broadcast "color" 32 | 33 | broadcast "back_led_on" 34 | broadcast "back_led_off" 35 | 36 | bluetooth_info = @sphero.bluetooth_info 37 | @sphero_name = bluetooth_info.body.pack('C'*16).strip unless bluetooth_info.nil? 38 | # there seems to be some timing issue where the bluetooth_info isn't already ready in time when I'm asking for it so this is a quick hack to avoid an error 39 | # I'm also not sure what it'll do with extended character sets 40 | @sphero_name = "-->unknown<--" if @sphero_name.nil? 41 | # this needs more work... when it is unknown we don't seem to have communication with the ball -- somehow I think it is preventing the "Resource Busy" exception? 42 | puts "Connected to #{@sphero_name}" 43 | end 44 | 45 | def on_sensor_update(name, value) # when a variable or sensor is updated 46 | value = value.to_i if %w(speed initial_heading steps degrees).include? name 47 | if name == "speed" 48 | @speed = value 49 | elsif name == "initial_heading" 50 | @initial_heading = value 51 | elsif name == "steps" 52 | @steps = value 53 | elsif name == "degrees" 54 | @degrees = value 55 | elsif name == "color_name" 56 | @color_name = value 57 | end 58 | puts "#{@sphero_name} -- #{name} assigned #{value}" 59 | puts "#{@sphero_name} -- current_heading: #{@current_heading}, absolute_heading: #{@initial_heading + @current_heading}" 60 | end 61 | 62 | def broadcast_move 63 | _roll(@speed, @initial_heading + @current_heading, @steps) 64 | end 65 | 66 | def broadcast_turn 67 | puts "#{@sphero_name} -- turn #{@degrees}" 68 | @current_heading += @degrees 69 | end 70 | 71 | def broadcast_right 72 | heading = @initial_heading + 90 73 | _roll(@speed, heading) 74 | end 75 | 76 | def broadcast_left 77 | heading = @initial_heading + 270 78 | _roll(@speed, heading) 79 | end 80 | 81 | def broadcast_forward 82 | heading = @initial_heading 83 | _roll(@speed, heading) 84 | end 85 | 86 | def broadcast_backward 87 | heading = @initial_heading + 180 88 | _roll(@speed, heading) 89 | end 90 | 91 | def broadcast_color 92 | color 93 | end 94 | 95 | def broadcast_back_led_on 96 | back_led_on 97 | end 98 | 99 | def broadcast_back_led_off 100 | back_led_off 101 | end 102 | 103 | def on_broadcast(name) 104 | (action, argument) = name.split('_') 105 | case action 106 | when "move" 107 | steps = argument.to_i 108 | _roll(@speed, @initial_heading + @current_heading, steps) 109 | when "turn" 110 | heading = argument.to_i 111 | puts "#{@sphero_name} -- turn #{heading}" 112 | @current_heading += heading 113 | when "color" 114 | color(argument) 115 | end 116 | end 117 | 118 | private 119 | def _roll(speed, heading, steps = 10) 120 | sleep_time = steps / 10.0 121 | 122 | if heading < 0 123 | heading = heading % 360 + 360 124 | elsif heading > 359 125 | heading = heading % 360 126 | end 127 | 128 | puts "#{@sphero_name} -- roll #{@speed}, #{heading}" 129 | @sphero.roll(speed, heading) 130 | sleep sleep_time 131 | @sphero.stop 132 | sleep 1 133 | end 134 | 135 | # sets the color of the sphero, via the @color_name value 136 | # valid color_names can be found in the COLORS defintion in the sphero gem, which coincide with the 140 named colors CSS uses 137 | # sending a blank color_name will reset the sphero to the user led color, which is stored in the balls memory 138 | def color(color_name=@color_name) 139 | if color_name.empty? 140 | user_led_color 141 | else 142 | begin 143 | puts "#{@sphero_name} -- color #{color_name}" 144 | @sphero.color color_name.downcase 145 | rescue => e 146 | puts "#{@sphero_name} -- unable to set color_name #{color_name}. #{e.message}" 147 | end 148 | end 149 | end 150 | 151 | # reset the color of the sphero to the persistent user selected colour 152 | def user_led_color() 153 | puts "#{@sphero_name} -- Reseting color to user_led" 154 | user_led = @sphero.user_led 155 | @sphero.rgb user_led.body[0], user_led.body[1], user_led.body[2] 156 | end 157 | 158 | # turns on the back LED so you know which way is forward. 159 | # it does accept a range between 0x00-0xFF (0-255) but I don't see a need for that atm, just a simple on/off is enough 160 | def back_led_on() 161 | puts "#{@sphero_name} -- back LED on" 162 | @sphero.back_led_output=0xFF 163 | end 164 | 165 | # turns the back LED off 166 | def back_led_off() 167 | puts "#{@sphero_name} -- back LED off" 168 | @sphero.back_led_output=0x00 # turns off the back led 169 | end 170 | 171 | end 172 | 173 | begin 174 | watcher = PrintRSC.new # you can provide the host as an argument 175 | watcher.sensor_update "connected", "1" 176 | loop { watcher.handle_command } 177 | rescue Errno::ECONNREFUSED 178 | puts "\033[31m\033[1mError: Scratch may not be running or remote sensor connections are not enabled.\033[00m\n" 179 | rescue => e 180 | puts "\033[31m\033[1mError: #{e.message}\033[00m\n" 181 | end 182 | --------------------------------------------------------------------------------