From 0c2db2ea4245e00dc16d5269a86bd551c29b7004 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Sun, 14 Oct 2012 17:25:54 +0900 Subject: [PATCH 01/20] add a simulation high level object and make serialsimulation not inherit from roomba --- README.md | 3 +- lib/byte_processing.rb | 13 +++ lib/calculations.rb | 12 ++ lib/roomba.rb | 27 +---- lib/roomba_serial_simulation.rb | 190 +++++++++++++++----------------- lib/roomba_simulation.rb | 11 ++ lib/simulator.rb | 38 +++++++ lib/world.rb | 16 ++- 8 files changed, 184 insertions(+), 126 deletions(-) create mode 100644 lib/byte_processing.rb create mode 100644 lib/calculations.rb create mode 100644 lib/simulator.rb diff --git a/README.md b/README.md index 5ce4f3d..4a1996a 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,8 @@ Jump in console and give the basic simulation a shot. ````ruby $ rails c -:001 > earth = World.new +:001 > simulator = Simulator.new +:001 > earth = simulator.world :002 > roo = RoombaSimulation.new :003 > earth.spawn(roo) :004 > roo.move(100) diff --git a/lib/byte_processing.rb b/lib/byte_processing.rb new file mode 100644 index 0000000..48f63ee --- /dev/null +++ b/lib/byte_processing.rb @@ -0,0 +1,13 @@ +module ByteProcessing + + def signed_integer(bytes) + case bytes.size + when 1 + return (bytes[0] & ~(1 << 7)) - (bytes[0] & (1 << 7)) + when 2 + sixteenbit = bytes[0] << 8 | bytes[1] + return (sixteenbit & ~(1 << 15)) - (sixteenbit & (1 << 15))#http://en.wikipedia.org/wiki/Two%27s_complement#Calculating_two.27s_complement + end + end + +end diff --git a/lib/calculations.rb b/lib/calculations.rb new file mode 100644 index 0000000..5837972 --- /dev/null +++ b/lib/calculations.rb @@ -0,0 +1,12 @@ +module Calculations + def calculate_spin_time(velocity, degree) + # time = wheelbase * PI / 360degrees * degrees / velocity ABS + # wheelbase might be different for different roombas, consider refactoring + ((((Roomba::ROOMBA_WHEELBASE * Math::PI) / 360) * degree.abs).to_f / velocity.to_f).abs + end + + #spinning needs some work + def calculate_spin_degree(velocity, time) + ((time.to_f * velocity.to_f) / ((Roomba::ROOMBA_WHEELBASE * Math::PI) / 360)) #/ 10**10 + end +end diff --git a/lib/roomba.rb b/lib/roomba.rb index 24c1507..dcd457d 100644 --- a/lib/roomba.rb +++ b/lib/roomba.rb @@ -73,6 +73,8 @@ class Roomba :saturday => '01000000' } + include ByteProcessing + include Calculations def initialize(port, latency=0.1, baud=115200, serial=nil) # baud must be 115200 for communicating with 500 series Roomba and newer (tested with Roomba 770), change to 57600 for 400 series and older @@ -119,9 +121,9 @@ def move(distance, degree=0, velocity=200) set_velocity(velocity) set_degree(degree) api_drive(@velocity_high, @velocity_low, @radius_high, @radius_low) - start_moving = Time.now time_in_seconds = 10 if time_in_seconds > 10 - until (start_moving - Time.now).abs >= time_in_seconds + start_moving = current_time + until (start_moving - current_time).abs >= time_in_seconds # sensors call sleeps the script for 20ms, max read is 50ms, total time between loops about 65ms sensors = get_readings(:bumps_and_drops, :wall) @messages.push sensors @@ -131,15 +133,8 @@ def move(distance, degree=0, velocity=200) sensors end - def calculate_spin_time(velocity, degree) - # time = wheelbase * PI / 360degrees * degrees / velocity ABS - # wheelbase might be different for different roombas, consider refactoring - ((((ROOMBA_WHEELBASE * Math::PI) / 360) * degree.abs).to_f / velocity.to_f).abs - end - - #spinning needs some work - def calculate_spin_degree(velocity, time) - ((time.to_f * velocity.to_f) / ((ROOMBA_WHEELBASE * Math::PI) / 360)) / 10**10 + def current_time + Time.now end def set_degree(degree) @@ -243,16 +238,6 @@ def set_readings(sensor, readings) end end - def signed_integer(bytes) - case bytes.size - when 1 - return (bytes[0] & ~(1 << 7)) - (bytes[0] & (1 << 7)) - when 2 - sixteenbit = bytes[0] << 8 | bytes[1] - return (sixteenbit & ~(1 << 15)) - (sixteenbit & (1 << 15))#http://en.wikipedia.org/wiki/Two%27s_complement#Calculating_two.27s_complement - end - end - def motors api_motors(1) sleep 2 diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index 95bd35a..f77f5b7 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -1,6 +1,9 @@ -class RoombaSerialSimulation < Roomba - attr_accessor :simulation, :requested_readings, :readings, :x, :y, :facing, :moving, :velocity, :turning, :degree, :timestamp, :world - ROOMBA_RADIUS = 176 +class RoombaSerialSimulation + attr_accessor :simulation, :requested_readings, :readings, :facing, :moving, :velocity, :turning, :degree, :world + attr_writer :x, :y + + include ByteProcessing + include Calculations # currently the simulation settings are hardcoded in the initializer # need to refactor to allow various predefined or even random simulations @@ -23,6 +26,14 @@ def initialize self end + #components outside here can not see our internal floating point representation + def x + @x.round + end + + def y + @y.round + end # When the RoombaSimulation class tries to send data to the simulated Roomba # this is where that data arrives. Check the first byte to get the opcode @@ -33,14 +44,67 @@ def write(*bytes) command = bytes.shift case command when 137 - move(*bytes) + setup_move(*bytes) when 149 prepare_readings(*bytes) end end + def moving? + return @moving + end + + def radius + Roomba::ROOMBA_RADIUS + end + + def born_in(world) + @world = world + end + + def render + ui = {} + ui['x'] = x + ui['y'] = y + ui['radius'] = radius + ui['name'] = 'Roomba' + ui + end + + def step(step_time) + if !@turning + distance = (@velocity * step_time).to_i + puts "Travelled #{distance}mm" + @previous_x = @x + @previous_y = @y + move_to(@facing, distance) + puts "N: #@facing, x: #@x, y: #@y" + else + @facing = @facing + calculate_spin_degree(@velocity, step_time) + puts "N:#@facing" + end + end + + def step_back + @x = @previous_x + @y = @previous_y + end + + # When the RoombaSimulation requests a byte from + # the simulated serial port, it gets shifted off the array of available bytes. + def getbyte + readings.shift + end + + def read_timeout=(timeout) + end + + + + private + # Sets the state of simulated Roomba to moving - def move(*args) + def setup_move(*args) # update x, y; check if any obstacle coordinates fall inside roomba's radius; # queue sensor readings in some array to simulate TX/RX @velocity = signed_integer([args[0], args[1]]) @@ -50,46 +114,40 @@ def move(*args) else puts "Stopped moving" end - @timestamp = Time.now @degree = signed_integer([args[2], args[3]]) @turning = (@degree.abs == 1) ? true : false # Simulation can currently only handle the following (cannot support half-points or curves) - if @facing > 45 && @facing < 135 + if @facing.between?(45, 135) @facing = 90 - elsif @facing >= 135 && @facing < 225 + elsif @facing.between?(135, 225) @facing = 180 - elsif @facing >= 225 && @facing < 315 + elsif @facing.between?(225, 315) @facing = 270 else @facing = 0 end + if @velocity < 0 && @fading != 0 + @facing = 360 - @facing + end return true end - def moving? - return @moving - end - - def radius - ROOMBA_RADIUS - end - - def born_in(world) - @world = world - end - - def render - ui = {} - ui['x'] = x - ui['y'] = y - ui['radius'] = radius - ui['name'] = 'Roomba' - ui + def move_to(direction, distance) + case direction + when 0 + @y += distance + when 90 + @x += distance + when 180 + @y -= distance + when 270 + @x -= distance + end end def prepare_readings(*args) args.each do |request| - SENSORS.each do |sensor| + Roomba::SENSORS.each do |sensor| if sensor[1][:packet] == request if respond_to? "prepare_reading_#{request}".to_sym send("prepare_reading_#{request}".to_sym) @@ -106,78 +164,8 @@ def prepare_readings(*args) # Roomba's state, so that other sensors that also check Roomba's immediate # environment can leverage the same current X,Y coordinates def prepare_reading_7 - start_x = @x - start_y = @y - previous_x = 0 - previous_y = 0 - reading = 0 #value of bump_and_drops sensors - latest_check_time = Time.now - difference = latest_check_time - @timestamp #difference from last reading - puts @turning.inspect - if !@turning - puts "Time diff: #{difference}" - distance = (@velocity * difference).to_i - puts "Travelled #{distance}mm" - if distance > 0 #driving forward - 1.upto(distance) do |x| - previous_x = @x - previous_y = @y - case @facing - when 0 - @y = @y+1 - when 90 - @x = @x+1 - when 180 - @y = @y-1 - when 270 - @x = @x-1 - end - puts "N:#{@facing}, X:#{@x} Y:#{@y}" - reading = (@world.collision_with?(self)) ? 1 : 0 - break if reading == 1 - end - else #driving backward, driving blind (no sensors!) - distance.upto(0) do |x| - previous_x = @x - previous_y = @y - case @facing - when 0 - @y = @y-1 - when 90 - @x = @x-1 - when 180 - @y = @y+1 - when 270 - @x = @x+1 - end - puts "N:#{@facing}, X:#{@x} Y:#{@y}" - blind_reading = (@world.collision_with?(self)) ? 1 : 0 - break if blind_reading == 1 - end - reading = 0 #always return 0 when driving blind - end - if reading == 1 #hit something at that coordinate, impassable, back to previous coordinate (don't share points) - @x = previous_x - @y = previous_y - end - if start_x != @x || start_y != @y #some coordinate changed - @timestamp = latest_check_time #enough time accumalated to register movement, record this check in timestamp so we don't accelerate exponentially - end - else - @timestamp = latest_check_time - @facing = @facing + calculate_spin_degree(@velocity, latest_check_time) - puts "N:#{@facing}" - end - - @readings.push(reading) - end - - # When the RoombaSimulation requests a byte from - # the simulated serial port, it gets shifted off the array of available bytes. - def getbyte - readings.shift + # TODO: distinguish collisions with bumpers and not bumpers + @readings.push (@world.collision_with?(self)) ? 1 : 0 end - def read_timeout=(timeout) - end end diff --git a/lib/roomba_simulation.rb b/lib/roomba_simulation.rb index 5a2ccba..e8ce532 100644 --- a/lib/roomba_simulation.rb +++ b/lib/roomba_simulation.rb @@ -16,4 +16,15 @@ def born_in(*args) def write(*args) @serial.write(*args) end + + #makes roomba use our virtual time + def current_time + @serial.world.time + end + + def method_missing(method, *args) + #we will raise if the method is not there either + return @serial.send(method, *args) + end + end diff --git a/lib/simulator.rb b/lib/simulator.rb new file mode 100644 index 0000000..8c75b9e --- /dev/null +++ b/lib/simulator.rb @@ -0,0 +1,38 @@ +############################## +# +#This class is the highest level entity, controls how +#the simulation behaves, contains the world and the robots +#We already had a simulation model so we call this simulator +# +############################### +class Simulator + attr_reader :world + attr_writer :stop + STEP = 0.01 + + def initialize(world = nil) + @world = world || World.new + @stop = false + @current_time = Time.now + Thread.abort_on_exception = true + Thread.new { run } + end + + def add_robot(robot) + @world.spawn(robot) + end + + + private + def run + puts "Simulation started" + while (!@stop) + @world.step(STEP) + sleep(STEP) + @current_time += STEP + @world.time = @current_time + end + puts "Simulation terminated" + end + +end diff --git a/lib/world.rb b/lib/world.rb index 7c47d0d..d87e764 100644 --- a/lib/world.rb +++ b/lib/world.rb @@ -4,6 +4,7 @@ # ############################## class World + attr_accessor :time def initialize(*roombots) @robots = [] @@ -32,6 +33,15 @@ def spawn(robot) def robot(index=0) @robots[index] end + + def step (time_step) + @robots.each do |robot| + robot.step(time_step) + if collision_with?(robot) + robot.step_back + end + end + end #TODO: collision with a serial it is not very intuitive @@ -51,12 +61,12 @@ def collision_with?(serial) end private + def read_world - #TODO: read from external .yml or something - #TODO: mass and shape for the obstacles + #TODO: read from external .yml or something + #TODO: mass and shape for the obstacles @boundaries ||= [1000, -1000, 800, -800]#x,-x, y, -y @obstacles ||= [{x:0, y:500, radius:20}, {x:300, y:0, radius:20},{x:-900, y:-700, radius:10}] - end end From bf2ffd471bbd58962ed358462b2f8b6209fda421 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Sun, 13 Jan 2013 23:05:45 +0900 Subject: [PATCH 02/20] correctly account for local rvmrc --- .rvmrc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.rvmrc b/.rvmrc index 4c79880..b86def2 100644 --- a/.rvmrc +++ b/.rvmrc @@ -1,5 +1,5 @@ -if [ -f "$0".local ]; then - source "$0".local +if [ -f .rvmrc.local ]; then + source .rvmrc.local else rvm use 1.9.3 fi From 3b63f43987de622dea7667fa54043d8e07c3fc7f Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Mon, 14 Jan 2013 01:02:57 +0900 Subject: [PATCH 03/20] added initial test for simulation --- Gemfile | 4 +++ Gemfile.lock | 9 +++++++ lib/roomba_serial_simulation.rb | 7 +++-- lib/simulator.rb | 21 ++++++++++----- test/test_helper.rb | 2 +- test/unit/simulation_test.rb | 47 ++++++++++++++++++++++++++++++--- 6 files changed, 77 insertions(+), 13 deletions(-) diff --git a/Gemfile b/Gemfile index aae912e..14df0ed 100644 --- a/Gemfile +++ b/Gemfile @@ -20,6 +20,10 @@ group :assets do gem 'twitter-bootstrap-rails' end +group :development,:test do + gem 'debugger' +end + group :development do gem 'thin' end diff --git a/Gemfile.lock b/Gemfile.lock index 07b980d..eadf8f6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -39,8 +39,16 @@ GEM coffee-script-source execjs coffee-script-source (1.2.0) + columnize (0.3.6) commonjs (0.2.5) daemons (1.1.8) + debugger (1.2.3) + columnize (>= 0.3.1) + debugger-linecache (~> 1.1.1) + debugger-ruby_core_source (~> 1.1.5) + debugger-linecache (1.1.2) + debugger-ruby_core_source (>= 1.1.1) + debugger-ruby_core_source (1.1.6) erubis (2.7.0) eventmachine (0.12.10) execjs (1.3.0) @@ -139,6 +147,7 @@ PLATFORMS DEPENDENCIES coffee-rails + debugger jquery-rails json minitest diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index f77f5b7..bffd300 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -74,7 +74,6 @@ def render def step(step_time) if !@turning distance = (@velocity * step_time).to_i - puts "Travelled #{distance}mm" @previous_x = @x @previous_y = @y move_to(@facing, distance) @@ -165,7 +164,11 @@ def prepare_readings(*args) # environment can leverage the same current X,Y coordinates def prepare_reading_7 # TODO: distinguish collisions with bumpers and not bumpers - @readings.push (@world.collision_with?(self)) ? 1 : 0 + if @world.collision.with?(self) + @readings.push 1 + else + @readings.push 0 + end end end diff --git a/lib/simulator.rb b/lib/simulator.rb index 8c75b9e..a6613a5 100644 --- a/lib/simulator.rb +++ b/lib/simulator.rb @@ -7,26 +7,35 @@ ############################### class Simulator attr_reader :world - attr_writer :stop STEP = 0.01 def initialize(world = nil) @world = world || World.new - @stop = false @current_time = Time.now - Thread.abort_on_exception = true - Thread.new { run } end def add_robot(robot) @world.spawn(robot) end - + def start + Thread.abort_on_exception = true + Thread.new { run } + end + + def stop + @wants_to_stop = true + end + + def running? + !@wants_to_stop + end + private def run + @wants_to_stop = false puts "Simulation started" - while (!@stop) + while (!@wants_to_stop) @world.step(STEP) sleep(STEP) @current_time += STEP diff --git a/test/test_helper.rb b/test/test_helper.rb index 275cddd..43e5daa 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -10,7 +10,7 @@ # :pretty - new pretty reporter # :marshal - dump output as YAML (normal run mode only) # :cue - interactive testing - c.format = :outline + c.format = :pretty # turn on invoke/execute tracing, enable full backtrace c.trace = true # use humanized test names (works only with :outline format) diff --git a/test/unit/simulation_test.rb b/test/unit/simulation_test.rb index 1c5eebd..5b0efef 100644 --- a/test/unit/simulation_test.rb +++ b/test/unit/simulation_test.rb @@ -1,7 +1,46 @@ + require 'test_helper' -class SimulationTest < ActiveSupport::TestCase - # test "the truth" do - # assert true - # end +describe Simulation do + before(:each) do + @simulation = Simulator.new + @roo = RoombaSimulation.new + @simulation.add_robot(@roo) + end + + after(:each) do + @simulation.stop + end + + it "can be started and stopped" do + @simulation.start + @simulation.running?.must_equal true + @simulation.stop + @simulation.running?.must_equal false + end +=begin + it "should not move the robot if the simulation has not started" do + @roo.move(50) + sleep(0.1) + @roo.x.must_equal 0 + @roo.y.must_equal 0 + end +=end + + it "must spawn the robot into 0,0" do + @simulation.start + @roo.x.must_equal 0 + @roo.y.must_equal 0 + end +=begin +TODO + it "should move in a straight line" do + @simulation.start + @roo.move(50) + @roo.x.must_equal 50 + @roo.y.must_equal 0 + end +=end + + end From 872485270917c208d61530704b6b7447f0c9fcea Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Mon, 14 Jan 2013 01:20:44 +0900 Subject: [PATCH 04/20] added a formatter to make output more selective and open to send data to the web or other medium --- lib/console.rb | 28 ++++++++++++++++++++++++++++ lib/roomba_serial_simulation.rb | 14 +++++++------- lib/simulator.rb | 7 ++++--- 3 files changed, 39 insertions(+), 10 deletions(-) create mode 100644 lib/console.rb diff --git a/lib/console.rb b/lib/console.rb new file mode 100644 index 0000000..480f725 --- /dev/null +++ b/lib/console.rb @@ -0,0 +1,28 @@ +############### +# One of the possible formatters of our information. +# It outputs to the console. By default only info messages will be output +# create it with new(:debug) to make it much more verbose +############### +class Console + + def initilize(level=:info) + @level = level + end + + def info(text) + puts(text) + end + + def debug(text) + return if @level != :debug + puts(text) + end + + private + + def puts(text) + return if defined?(Rails) && Rails.env == :test + Kernel.puts text + end + +end diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index bffd300..9f2b32b 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -7,12 +7,11 @@ class RoombaSerialSimulation # currently the simulation settings are hardcoded in the initializer # need to refactor to allow various predefined or even random simulations - def initialize + def initialize(formatter = nil) yield self if block_given? # Set defaults if not set in the initializer block # These defaults match the previously hard-coded values - @simulation ||= 'simulation' @x ||= 0 @y ||= 0 @facing ||= 0 #+y, @facing of 90 == +x, @facing of 180 == -y, @facing of 270 == -x @@ -23,6 +22,7 @@ def initialize @degree = 0 @turning = false @readings = [] + @formatter = formatter || Console.new self end @@ -40,7 +40,7 @@ def y # if a method exists for handling that opcode, run it; Need to write mock # methods for each useful ROI command def write(*bytes) - puts "Bytes Roomba received: #{bytes.inspect}" + @formatter.debug "Bytes Roomba received: #{bytes.inspect}" command = bytes.shift case command when 137 @@ -77,10 +77,10 @@ def step(step_time) @previous_x = @x @previous_y = @y move_to(@facing, distance) - puts "N: #@facing, x: #@x, y: #@y" + @formatter.debug "N: #@facing, x: #@x, y: #@y" else @facing = @facing + calculate_spin_degree(@velocity, step_time) - puts "N:#@facing" + @formatter.debug "N:#@facing" end end @@ -109,9 +109,9 @@ def setup_move(*args) @velocity = signed_integer([args[0], args[1]]) @moving = (@velocity.abs > 0) ? true : false if @moving - puts "Moving at #{@velocity}mm/s" + @formatter.debug "Moving at #{@velocity}mm/s" else - puts "Stopped moving" + @formatter.debug "Stopped moving" end @degree = signed_integer([args[2], args[3]]) @turning = (@degree.abs == 1) ? true : false diff --git a/lib/simulator.rb b/lib/simulator.rb index a6613a5..1e2d6f8 100644 --- a/lib/simulator.rb +++ b/lib/simulator.rb @@ -9,8 +9,9 @@ class Simulator attr_reader :world STEP = 0.01 - def initialize(world = nil) + def initialize(world = nil, formatter = nil) @world = world || World.new + @formatter = formatter || Console.new @current_time = Time.now end @@ -34,14 +35,14 @@ def running? private def run @wants_to_stop = false - puts "Simulation started" + @formatter.info "Simulation started" while (!@wants_to_stop) @world.step(STEP) sleep(STEP) @current_time += STEP @world.time = @current_time end - puts "Simulation terminated" + @formatter.info "Simulation terminated" end end From ef7bae2f5cd578721de56bbd00eedce1a07f3a0d Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Mon, 14 Jan 2013 01:29:15 +0900 Subject: [PATCH 05/20] update README --- README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 4a1996a..eaf333a 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,12 @@ Jump in console and give the basic simulation a shot. ````ruby $ rails c :001 > simulator = Simulator.new -:001 > earth = simulator.world :002 > roo = RoombaSimulation.new -:003 > earth.spawn(roo) -:004 > roo.move(100) -:005 > roo.move(0,120) -:006 > roo.move(1000) +:003 > simulator.add_robot(roo) +:004 > simulator.start +:005 > roo.move(100) +:006 > roo.move(0,120) +:007 > roo.move(1000) ```` You should end up with a bump reading at N:90, X:126 Y:89 From d2141a732a17dd5848f09a6b332f426024a13d14 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Sun, 3 Feb 2013 00:24:33 +0900 Subject: [PATCH 06/20] created a subdir simulation, this may be the start of a different gem, console now have levels of output --- config/application.rb | 2 +- lib/console.rb | 28 ------------------- lib/simulator/console.rb | 46 ++++++++++++++++++++++++++++++++ lib/{ => simulator}/simulator.rb | 0 lib/{ => simulator}/world.rb | 0 5 files changed, 47 insertions(+), 29 deletions(-) delete mode 100644 lib/console.rb create mode 100644 lib/simulator/console.rb rename lib/{ => simulator}/simulator.rb (100%) rename lib/{ => simulator}/world.rb (100%) diff --git a/config/application.rb b/config/application.rb index f17f11d..f6151c7 100644 --- a/config/application.rb +++ b/config/application.rb @@ -17,7 +17,7 @@ class Application < Rails::Application # Custom directories with classes and modules you want to be autoloadable. # config.autoload_paths += %W(#{config.root}/extras) - config.autoload_paths += %W(#{config.root}/lib) + config.autoload_paths += %W(#{config.root}/lib #{config.root}/lib/simulator) # Only load the plugins named here, in the order given (default is alphabetical). # :all can be used as a placeholder for all plugins not explicitly named. diff --git a/lib/console.rb b/lib/console.rb deleted file mode 100644 index 480f725..0000000 --- a/lib/console.rb +++ /dev/null @@ -1,28 +0,0 @@ -############### -# One of the possible formatters of our information. -# It outputs to the console. By default only info messages will be output -# create it with new(:debug) to make it much more verbose -############### -class Console - - def initilize(level=:info) - @level = level - end - - def info(text) - puts(text) - end - - def debug(text) - return if @level != :debug - puts(text) - end - - private - - def puts(text) - return if defined?(Rails) && Rails.env == :test - Kernel.puts text - end - -end diff --git a/lib/simulator/console.rb b/lib/simulator/console.rb new file mode 100644 index 0000000..e9eeb1f --- /dev/null +++ b/lib/simulator/console.rb @@ -0,0 +1,46 @@ +############### +# One of the possible formatters of our information. +# It outputs to the console. By default only info messages will be output +# create it with new(:debug) to make it much more verbose +############### +class Console + + def initialize(level=:info) + if available?(level) + @level = level + else + @level = :info + end + end + + def info(text) + puts(text) if should_print(:info) + end + + def debug(text) + puts(text) if should_print(:debug) + end + + private + def available_levels + [:debug, :info, :quiet] + end + + def available?(level) + available_levels.include?(level) + end + + def priority(level) + available_levels.index(level) + end + + def should_print(level) + priority(level) >= priority(@level) + end + + def puts(text) + return if defined?(Rails) && Rails.env == :test + Kernel.puts text + end + +end diff --git a/lib/simulator.rb b/lib/simulator/simulator.rb similarity index 100% rename from lib/simulator.rb rename to lib/simulator/simulator.rb diff --git a/lib/world.rb b/lib/simulator/world.rb similarity index 100% rename from lib/world.rb rename to lib/simulator/world.rb From f458c1a68f8c31d2c9e5a5c7dff897c2a76f9d35 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Sun, 3 Feb 2013 16:51:22 +0900 Subject: [PATCH 07/20] solved some bugs, now it seems much more correct --- lib/roomba.rb | 2 ++ lib/roomba_serial_simulation.rb | 2 +- lib/simulator/robot.rb | 31 +++++++++++++++++++++++++++++++ lib/simulator/simulator.rb | 22 ++++++++++++++++------ lib/simulator/world.rb | 11 +++++------ test/unit/simulation_test.rb | 19 ++++++++----------- 6 files changed, 63 insertions(+), 24 deletions(-) create mode 100644 lib/simulator/robot.rb diff --git a/lib/roomba.rb b/lib/roomba.rb index dcd457d..94ebe1d 100644 --- a/lib/roomba.rb +++ b/lib/roomba.rb @@ -133,6 +133,8 @@ def move(distance, degree=0, velocity=200) sensors end + #This is overwritten in the roomba_simulation so we can use simulation + #time instead of real world time. def current_time Time.now end diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index 9f2b32b..f93b095 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -73,7 +73,7 @@ def render def step(step_time) if !@turning - distance = (@velocity * step_time).to_i + distance = @velocity * step_time @previous_x = @x @previous_y = @y move_to(@facing, distance) diff --git a/lib/simulator/robot.rb b/lib/simulator/robot.rb new file mode 100644 index 0000000..015e39e --- /dev/null +++ b/lib/simulator/robot.rb @@ -0,0 +1,31 @@ +################### +# +# This is the interface a robot needs to implement to work with +# the simulator +# +################## + +class Robot + #current position and size (radius) of the robot + attr_reader :x,:y, :radius + def initialize + @x = 0 + @y = 0 + @radius = 10 + end + + def render + end + + def born_in(*args) + end + + def current_time + end + + def step(time) + + + end + +end diff --git a/lib/simulator/simulator.rb b/lib/simulator/simulator.rb index 1e2d6f8..877f7ec 100644 --- a/lib/simulator/simulator.rb +++ b/lib/simulator/simulator.rb @@ -1,42 +1,52 @@ +require 'console' +require 'world' + ############################## # #This class is the highest level entity, controls how #the simulation behaves, contains the world and the robots -#We already had a simulation model so we call this simulator # ############################### class Simulator attr_reader :world - STEP = 0.01 def initialize(world = nil, formatter = nil) @world = world || World.new @formatter = formatter || Console.new @current_time = Time.now + @running = false + @world.time = @current_time end + #TODO: should these methods add ! because they modify the simulation ? def add_robot(robot) @world.spawn(robot) + self end def start + @running = true Thread.abort_on_exception = true Thread.new { run } + self end def stop - @wants_to_stop = true + @running = false + self end def running? - !@wants_to_stop + @running end private + + STEP = 0.1 + def run - @wants_to_stop = false @formatter.info "Simulation started" - while (!@wants_to_stop) + while (@running) @world.step(STEP) sleep(STEP) @current_time += STEP diff --git a/lib/simulator/world.rb b/lib/simulator/world.rb index d87e764..d1484bf 100644 --- a/lib/simulator/world.rb +++ b/lib/simulator/world.rb @@ -44,11 +44,10 @@ def step (time_step) end - #TODO: collision with a serial it is not very intuitive - def collision_with?(serial) - x = serial.x - y = serial.y - radius = serial.radius + def collision_with?(robot) + x = robot.x + y = robot.y + radius = robot.radius if (@boundaries[0] - x).abs == radius || (@boundaries[1] - x).abs == radius || (@boundaries[2] - y).abs == radius || (@boundaries[3] - y).abs == radius return true @@ -61,7 +60,7 @@ def collision_with?(serial) end private - + def read_world #TODO: read from external .yml or something #TODO: mass and shape for the obstacles diff --git a/test/unit/simulation_test.rb b/test/unit/simulation_test.rb index 5b0efef..687b5c6 100644 --- a/test/unit/simulation_test.rb +++ b/test/unit/simulation_test.rb @@ -8,39 +8,36 @@ @simulation.add_robot(@roo) end - after(:each) do - @simulation.stop - end - it "can be started and stopped" do @simulation.start @simulation.running?.must_equal true @simulation.stop @simulation.running?.must_equal false end -=begin + + it "should not move the robot if the simulation has not started" do @roo.move(50) sleep(0.1) @roo.x.must_equal 0 @roo.y.must_equal 0 end -=end + it "must spawn the robot into 0,0" do @simulation.start @roo.x.must_equal 0 @roo.y.must_equal 0 + @simulation.stop end -=begin -TODO + it "should move in a straight line" do @simulation.start @roo.move(50) - @roo.x.must_equal 50 - @roo.y.must_equal 0 + sleep 0.3 + @roo.x.must_equal 0 + @roo.y.must_equal 50 end -=end end From fa5d6d4da65b6af9e54abd08de93630dc655741c Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Sun, 3 Feb 2013 23:44:59 +0900 Subject: [PATCH 08/20] abstracted a class robotsimulation and moved all the robot like properties there, serial_simulation thus has only one work to do instead of two --- README.md | 3 +- app/controllers/simulations_controller.rb | 9 +- lib/roomba_serial_simulation.rb | 135 ++++++++++------------ lib/roomba_simulation.rb | 37 +++--- lib/simulator/robot.rb | 31 ----- lib/simulator/robot_simulation.rb | 99 ++++++++++++++++ lib/simulator/simulator.rb | 5 - lib/simulator/world.rb | 1 - 8 files changed, 180 insertions(+), 140 deletions(-) delete mode 100644 lib/simulator/robot.rb create mode 100644 lib/simulator/robot_simulation.rb diff --git a/README.md b/README.md index eaf333a..ca80b04 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,7 @@ Jump in console and give the basic simulation a shot. ````ruby $ rails c :001 > simulator = Simulator.new -:002 > roo = RoombaSimulation.new -:003 > simulator.add_robot(roo) +:002 > roo = RoombaSimulation.new(simulator) :004 > simulator.start :005 > roo.move(100) :006 > roo.move(0,120) diff --git a/app/controllers/simulations_controller.rb b/app/controllers/simulations_controller.rb index 74cd7ce..72f7943 100644 --- a/app/controllers/simulations_controller.rb +++ b/app/controllers/simulations_controller.rb @@ -13,12 +13,15 @@ def index # GET /simulations/1 # GET /simulations/1.json def show + #TODO: serialize/deserialize sim from DB @simulation = Simulation.find(params[:id]) + # TODO: This is confusing + # Watch out! Simulator != Simulation . + simulator = Simulator.new + @world = simulator.world #this is super crap, just for debugging ATM, moving out soon - @world = World.new - @roombot = RoombaSimulation.new - @world.spawn(@roombot) + @roombot = RoombaSimulation.new(simulator) respond_to do |format| format.html # show.html.erb diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index f93b095..98f630d 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -1,21 +1,11 @@ class RoombaSerialSimulation - attr_accessor :simulation, :requested_readings, :readings, :facing, :moving, :velocity, :turning, :degree, :world - attr_writer :x, :y include ByteProcessing include Calculations # currently the simulation settings are hardcoded in the initializer # need to refactor to allow various predefined or even random simulations - def initialize(formatter = nil) - yield self if block_given? - - # Set defaults if not set in the initializer block - # These defaults match the previously hard-coded values - @x ||= 0 - @y ||= 0 - @facing ||= 0 #+y, @facing of 90 == +x, @facing of 180 == -y, @facing of 270 == -x - + def initialize(virtual_roomba = nil, formatter = nil) # The following are not to be set by the user @moving = false @velocity = 0 @@ -23,30 +13,32 @@ def initialize(formatter = nil) @turning = false @readings = [] @formatter = formatter || Console.new + @virtual_roomba = virtual_roomba # TODO: only used to check collitions, may go to bumper in the future. + @waiting_bytes = 0 + @command_bytes = [] self end - #components outside here can not see our internal floating point representation - def x - @x.round - end - - def y - @y.round - end - # When the RoombaSimulation class tries to send data to the simulated Roomba - # this is where that data arrives. Check the first byte to get the opcode - # if a method exists for handling that opcode, run it; Need to write mock - # methods for each useful ROI command + # this is where that data arrives. + # Roomba will write one byte at a time here + # we need to take all the bytes and reconstruct the original order + # This method will be highly simplify when we abstract from the byte + # protocoll of Roomba to a more general interface. + # Need to write mock methods for each useful ROI command + def write(*bytes) - @formatter.debug "Bytes Roomba received: #{bytes.inspect}" - command = bytes.shift - case command - when 137 - setup_move(*bytes) - when 149 - prepare_readings(*bytes) + byte = bytes.first.ord + if @waiting_bytes == 0 + command = byte + @command_bytes = [command] + @waiting_bytes = bytes_needed_per_command(command) + else + @command_bytes.push byte + @waiting_bytes -= 1 + if @waiting_bytes == 0 + dispatch_command + end end end @@ -54,56 +46,56 @@ def moving? return @moving end - def radius - Roomba::ROOMBA_RADIUS - end - - def born_in(world) - @world = world - end - - def render - ui = {} - ui['x'] = x - ui['y'] = y - ui['radius'] = radius - ui['name'] = 'Roomba' - ui + def calculate_rotation(step_time) + if @turning + calculate_spin_degree(@velocity, step_time) + else + 0 + end end - def step(step_time) - if !@turning - distance = @velocity * step_time - @previous_x = @x - @previous_y = @y - move_to(@facing, distance) - @formatter.debug "N: #@facing, x: #@x, y: #@y" + def calculate_distance(step_time) + if @turning + 0 else - @facing = @facing + calculate_spin_degree(@velocity, step_time) - @formatter.debug "N:#@facing" + @velocity * step_time end end - def step_back - @x = @previous_x - @y = @previous_y - end # When the RoombaSimulation requests a byte from # the simulated serial port, it gets shifted off the array of available bytes. def getbyte - readings.shift + @readings.shift end def read_timeout=(timeout) end - private + + def dispatch_command + command = @command_bytes.shift + case command + when 137 + setup_move(@command_bytes) + when 149 + prepare_readings(@command_bytes) + when 128,130 + @formatter.info "Roomba API ready to receive commands" + else + @formatter.debug "Command not implemented #{command}" + end + end + + def bytes_needed_per_command(command) + bytes_per_command = { 137 => 4, 128 => 0, 130 => 0, 149 => 3 } + bytes_per_command[command] || 0 + end # Sets the state of simulated Roomba to moving - def setup_move(*args) + def setup_move(args) # update x, y; check if any obstacle coordinates fall inside roomba's radius; # queue sensor readings in some array to simulate TX/RX @velocity = signed_integer([args[0], args[1]]) @@ -115,6 +107,11 @@ def setup_move(*args) end @degree = signed_integer([args[2], args[3]]) @turning = (@degree.abs == 1) ? true : false + +=begin + #TODO: I think this is about degree, not about facing. + #We do not support this at this moment. So either rotate over yourself or do not rotate + # Simulation can currently only handle the following (cannot support half-points or curves) if @facing.between?(45, 135) @facing = 90 @@ -128,22 +125,10 @@ def setup_move(*args) if @velocity < 0 && @fading != 0 @facing = 360 - @facing end +=end return true end - def move_to(direction, distance) - case direction - when 0 - @y += distance - when 90 - @x += distance - when 180 - @y -= distance - when 270 - @x -= distance - end - end - def prepare_readings(*args) args.each do |request| Roomba::SENSORS.each do |sensor| @@ -164,7 +149,7 @@ def prepare_readings(*args) # environment can leverage the same current X,Y coordinates def prepare_reading_7 # TODO: distinguish collisions with bumpers and not bumpers - if @world.collision.with?(self) + if @virtual_roomba.got_collisions? @readings.push 1 else @readings.push 0 diff --git a/lib/roomba_simulation.rb b/lib/roomba_simulation.rb index e8ce532..6521ea6 100644 --- a/lib/roomba_simulation.rb +++ b/lib/roomba_simulation.rb @@ -1,30 +1,21 @@ -class RoombaSimulation < Roomba - def initialize(port="simulation", latency=0, baud=115200) - @serial = RoombaSerialSimulation.new - super(port, latency, baud, @serial) - self - end +# ######## +# This class is an specific implementation of a simulation for roomba +# ####### +class RoombaSimulation < RobotSimulation + def initialize(simulation) + world = simulation.world + serial = RoombaSerialSimulation.new + real_robot = Roomba.new('simulation', 0, 115200, serial) + world.spawn(self) - def render - @serial.render + super(world, serial, real_robot) + self end - def born_in(*args) - @serial.born_in(*args) + def radius + Roomba::ROOMBA_RADIUS end - def write(*args) - @serial.write(*args) - end - - #makes roomba use our virtual time - def current_time - @serial.world.time - end +end - def method_missing(method, *args) - #we will raise if the method is not there either - return @serial.send(method, *args) - end -end diff --git a/lib/simulator/robot.rb b/lib/simulator/robot.rb deleted file mode 100644 index 015e39e..0000000 --- a/lib/simulator/robot.rb +++ /dev/null @@ -1,31 +0,0 @@ -################### -# -# This is the interface a robot needs to implement to work with -# the simulator -# -################## - -class Robot - #current position and size (radius) of the robot - attr_reader :x,:y, :radius - def initialize - @x = 0 - @y = 0 - @radius = 10 - end - - def render - end - - def born_in(*args) - end - - def current_time - end - - def step(time) - - - end - -end diff --git a/lib/simulator/robot_simulation.rb b/lib/simulator/robot_simulation.rb new file mode 100644 index 0000000..2aaf058 --- /dev/null +++ b/lib/simulator/robot_simulation.rb @@ -0,0 +1,99 @@ +# ##################### +# This class is an abstract interface for simulated robots +# it can not be used directly +# Child classes need to call the initializer with a serial and a the +# class which drives the robot. +# They need also to implement radius so we know its geometry +# ################### +class RobotSimulation + attr_reader :facing + + def initialize(world, serial, real_robot) + raise "A virtual robot needs virtual hardware" if serial.nil? + raise "A virtual robot needs a real robot implementation" if real_robot.nil? + raise "A virtual robot needs a simulation" if world.nil? + @serial = serial + @real_robot = real_robot + @world = world + + yield self if block_given? + + # Set defaults if not set in the initializer block + # These defaults match the previously hard-coded values + @x ||= 0 + @y ||= 0 + @facing ||= 0 #+y, @facing of 90 == +x, @facing of 180 == -y, @facing of 270 == -x + @previous_x = @previous_y = 0 + #TODO: formatter has to be a singleton everyone can use + @formatter = Console.new + end + + def radius + 100 #default radius, this method should be overloaded by every robot + end + + def step(step_time) + @previous_x = @x + @previous_y = @y + @facing = @facing + @serial.calculate_rotation(step_time) + distance = @serial.calculate_distance(step_time) + move_to(@facing, distance) + @formatter.debug "N: #@facing, x: #@x, y: #@y" + end + + #TODO: this is fugly, should be a better way to stop on obstacles + def step_back + @x = @previous_x + @y = @previous_y + end + + def render + ui = {} + ui['x'] = x + ui['y'] = y + ui['radius'] = radius + ui['name'] = 'Roomba' + ui + end + + def x + @x.round + end + + def y + @y.round + end + + #TODO: move this to a class and pass it in a constructor to the real + #robot + #makes roomba use our virtual time + def current_time + @serial.world.time + end + + #TODO: this method could have a better name + def got_collitions? + @world.collision.with?(@virtual_roomba) + end + + private + + def move_to(direction, distance) + case direction + when 0 + @y += distance + when 90 + @x += distance + when 180 + @y -= distance + when 270 + @x -= distance + end + end + + def method_missing(method, *args) + #we will raise if the method is not there either + return @real_robot.send(method, *args) + end + +end diff --git a/lib/simulator/simulator.rb b/lib/simulator/simulator.rb index 877f7ec..b7b84c6 100644 --- a/lib/simulator/simulator.rb +++ b/lib/simulator/simulator.rb @@ -19,11 +19,6 @@ def initialize(world = nil, formatter = nil) end #TODO: should these methods add ! because they modify the simulation ? - def add_robot(robot) - @world.spawn(robot) - self - end - def start @running = true Thread.abort_on_exception = true diff --git a/lib/simulator/world.rb b/lib/simulator/world.rb index d1484bf..b963fbd 100644 --- a/lib/simulator/world.rb +++ b/lib/simulator/world.rb @@ -27,7 +27,6 @@ def render def spawn(robot) @robots.push(robot) - robot.born_in self end def robot(index=0) From 04098d66dca6adaff5d3a60ac387b88c265fb459 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Sat, 9 Feb 2013 18:57:42 +0900 Subject: [PATCH 09/20] Degrees are now exact instead of approximated. --- README.md | 2 +- lib/roomba_serial_simulation.rb | 18 ------------------ lib/simulator/robot_simulation.rb | 18 +++++++----------- lib/simulator/simulator.rb | 2 +- 4 files changed, 9 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index ca80b04..cf1603b 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ $ rails c :002 > roo = RoombaSimulation.new(simulator) :004 > simulator.start :005 > roo.move(100) -:006 > roo.move(0,120) +:006 > roo.move(0,90) :007 > roo.move(1000) ```` You should end up with a bump reading at N:90, X:126 Y:89 diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index 98f630d..f73476a 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -108,24 +108,6 @@ def setup_move(args) @degree = signed_integer([args[2], args[3]]) @turning = (@degree.abs == 1) ? true : false -=begin - #TODO: I think this is about degree, not about facing. - #We do not support this at this moment. So either rotate over yourself or do not rotate - - # Simulation can currently only handle the following (cannot support half-points or curves) - if @facing.between?(45, 135) - @facing = 90 - elsif @facing.between?(135, 225) - @facing = 180 - elsif @facing.between?(225, 315) - @facing = 270 - else - @facing = 0 - end - if @velocity < 0 && @fading != 0 - @facing = 360 - @facing - end -=end return true end diff --git a/lib/simulator/robot_simulation.rb b/lib/simulator/robot_simulation.rb index 2aaf058..3ae037a 100644 --- a/lib/simulator/robot_simulation.rb +++ b/lib/simulator/robot_simulation.rb @@ -22,7 +22,7 @@ def initialize(world, serial, real_robot) # These defaults match the previously hard-coded values @x ||= 0 @y ||= 0 - @facing ||= 0 #+y, @facing of 90 == +x, @facing of 180 == -y, @facing of 270 == -x + @facing ||= 0 # @facing=0 => +y, @facing=90 => +x, @facing=180 => -y, @facing=270 => -x @previous_x = @previous_y = 0 #TODO: formatter has to be a singleton everyone can use @formatter = Console.new @@ -78,17 +78,13 @@ def got_collitions? private + def degrees_to_radians(degrees) + degrees * Math::PI / 180 + end + def move_to(direction, distance) - case direction - when 0 - @y += distance - when 90 - @x += distance - when 180 - @y -= distance - when 270 - @x -= distance - end + @y += distance * Math.cos(degrees_to_radians(direction)) + @x += distance * Math.sin(degrees_to_radians(direction)) end def method_missing(method, *args) diff --git a/lib/simulator/simulator.rb b/lib/simulator/simulator.rb index b7b84c6..1587a79 100644 --- a/lib/simulator/simulator.rb +++ b/lib/simulator/simulator.rb @@ -37,7 +37,7 @@ def running? private - STEP = 0.1 + STEP = 0.01 def run @formatter.info "Simulation started" From 47bf1a216e7edd11559fb297c48bfd7b6bc859a5 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Sat, 9 Feb 2013 19:30:06 +0900 Subject: [PATCH 10/20] metaprogram to modify roomba from the outside and make it live in the simulation time instead of its own --- lib/roomba.rb | 2 +- lib/roomba_simulation.rb | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/lib/roomba.rb b/lib/roomba.rb index 94ebe1d..d22438b 100644 --- a/lib/roomba.rb +++ b/lib/roomba.rb @@ -217,7 +217,7 @@ def get_readings(*sensors_requested) readings[sensor] = {:raw => nil, :formatted => []} readings[sensor][:raw] = bytes.shift(SENSORS[sensor][:bytes]) readings[sensor][:formatted] = set_readings(sensor, readings[sensor][:raw]) - puts "Sensors: #{readings[sensor].inspect}" + #puts "Sensors: #{readings[sensor].inspect}" end readings #return hash of readings end diff --git a/lib/roomba_simulation.rb b/lib/roomba_simulation.rb index 6521ea6..4d28c55 100644 --- a/lib/roomba_simulation.rb +++ b/lib/roomba_simulation.rb @@ -5,6 +5,7 @@ class RoombaSimulation < RobotSimulation def initialize(simulation) world = simulation.world serial = RoombaSerialSimulation.new + modify_roomba_internals(world) real_robot = Roomba.new('simulation', 0, 115200, serial) world.spawn(self) @@ -16,6 +17,20 @@ def radius Roomba::ROOMBA_RADIUS end + private + + #TODO: TOTALLY Hacky. As I do not want to modify Roomba at all if + #possible, using metaprogramming to modify it from outside here. + #Eventually Roomba will get this mehods and constants from somewhere + #else so we can do this correctly. + def modify_roomba_internals(world) + Roomba.send(:define_method, :current_time) do + world.time + end + #Same step than the simulation + Roomba.send(:remove_const, :ROOMBA_DATA_REFRESH_RATE) + Roomba.const_set(:ROOMBA_DATA_REFRESH_RATE, 0.01) + end end From 576092723a0918751f94533979129bad6aecd4ea Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Sat, 9 Feb 2013 21:01:46 +0900 Subject: [PATCH 11/20] changed params in initializers, we do not need to propagate time --- lib/roomba_simulation.rb | 8 ++++---- lib/simulator/robot_simulation.rb | 10 ++-------- lib/simulator/simulator.rb | 13 ++++++------- lib/simulator/world.rb | 14 ++++++-------- 4 files changed, 18 insertions(+), 27 deletions(-) diff --git a/lib/roomba_simulation.rb b/lib/roomba_simulation.rb index 4d28c55..13518b1 100644 --- a/lib/roomba_simulation.rb +++ b/lib/roomba_simulation.rb @@ -5,11 +5,11 @@ class RoombaSimulation < RobotSimulation def initialize(simulation) world = simulation.world serial = RoombaSerialSimulation.new - modify_roomba_internals(world) + modify_roomba_internals(simulation) real_robot = Roomba.new('simulation', 0, 115200, serial) world.spawn(self) - super(world, serial, real_robot) + super(world, serial, real_robot, simulation.formatter) self end @@ -23,9 +23,9 @@ def radius #possible, using metaprogramming to modify it from outside here. #Eventually Roomba will get this mehods and constants from somewhere #else so we can do this correctly. - def modify_roomba_internals(world) + def modify_roomba_internals(simulation) Roomba.send(:define_method, :current_time) do - world.time + simulation.current_time end #Same step than the simulation Roomba.send(:remove_const, :ROOMBA_DATA_REFRESH_RATE) diff --git a/lib/simulator/robot_simulation.rb b/lib/simulator/robot_simulation.rb index 3ae037a..a36f449 100644 --- a/lib/simulator/robot_simulation.rb +++ b/lib/simulator/robot_simulation.rb @@ -8,7 +8,7 @@ class RobotSimulation attr_reader :facing - def initialize(world, serial, real_robot) + def initialize(world, serial, real_robot, formatter = nil) raise "A virtual robot needs virtual hardware" if serial.nil? raise "A virtual robot needs a real robot implementation" if real_robot.nil? raise "A virtual robot needs a simulation" if world.nil? @@ -25,7 +25,7 @@ def initialize(world, serial, real_robot) @facing ||= 0 # @facing=0 => +y, @facing=90 => +x, @facing=180 => -y, @facing=270 => -x @previous_x = @previous_y = 0 #TODO: formatter has to be a singleton everyone can use - @formatter = Console.new + @formatter = formatter || Console.new end def radius @@ -64,12 +64,6 @@ def y @y.round end - #TODO: move this to a class and pass it in a constructor to the real - #robot - #makes roomba use our virtual time - def current_time - @serial.world.time - end #TODO: this method could have a better name def got_collitions? diff --git a/lib/simulator/simulator.rb b/lib/simulator/simulator.rb index 1587a79..36c59e6 100644 --- a/lib/simulator/simulator.rb +++ b/lib/simulator/simulator.rb @@ -8,14 +8,13 @@ # ############################### class Simulator - attr_reader :world + attr_reader :world, :current_time, :formatter - def initialize(world = nil, formatter = nil) - @world = world || World.new - @formatter = formatter || Console.new + def initialize(formatter = nil) @current_time = Time.now + @formatter = formatter || Console.new + @world = World.new @running = false - @world.time = @current_time end #TODO: should these methods add ! because they modify the simulation ? @@ -42,12 +41,12 @@ def running? def run @formatter.info "Simulation started" while (@running) + @current_time += STEP @world.step(STEP) sleep(STEP) - @current_time += STEP - @world.time = @current_time end @formatter.info "Simulation terminated" end end + diff --git a/lib/simulator/world.rb b/lib/simulator/world.rb index b963fbd..bf4479d 100644 --- a/lib/simulator/world.rb +++ b/lib/simulator/world.rb @@ -4,14 +4,10 @@ # ############################## class World - attr_accessor :time - def initialize(*roombots) + def initialize @robots = [] read_world - roombots.each do |bot| - spawn(bot) - end self end @@ -25,14 +21,16 @@ def render world_ui.to_json end - def spawn(robot) - @robots.push(robot) + def spawn(*robots) + robots.each do |bot| + @robots.push(bot) + end end def robot(index=0) @robots[index] end - + def step (time_step) @robots.each do |robot| robot.step(time_step) From 3827b3768478c51b2511766b2fff70884a5829f9 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Sun, 10 Feb 2013 23:40:45 +0900 Subject: [PATCH 12/20] added physics related classes. --- lib/simulator/circle.rb | 4 ++++ lib/simulator/plane.rb | 21 +++++++++++++++++ lib/simulator/pose.rb | 32 +++++++++++++++++++++++++ lib/simulator/position.rb | 25 ++++++++++++++++++++ lib/simulator/robot_simulation.rb | 39 +++++++------------------------ lib/simulator/vector.rb | 28 ++++++++++++++++++++++ lib/simulator/world.rb | 28 ++++++++++++++-------- 7 files changed, 137 insertions(+), 40 deletions(-) create mode 100644 lib/simulator/circle.rb create mode 100644 lib/simulator/plane.rb create mode 100644 lib/simulator/pose.rb create mode 100644 lib/simulator/position.rb create mode 100644 lib/simulator/vector.rb diff --git a/lib/simulator/circle.rb b/lib/simulator/circle.rb new file mode 100644 index 0000000..6579e2f --- /dev/null +++ b/lib/simulator/circle.rb @@ -0,0 +1,4 @@ +################### +# Our obstacles are circles, but other shapes may come later +################### +Circle = Struct.new(:position, :radius) diff --git a/lib/simulator/plane.rb b/lib/simulator/plane.rb new file mode 100644 index 0000000..d8640a3 --- /dev/null +++ b/lib/simulator/plane.rb @@ -0,0 +1,21 @@ +####################################### +# This is an infinite 2D plane with infinite mass and infinitely strong +# Objects and robots can collide with it. +# normal is the normal of the plane towards the origin +# distance_to_origin is the distance from the plane to the origin +####################################### +class Plane + attr_reader :normal, :distance_to_origin + + def initialize(normal, distance_to_origin) + @normal = normal + @distance_to_origin = distance_to_origin + end + + def distance_to(point) + projected_point = normal.project(point) + projected_point.x + projected_point.y + distance_to_origin + end + +end + diff --git a/lib/simulator/pose.rb b/lib/simulator/pose.rb new file mode 100644 index 0000000..8d5980c --- /dev/null +++ b/lib/simulator/pose.rb @@ -0,0 +1,32 @@ +################################ +# Pose of an object in a 2D environment +# Its position in 2D coordinates and its angle in degrees +# angle=0 => +y, angle=90 => +x, angle=180 => -y, angle=270 => -x +################################ +class Pose + + attr_reader :position + + def initialize(position, angle) + @position = position + @angle = angle + end + + #Modify this pose, with a relative position and angle + def advance(distance, angle) + new_angle = @angle + angle + new_y = @position.y + distance * Math.cos(degrees_to_radians(new_angle)) + new_x = @position.x + distance * Math.sin(degrees_to_radians(new_angle)) + Pose.new(Position.new(new_x, new_y), new_angle) + end + + def to_s + "N: #@angle, #@position" + end + + private + def degrees_to_radians(degrees) + degrees * Math::PI / 180 + end + +end diff --git a/lib/simulator/position.rb b/lib/simulator/position.rb new file mode 100644 index 0000000..00660c4 --- /dev/null +++ b/lib/simulator/position.rb @@ -0,0 +1,25 @@ +######################## +# A 2D position +# Positions of objects and robots always represent +# the position if their centers +######################## +class Position + attr_accessor :x, :y + + def initialize(x,y) + @x, @y = x,y + end + + def distance_to(position) + Math.sqrt((@x - position.x)**2 + (@y - position.y)**2) # Pythagoras, miss you buddy. RIP + end + + def round + Position.new(@x.round, @y.round) + end + + def to_s + "x: #@x, y: #@y" + end + +end diff --git a/lib/simulator/robot_simulation.rb b/lib/simulator/robot_simulation.rb index a36f449..95e1603 100644 --- a/lib/simulator/robot_simulation.rb +++ b/lib/simulator/robot_simulation.rb @@ -20,10 +20,7 @@ def initialize(world, serial, real_robot, formatter = nil) # Set defaults if not set in the initializer block # These defaults match the previously hard-coded values - @x ||= 0 - @y ||= 0 - @facing ||= 0 # @facing=0 => +y, @facing=90 => +x, @facing=180 => -y, @facing=270 => -x - @previous_x = @previous_y = 0 + @pose = Pose.new(Position.new(0,0),0) #TODO: formatter has to be a singleton everyone can use @formatter = formatter || Console.new end @@ -33,38 +30,29 @@ def radius end def step(step_time) - @previous_x = @x - @previous_y = @y - @facing = @facing + @serial.calculate_rotation(step_time) - distance = @serial.calculate_distance(step_time) - move_to(@facing, distance) - @formatter.debug "N: #@facing, x: #@x, y: #@y" + @previous_pose = @pose.dup + @pose = @pose.advance(@serial.calculate_distance(step_time), @serial.calculate_rotation(step_time)) + @formatter.debug "#@pose" end #TODO: this is fugly, should be a better way to stop on obstacles def step_back - @x = @previous_x - @y = @previous_y + @pose = @previous_pose.dup end def render ui = {} - ui['x'] = x - ui['y'] = y + ui['x'] = @pose.position.x + ui['y'] = @pose.position.y ui['radius'] = radius ui['name'] = 'Roomba' ui end - def x - @x.round + def pos + @pose.position.round end - def y - @y.round - end - - #TODO: this method could have a better name def got_collitions? @world.collision.with?(@virtual_roomba) @@ -72,15 +60,6 @@ def got_collitions? private - def degrees_to_radians(degrees) - degrees * Math::PI / 180 - end - - def move_to(direction, distance) - @y += distance * Math.cos(degrees_to_radians(direction)) - @x += distance * Math.sin(degrees_to_radians(direction)) - end - def method_missing(method, *args) #we will raise if the method is not there either return @real_robot.send(method, *args) diff --git a/lib/simulator/vector.rb b/lib/simulator/vector.rb new file mode 100644 index 0000000..ef6e55b --- /dev/null +++ b/lib/simulator/vector.rb @@ -0,0 +1,28 @@ +################################ +# A vector in 2D space +################################ +class Vector + attr_accessor :x, :y + + def initialize(x,y) + @x, @y = x,y + end + + def project(position) + Vector.new(@x * position.x, @y * position.y) + end + + def length + Math.sqrt(@x**2 + @y**2) + end + + def *(scalar) + @x += @x * scalar + @y += @y * scalar + end + + def to_s + "x: #@x, y: #@y" + end + +end diff --git a/lib/simulator/world.rb b/lib/simulator/world.rb index bf4479d..f7ae2c5 100644 --- a/lib/simulator/world.rb +++ b/lib/simulator/world.rb @@ -42,16 +42,15 @@ def step (time_step) def collision_with?(robot) - x = robot.x - y = robot.y - radius = robot.radius - if (@boundaries[0] - x).abs == radius || (@boundaries[1] - x).abs == radius || (@boundaries[2] - y).abs == radius || (@boundaries[3] - y).abs == radius - return true + @boundaries.each do |boundary| + distance_to_boundary = boundary.distance_to(robot.pos) - robot.radius + return true if distance_to_boundary <= 0 end + @obstacles.each do |z| - distance = Math.sqrt((x - z[:x])**2 + (y - z[:y])**2) # Pythagoras, miss you buddy. RIP - return true if distance <= (z[:radius] + radius) + distance = z.position.distance_to(robot.pos) + return true if distance <= (z.radius + robot.radius) end false end @@ -61,9 +60,18 @@ def collision_with?(robot) def read_world #TODO: read from external .yml or something #TODO: mass and shape for the obstacles - @boundaries ||= [1000, -1000, 800, -800]#x,-x, y, -y - @obstacles ||= [{x:0, y:500, radius:20}, {x:300, y:0, radius:20},{x:-900, y:-700, radius:10}] + #boundaries from left boundary clockwise. + @boundaries = [ + Plane.new(Vector.new(1,0), 1000), + Plane.new(Vector.new(0,-1), 800), + Plane.new(Vector.new(-1,0), 1000), + Plane.new(Vector.new(0,1), 800) + ] + @obstacles = [ + Circle.new(Position.new(0,500), 20), + Circle.new(Position.new(300,0), 20), + Circle.new(Position.new(-900,-700), 10) + ] end end - From e21264842115bf00bcd80858c731a61b33f1dfa7 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Tue, 12 Feb 2013 19:35:11 +0900 Subject: [PATCH 13/20] added a bumper class, still unrealistic and the serial soon will become the driver --- lib/roomba_serial_simulation.rb | 12 ++++++------ lib/roomba_simulation.rb | 5 +---- lib/simulator/bumper.rb | 16 ++++++++++++++++ lib/simulator/robot_simulation.rb | 15 +++++---------- 4 files changed, 28 insertions(+), 20 deletions(-) create mode 100644 lib/simulator/bumper.rb diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index f73476a..46f36cc 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -5,7 +5,7 @@ class RoombaSerialSimulation # currently the simulation settings are hardcoded in the initializer # need to refactor to allow various predefined or even random simulations - def initialize(virtual_roomba = nil, formatter = nil) + def initialize(bumper, formatter = nil) # The following are not to be set by the user @moving = false @velocity = 0 @@ -13,7 +13,7 @@ def initialize(virtual_roomba = nil, formatter = nil) @turning = false @readings = [] @formatter = formatter || Console.new - @virtual_roomba = virtual_roomba # TODO: only used to check collitions, may go to bumper in the future. + @bumper = bumper @waiting_bytes = 0 @command_bytes = [] self @@ -112,12 +112,12 @@ def setup_move(args) end def prepare_readings(*args) - args.each do |request| + args.first.each do |request| Roomba::SENSORS.each do |sensor| if sensor[1][:packet] == request - if respond_to? "prepare_reading_#{request}".to_sym + begin send("prepare_reading_#{request}".to_sym) - else + rescue NameError 1.upto(sensor[1][:bytes]) { @readings.push(0) } end end @@ -131,7 +131,7 @@ def prepare_readings(*args) # environment can leverage the same current X,Y coordinates def prepare_reading_7 # TODO: distinguish collisions with bumpers and not bumpers - if @virtual_roomba.got_collisions? + if @bumper.got_collisions? @readings.push 1 else @readings.push 0 diff --git a/lib/roomba_simulation.rb b/lib/roomba_simulation.rb index 13518b1..753882e 100644 --- a/lib/roomba_simulation.rb +++ b/lib/roomba_simulation.rb @@ -4,7 +4,7 @@ class RoombaSimulation < RobotSimulation def initialize(simulation) world = simulation.world - serial = RoombaSerialSimulation.new + serial = RoombaSerialSimulation.new(Bumper.new(self, world)) modify_roomba_internals(simulation) real_robot = Roomba.new('simulation', 0, 115200, serial) world.spawn(self) @@ -27,9 +27,6 @@ def modify_roomba_internals(simulation) Roomba.send(:define_method, :current_time) do simulation.current_time end - #Same step than the simulation - Roomba.send(:remove_const, :ROOMBA_DATA_REFRESH_RATE) - Roomba.const_set(:ROOMBA_DATA_REFRESH_RATE, 0.01) end end diff --git a/lib/simulator/bumper.rb b/lib/simulator/bumper.rb new file mode 100644 index 0000000..2b4a46f --- /dev/null +++ b/lib/simulator/bumper.rb @@ -0,0 +1,16 @@ +###################################### +# This class represent a bumper sensor (it returns collisions when +# touching something) +##################################### +# TODO: At this moment checks the whole robot. +class Bumper + def initialize(robot, world) + @robot = robot + @world = world + end + + def got_collisions? + @world.collision_with?(@robot) + end + +end diff --git a/lib/simulator/robot_simulation.rb b/lib/simulator/robot_simulation.rb index 95e1603..620f919 100644 --- a/lib/simulator/robot_simulation.rb +++ b/lib/simulator/robot_simulation.rb @@ -1,18 +1,18 @@ # ##################### # This class is an abstract interface for simulated robots # it can not be used directly -# Child classes need to call the initializer with a serial and a the +# Child classes need to call the initializer with a driver and a the # class which drives the robot. # They need also to implement radius so we know its geometry # ################### class RobotSimulation attr_reader :facing - def initialize(world, serial, real_robot, formatter = nil) - raise "A virtual robot needs virtual hardware" if serial.nil? + def initialize(world, driver, real_robot, formatter = nil) + raise "A virtual robot needs virtual hardware" if driver.nil? raise "A virtual robot needs a real robot implementation" if real_robot.nil? raise "A virtual robot needs a simulation" if world.nil? - @serial = serial + @driver = driver @real_robot = real_robot @world = world @@ -31,7 +31,7 @@ def radius def step(step_time) @previous_pose = @pose.dup - @pose = @pose.advance(@serial.calculate_distance(step_time), @serial.calculate_rotation(step_time)) + @pose = @pose.advance(@driver.calculate_distance(step_time), @driver.calculate_rotation(step_time)) @formatter.debug "#@pose" end @@ -53,11 +53,6 @@ def pos @pose.position.round end - #TODO: this method could have a better name - def got_collitions? - @world.collision.with?(@virtual_roomba) - end - private def method_missing(method, *args) From 00e125e577ea1ad418a36cee3ef3f99b32a8b1f4 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Tue, 5 Mar 2013 18:14:41 +0900 Subject: [PATCH 14/20] finish the merge + some improvements on comments and such --- lib/roomba_serial_simulation.rb | 27 --------------------------- lib/roomba_simulation.rb | 5 +---- lib/simulator/pose.rb | 2 +- lib/simulator/robot_simulation.rb | 6 +++--- lib/simulator/world.rb | 6 +++--- 5 files changed, 8 insertions(+), 38 deletions(-) diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index 4fc2488..46f36cc 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -107,33 +107,6 @@ def setup_move(args) end @degree = signed_integer([args[2], args[3]]) @turning = (@degree.abs == 1) ? true : false -<<<<<<< HEAD -======= - # Simulation can currently only handle the following (cannot support half-points or curves) - if @facing > 45 && @facing < 135 - @facing = 90 - elsif @facing >= 135 && @facing < 225 - @facing = 180 - elsif @facing >= 225 && @facing < 315 - @facing = 270 - else - @facing = 0 - end - return true - end - - def moving? - return @moving - end - - def radius - RADIUS - end - - def born_in(world) - @world = world - end ->>>>>>> master return true end diff --git a/lib/roomba_simulation.rb b/lib/roomba_simulation.rb index 753882e..1cbcb26 100644 --- a/lib/roomba_simulation.rb +++ b/lib/roomba_simulation.rb @@ -19,10 +19,7 @@ def radius private - #TODO: TOTALLY Hacky. As I do not want to modify Roomba at all if - #possible, using metaprogramming to modify it from outside here. - #Eventually Roomba will get this mehods and constants from somewhere - #else so we can do this correctly. + #TODO: TOTALLY Hacky. Eventually Roomba can get its time from an API so we can hook there. def modify_roomba_internals(simulation) Roomba.send(:define_method, :current_time) do simulation.current_time diff --git a/lib/simulator/pose.rb b/lib/simulator/pose.rb index 8d5980c..e445d71 100644 --- a/lib/simulator/pose.rb +++ b/lib/simulator/pose.rb @@ -12,7 +12,7 @@ def initialize(position, angle) @angle = angle end - #Modify this pose, with a relative position and angle + #Create a new pose, modifying previous with a relative position and angle def advance(distance, angle) new_angle = @angle + angle new_y = @position.y + distance * Math.cos(degrees_to_radians(new_angle)) diff --git a/lib/simulator/robot_simulation.rb b/lib/simulator/robot_simulation.rb index 620f919..de717d6 100644 --- a/lib/simulator/robot_simulation.rb +++ b/lib/simulator/robot_simulation.rb @@ -1,7 +1,7 @@ # ##################### # This class is an abstract interface for simulated robots # it can not be used directly -# Child classes need to call the initializer with a driver and a the +# Child classes need to call the initializer with a driver and the # class which drives the robot. # They need also to implement radius so we know its geometry # ################### @@ -16,13 +16,13 @@ def initialize(world, driver, real_robot, formatter = nil) @real_robot = real_robot @world = world - yield self if block_given? - # Set defaults if not set in the initializer block # These defaults match the previously hard-coded values @pose = Pose.new(Position.new(0,0),0) #TODO: formatter has to be a singleton everyone can use @formatter = formatter || Console.new + + yield self if block_given? end def radius diff --git a/lib/simulator/world.rb b/lib/simulator/world.rb index f7ae2c5..825155c 100644 --- a/lib/simulator/world.rb +++ b/lib/simulator/world.rb @@ -48,9 +48,9 @@ def collision_with?(robot) return true if distance_to_boundary <= 0 end - @obstacles.each do |z| - distance = z.position.distance_to(robot.pos) - return true if distance <= (z.radius + robot.radius) + @obstacles.each do |obstacle| + distance = obstacle.position.distance_to(robot.pos) + return true if distance <= (obstacle.radius + robot.radius) end false end From 307ac84da3d2a3b12a05355b8a912c2489f26654 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Tue, 5 Mar 2013 22:55:41 +0900 Subject: [PATCH 15/20] more realistic bumper --- lib/roomba_serial_simulation.rb | 15 +++++---------- lib/roomba_simulation.rb | 3 ++- lib/simulator/angle.rb | 8 ++++++++ lib/simulator/bumper.rb | 26 +++++++++++++++++++++----- lib/simulator/circle.rb | 21 ++++++++++++++++++++- lib/simulator/plane.rb | 16 ++++++++++++++-- lib/simulator/pose.rb | 9 ++------- lib/simulator/robot_simulation.rb | 3 ++- lib/simulator/world.rb | 8 +++----- 9 files changed, 77 insertions(+), 32 deletions(-) create mode 100644 lib/simulator/angle.rb diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index 46f36cc..f877696 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -5,15 +5,14 @@ class RoombaSerialSimulation # currently the simulation settings are hardcoded in the initializer # need to refactor to allow various predefined or even random simulations - def initialize(bumper, formatter = nil) + def initialize(sensors, formatter = nil) # The following are not to be set by the user - @moving = false @velocity = 0 @degree = 0 @turning = false @readings = [] @formatter = formatter || Console.new - @bumper = bumper + @bumpers = sensors #bumpers are the only sensors we have now @waiting_bytes = 0 @command_bytes = [] self @@ -42,10 +41,6 @@ def write(*bytes) end end - def moving? - return @moving - end - def calculate_rotation(step_time) if @turning calculate_spin_degree(@velocity, step_time) @@ -99,8 +94,8 @@ def setup_move(args) # update x, y; check if any obstacle coordinates fall inside roomba's radius; # queue sensor readings in some array to simulate TX/RX @velocity = signed_integer([args[0], args[1]]) - @moving = (@velocity.abs > 0) ? true : false - if @moving + moving = (@velocity.abs > 0) ? true : false + if moving @formatter.debug "Moving at #{@velocity}mm/s" else @formatter.debug "Stopped moving" @@ -131,7 +126,7 @@ def prepare_readings(*args) # environment can leverage the same current X,Y coordinates def prepare_reading_7 # TODO: distinguish collisions with bumpers and not bumpers - if @bumper.got_collisions? + if @bumpers.any?(&:got_collisions?) @readings.push 1 else @readings.push 0 diff --git a/lib/roomba_simulation.rb b/lib/roomba_simulation.rb index 1cbcb26..9b41651 100644 --- a/lib/roomba_simulation.rb +++ b/lib/roomba_simulation.rb @@ -4,7 +4,8 @@ class RoombaSimulation < RobotSimulation def initialize(simulation) world = simulation.world - serial = RoombaSerialSimulation.new(Bumper.new(self, world)) + bumpers = [ Bumper.new(self, -45, 30, world), Bumper.new(self, 45, 30, world) ] + serial = RoombaSerialSimulation.new(bumpers) modify_roomba_internals(simulation) real_robot = Roomba.new('simulation', 0, 115200, serial) world.spawn(self) diff --git a/lib/simulator/angle.rb b/lib/simulator/angle.rb new file mode 100644 index 0000000..849d234 --- /dev/null +++ b/lib/simulator/angle.rb @@ -0,0 +1,8 @@ +################ +# A class with convenience method related to angles +########## +class Angle + def self.degrees_to_radians(degrees) + degrees * Math::PI / 180 + end +end diff --git a/lib/simulator/bumper.rb b/lib/simulator/bumper.rb index 2b4a46f..58021d4 100644 --- a/lib/simulator/bumper.rb +++ b/lib/simulator/bumper.rb @@ -2,15 +2,31 @@ # This class represent a bumper sensor (it returns collisions when # touching something) ##################################### -# TODO: At this moment checks the whole robot. class Bumper - def initialize(robot, world) - @robot = robot - @world = world + # The robot we belong to, the angle where the bumper is installed + # in case the bumper has an extension, lenght of it + # and the world we belong to + def initialize(robot, angle, lenght, world) + @robot, @angle, @lenght, @world = robot, angle, lenght, world end def got_collisions? - @world.collision_with?(@robot) + positions_to_test.any?{ |pos| @world.collision_with?(pos) } + end + + private + def positions_to_test + [bumper_position(@angle-lenght), bumper_position(@angle), bumper_position(@angle+lenght)] + end + + def bumper_position(angle) + bumper_x = @robot.pos.x + @robot.radius * Math.cos(world_angle(angle)) + bumper_y = @robot.pos.y + @robot.radius * Math.sin(world_angle(angle)) + Position.new(bumper_x, bumper_y) + end + + def world_angle(angle) + Angle.degrees_to_radians(@robot.pose.angle + angle) end end diff --git a/lib/simulator/circle.rb b/lib/simulator/circle.rb index 6579e2f..ff0fe93 100644 --- a/lib/simulator/circle.rb +++ b/lib/simulator/circle.rb @@ -1,4 +1,23 @@ ################### # Our obstacles are circles, but other shapes may come later ################### -Circle = Struct.new(:position, :radius) +class Circle + + attr_reader :position, :radius + + def initialize(position, radius) + @position, @radius = position, radius + end + + def distance_to(object) + if object.class == Circle || object.class.ancestors.include?(RobotSimulation) + @position.distance_to(object.pos) - ( @radius + object.radius) + elsif object.class == Position + @position.distance_to(object) - ( @radius ) + else + raise "can not calculate distance from Circle to #{object.class}" + end + end + + +end diff --git a/lib/simulator/plane.rb b/lib/simulator/plane.rb index d8640a3..3d9c5ab 100644 --- a/lib/simulator/plane.rb +++ b/lib/simulator/plane.rb @@ -12,9 +12,21 @@ def initialize(normal, distance_to_origin) @distance_to_origin = distance_to_origin end - def distance_to(point) + def distance_to(object) + if object.class == Circle || object.class.ancestors.include?(RobotSimulation) + distance_to_point(object.position) - object.radius + elsif object.class == Position + distance_to_point(object) + else + raise "can not calculate distance from Plane to #{object.class}" + end + end + + private + + def distance_to_point(point) projected_point = normal.project(point) - projected_point.x + projected_point.y + distance_to_origin + projected_point.x + projected_point.y + @distance_to_origin end end diff --git a/lib/simulator/pose.rb b/lib/simulator/pose.rb index e445d71..ae34727 100644 --- a/lib/simulator/pose.rb +++ b/lib/simulator/pose.rb @@ -15,8 +15,8 @@ def initialize(position, angle) #Create a new pose, modifying previous with a relative position and angle def advance(distance, angle) new_angle = @angle + angle - new_y = @position.y + distance * Math.cos(degrees_to_radians(new_angle)) - new_x = @position.x + distance * Math.sin(degrees_to_radians(new_angle)) + new_y = @position.y + distance * Math.cos(Angle.degrees_to_radians(new_angle)) + new_x = @position.x + distance * Math.sin(Angle.degrees_to_radians(new_angle)) Pose.new(Position.new(new_x, new_y), new_angle) end @@ -24,9 +24,4 @@ def to_s "N: #@angle, #@position" end - private - def degrees_to_radians(degrees) - degrees * Math::PI / 180 - end - end diff --git a/lib/simulator/robot_simulation.rb b/lib/simulator/robot_simulation.rb index de717d6..2947b1d 100644 --- a/lib/simulator/robot_simulation.rb +++ b/lib/simulator/robot_simulation.rb @@ -6,7 +6,7 @@ # They need also to implement radius so we know its geometry # ################### class RobotSimulation - attr_reader :facing + attr_reader :pose def initialize(world, driver, real_robot, formatter = nil) raise "A virtual robot needs virtual hardware" if driver.nil? @@ -44,6 +44,7 @@ def render ui = {} ui['x'] = @pose.position.x ui['y'] = @pose.position.y + ui['angle'] = @pose.angle ui['radius'] = radius ui['name'] = 'Roomba' ui diff --git a/lib/simulator/world.rb b/lib/simulator/world.rb index 825155c..fd1df51 100644 --- a/lib/simulator/world.rb +++ b/lib/simulator/world.rb @@ -41,16 +41,14 @@ def step (time_step) end - def collision_with?(robot) + def collision_with?(object) @boundaries.each do |boundary| - distance_to_boundary = boundary.distance_to(robot.pos) - robot.radius - return true if distance_to_boundary <= 0 + return true if boundary.distance_to(object) <= 0 end @obstacles.each do |obstacle| - distance = obstacle.position.distance_to(robot.pos) - return true if distance <= (obstacle.radius + robot.radius) + return true if obstacle.distance_to(object) <= 0 end false end From 989bef03faf33e6ef73f5082e534416e1eebd18e Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Wed, 6 Mar 2013 06:14:31 +0900 Subject: [PATCH 16/20] solve several bugs --- lib/calculations.rb | 4 ++-- lib/roomba_simulation.rb | 2 +- lib/simulator/plane.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/calculations.rb b/lib/calculations.rb index 5837972..7377834 100644 --- a/lib/calculations.rb +++ b/lib/calculations.rb @@ -2,11 +2,11 @@ module Calculations def calculate_spin_time(velocity, degree) # time = wheelbase * PI / 360degrees * degrees / velocity ABS # wheelbase might be different for different roombas, consider refactoring - ((((Roomba::ROOMBA_WHEELBASE * Math::PI) / 360) * degree.abs).to_f / velocity.to_f).abs + ((((Roomba::Specification::WHEELBASE * Math::PI) / 360) * degree.abs).to_f / velocity.to_f).abs end #spinning needs some work def calculate_spin_degree(velocity, time) - ((time.to_f * velocity.to_f) / ((Roomba::ROOMBA_WHEELBASE * Math::PI) / 360)) #/ 10**10 + ((time.to_f * velocity.to_f) / ((Roomba::Specification::WHEELBASE * Math::PI) / 360)) #/ 10**10 end end diff --git a/lib/roomba_simulation.rb b/lib/roomba_simulation.rb index 9b41651..2d9c2d3 100644 --- a/lib/roomba_simulation.rb +++ b/lib/roomba_simulation.rb @@ -15,7 +15,7 @@ def initialize(simulation) end def radius - Roomba::ROOMBA_RADIUS + Roomba::Specification::RADIUS end private diff --git a/lib/simulator/plane.rb b/lib/simulator/plane.rb index 3d9c5ab..0382edf 100644 --- a/lib/simulator/plane.rb +++ b/lib/simulator/plane.rb @@ -14,7 +14,7 @@ def initialize(normal, distance_to_origin) def distance_to(object) if object.class == Circle || object.class.ancestors.include?(RobotSimulation) - distance_to_point(object.position) - object.radius + distance_to_point(object.pos) - object.radius elsif object.class == Position distance_to_point(object) else From e94e7854ccc5798e6873879d54a9d9bd6dc0b598 Mon Sep 17 00:00:00 2001 From: Jordi Carres Date: Thu, 7 Mar 2013 04:05:22 +0900 Subject: [PATCH 17/20] added physical_shape to robot simulation --- lib/roomba_serial_simulation.rb | 4 ++-- lib/simulator/robot_simulation.rb | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index f877696..e465590 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -5,7 +5,7 @@ class RoombaSerialSimulation # currently the simulation settings are hardcoded in the initializer # need to refactor to allow various predefined or even random simulations - def initialize(sensors, formatter = nil) + def initialize(sensors=nil, formatter = nil) # The following are not to be set by the user @velocity = 0 @degree = 0 @@ -126,7 +126,7 @@ def prepare_readings(*args) # environment can leverage the same current X,Y coordinates def prepare_reading_7 # TODO: distinguish collisions with bumpers and not bumpers - if @bumpers.any?(&:got_collisions?) + if @bumpers && @bumpers.any?(&:got_collisions?) @readings.push 1 else @readings.push 0 diff --git a/lib/simulator/robot_simulation.rb b/lib/simulator/robot_simulation.rb index 2947b1d..9a01da1 100644 --- a/lib/simulator/robot_simulation.rb +++ b/lib/simulator/robot_simulation.rb @@ -29,6 +29,11 @@ def radius 100 #default radius, this method should be overloaded by every robot end + #this is needed so the physics simulation treat objects of this class as circles + def physical_shape + :circle + end + def step(step_time) @previous_pose = @pose.dup @pose = @pose.advance(@driver.calculate_distance(step_time), @driver.calculate_rotation(step_time)) From 3897e00f83da51fffb45a7fd85c68476a93f699c Mon Sep 17 00:00:00 2001 From: JordiPolo Date: Thu, 7 Mar 2013 04:37:28 +0900 Subject: [PATCH 18/20] removed code managed by Ein physics simulator --- Gemfile | 1 + Gemfile.lock | 2 + lib/simulator/angle.rb | 8 ---- lib/simulator/circle.rb | 23 ---------- lib/simulator/plane.rb | 33 -------------- lib/simulator/pose.rb | 27 ----------- lib/simulator/position.rb | 25 ----------- lib/simulator/robot_simulation.rb | 2 +- lib/simulator/simulator.rb | 44 +----------------- lib/simulator/vector.rb | 28 ------------ lib/simulator/world.rb | 75 ------------------------------- 11 files changed, 5 insertions(+), 263 deletions(-) delete mode 100644 lib/simulator/angle.rb delete mode 100644 lib/simulator/circle.rb delete mode 100644 lib/simulator/plane.rb delete mode 100644 lib/simulator/pose.rb delete mode 100644 lib/simulator/position.rb delete mode 100644 lib/simulator/vector.rb delete mode 100644 lib/simulator/world.rb diff --git a/Gemfile b/Gemfile index 14df0ed..47002e4 100644 --- a/Gemfile +++ b/Gemfile @@ -22,6 +22,7 @@ end group :development,:test do gem 'debugger' + gem 'ein' #physics simulator end group :development do diff --git a/Gemfile.lock b/Gemfile.lock index eadf8f6..338d13d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -49,6 +49,7 @@ GEM debugger-linecache (1.1.2) debugger-ruby_core_source (>= 1.1.1) debugger-ruby_core_source (1.1.6) + ein (0.0.2) erubis (2.7.0) eventmachine (0.12.10) execjs (1.3.0) @@ -148,6 +149,7 @@ PLATFORMS DEPENDENCIES coffee-rails debugger + ein jquery-rails json minitest diff --git a/lib/simulator/angle.rb b/lib/simulator/angle.rb deleted file mode 100644 index 849d234..0000000 --- a/lib/simulator/angle.rb +++ /dev/null @@ -1,8 +0,0 @@ -################ -# A class with convenience method related to angles -########## -class Angle - def self.degrees_to_radians(degrees) - degrees * Math::PI / 180 - end -end diff --git a/lib/simulator/circle.rb b/lib/simulator/circle.rb deleted file mode 100644 index ff0fe93..0000000 --- a/lib/simulator/circle.rb +++ /dev/null @@ -1,23 +0,0 @@ -################### -# Our obstacles are circles, but other shapes may come later -################### -class Circle - - attr_reader :position, :radius - - def initialize(position, radius) - @position, @radius = position, radius - end - - def distance_to(object) - if object.class == Circle || object.class.ancestors.include?(RobotSimulation) - @position.distance_to(object.pos) - ( @radius + object.radius) - elsif object.class == Position - @position.distance_to(object) - ( @radius ) - else - raise "can not calculate distance from Circle to #{object.class}" - end - end - - -end diff --git a/lib/simulator/plane.rb b/lib/simulator/plane.rb deleted file mode 100644 index 0382edf..0000000 --- a/lib/simulator/plane.rb +++ /dev/null @@ -1,33 +0,0 @@ -####################################### -# This is an infinite 2D plane with infinite mass and infinitely strong -# Objects and robots can collide with it. -# normal is the normal of the plane towards the origin -# distance_to_origin is the distance from the plane to the origin -####################################### -class Plane - attr_reader :normal, :distance_to_origin - - def initialize(normal, distance_to_origin) - @normal = normal - @distance_to_origin = distance_to_origin - end - - def distance_to(object) - if object.class == Circle || object.class.ancestors.include?(RobotSimulation) - distance_to_point(object.pos) - object.radius - elsif object.class == Position - distance_to_point(object) - else - raise "can not calculate distance from Plane to #{object.class}" - end - end - - private - - def distance_to_point(point) - projected_point = normal.project(point) - projected_point.x + projected_point.y + @distance_to_origin - end - -end - diff --git a/lib/simulator/pose.rb b/lib/simulator/pose.rb deleted file mode 100644 index ae34727..0000000 --- a/lib/simulator/pose.rb +++ /dev/null @@ -1,27 +0,0 @@ -################################ -# Pose of an object in a 2D environment -# Its position in 2D coordinates and its angle in degrees -# angle=0 => +y, angle=90 => +x, angle=180 => -y, angle=270 => -x -################################ -class Pose - - attr_reader :position - - def initialize(position, angle) - @position = position - @angle = angle - end - - #Create a new pose, modifying previous with a relative position and angle - def advance(distance, angle) - new_angle = @angle + angle - new_y = @position.y + distance * Math.cos(Angle.degrees_to_radians(new_angle)) - new_x = @position.x + distance * Math.sin(Angle.degrees_to_radians(new_angle)) - Pose.new(Position.new(new_x, new_y), new_angle) - end - - def to_s - "N: #@angle, #@position" - end - -end diff --git a/lib/simulator/position.rb b/lib/simulator/position.rb deleted file mode 100644 index 00660c4..0000000 --- a/lib/simulator/position.rb +++ /dev/null @@ -1,25 +0,0 @@ -######################## -# A 2D position -# Positions of objects and robots always represent -# the position if their centers -######################## -class Position - attr_accessor :x, :y - - def initialize(x,y) - @x, @y = x,y - end - - def distance_to(position) - Math.sqrt((@x - position.x)**2 + (@y - position.y)**2) # Pythagoras, miss you buddy. RIP - end - - def round - Position.new(@x.round, @y.round) - end - - def to_s - "x: #@x, y: #@y" - end - -end diff --git a/lib/simulator/robot_simulation.rb b/lib/simulator/robot_simulation.rb index 9a01da1..d8844b2 100644 --- a/lib/simulator/robot_simulation.rb +++ b/lib/simulator/robot_simulation.rb @@ -18,7 +18,7 @@ def initialize(world, driver, real_robot, formatter = nil) # Set defaults if not set in the initializer block # These defaults match the previously hard-coded values - @pose = Pose.new(Position.new(0,0),0) + @pose = Ein::Pose.new(Ein::Position.new(0,0),0) #TODO: formatter has to be a singleton everyone can use @formatter = formatter || Console.new diff --git a/lib/simulator/simulator.rb b/lib/simulator/simulator.rb index 36c59e6..7e91800 100644 --- a/lib/simulator/simulator.rb +++ b/lib/simulator/simulator.rb @@ -1,52 +1,10 @@ -require 'console' -require 'world' - ############################## # #This class is the highest level entity, controls how #the simulation behaves, contains the world and the robots # ############################### -class Simulator - attr_reader :world, :current_time, :formatter - - def initialize(formatter = nil) - @current_time = Time.now - @formatter = formatter || Console.new - @world = World.new - @running = false - end - - #TODO: should these methods add ! because they modify the simulation ? - def start - @running = true - Thread.abort_on_exception = true - Thread.new { run } - self - end - - def stop - @running = false - self - end - - def running? - @running - end - - private - - STEP = 0.01 - - def run - @formatter.info "Simulation started" - while (@running) - @current_time += STEP - @world.step(STEP) - sleep(STEP) - end - @formatter.info "Simulation terminated" - end +class Simulator < Ein::Simulator end diff --git a/lib/simulator/vector.rb b/lib/simulator/vector.rb deleted file mode 100644 index ef6e55b..0000000 --- a/lib/simulator/vector.rb +++ /dev/null @@ -1,28 +0,0 @@ -################################ -# A vector in 2D space -################################ -class Vector - attr_accessor :x, :y - - def initialize(x,y) - @x, @y = x,y - end - - def project(position) - Vector.new(@x * position.x, @y * position.y) - end - - def length - Math.sqrt(@x**2 + @y**2) - end - - def *(scalar) - @x += @x * scalar - @y += @y * scalar - end - - def to_s - "x: #@x, y: #@y" - end - -end diff --git a/lib/simulator/world.rb b/lib/simulator/world.rb deleted file mode 100644 index fd1df51..0000000 --- a/lib/simulator/world.rb +++ /dev/null @@ -1,75 +0,0 @@ -############################## -# -# This class represents the virtual world our simulation runs in -# -############################## -class World - - def initialize - @robots = [] - read_world - self - end - - def render - world_ui = {} - world_ui['boundaries'] = @boundaries - world_ui['obstacles'] = @obstacles - @robots.each do |r| - world_ui['robot'] = r.render - end - world_ui.to_json - end - - def spawn(*robots) - robots.each do |bot| - @robots.push(bot) - end - end - - def robot(index=0) - @robots[index] - end - - def step (time_step) - @robots.each do |robot| - robot.step(time_step) - if collision_with?(robot) - robot.step_back - end - end - end - - - def collision_with?(object) - - @boundaries.each do |boundary| - return true if boundary.distance_to(object) <= 0 - end - - @obstacles.each do |obstacle| - return true if obstacle.distance_to(object) <= 0 - end - false - end - - private - - def read_world - #TODO: read from external .yml or something - #TODO: mass and shape for the obstacles - #boundaries from left boundary clockwise. - @boundaries = [ - Plane.new(Vector.new(1,0), 1000), - Plane.new(Vector.new(0,-1), 800), - Plane.new(Vector.new(-1,0), 1000), - Plane.new(Vector.new(0,1), 800) - ] - @obstacles = [ - Circle.new(Position.new(0,500), 20), - Circle.new(Position.new(300,0), 20), - Circle.new(Position.new(-900,-700), 10) - ] - end - -end From d38035335f6e5a8442d0d78aa14815d510b16666 Mon Sep 17 00:00:00 2001 From: JordiPolo Date: Sun, 10 Mar 2013 13:22:36 +0900 Subject: [PATCH 19/20] get rid of own implementation of logger and use standard library, make it global --- README.md | 6 ++++ lib/roomba_serial_simulation.rb | 11 ++++---- lib/roomba_simulation.rb | 4 +-- lib/simulator/console.rb | 46 ------------------------------- lib/simulator/robot_simulation.rb | 6 ++-- lib/simulator/simulator.rb | 6 ++++ test/unit/world_test.rb | 36 ------------------------ 7 files changed, 21 insertions(+), 94 deletions(-) delete mode 100644 lib/simulator/console.rb delete mode 100644 test/unit/world_test.rb diff --git a/README.md b/README.md index cf1603b..8c3415f 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,12 @@ You should end up with a bump reading at N:90, X:126 Y:89 X:126 is the center point of Simulated Roomba. Add the radius of Roomba + the radius of the obstacle and it should be the same as the distance between X:126 and the default simulated obstacle. +If you want to get information of each step of the simulation do: + +```Ruby +LOGGER.level = Logger::DEBUG +``` + Or jump into the rails app and play around. ```` diff --git a/lib/roomba_serial_simulation.rb b/lib/roomba_serial_simulation.rb index e465590..348a778 100644 --- a/lib/roomba_serial_simulation.rb +++ b/lib/roomba_serial_simulation.rb @@ -5,13 +5,12 @@ class RoombaSerialSimulation # currently the simulation settings are hardcoded in the initializer # need to refactor to allow various predefined or even random simulations - def initialize(sensors=nil, formatter = nil) + def initialize(sensors=nil) # The following are not to be set by the user @velocity = 0 @degree = 0 @turning = false @readings = [] - @formatter = formatter || Console.new @bumpers = sensors #bumpers are the only sensors we have now @waiting_bytes = 0 @command_bytes = [] @@ -78,9 +77,9 @@ def dispatch_command when 149 prepare_readings(@command_bytes) when 128,130 - @formatter.info "Roomba API ready to receive commands" + LOGGER.info "Roomba API ready to receive commands" else - @formatter.debug "Command not implemented #{command}" + LOGGER.debug "Command not implemented #{command}" end end @@ -96,9 +95,9 @@ def setup_move(args) @velocity = signed_integer([args[0], args[1]]) moving = (@velocity.abs > 0) ? true : false if moving - @formatter.debug "Moving at #{@velocity}mm/s" + LOGGER.debug "Moving at #{@velocity}mm/s" else - @formatter.debug "Stopped moving" + LOGGER.debug "Stopped moving" end @degree = signed_integer([args[2], args[3]]) @turning = (@degree.abs == 1) ? true : false diff --git a/lib/roomba_simulation.rb b/lib/roomba_simulation.rb index 2d9c2d3..ea1a23e 100644 --- a/lib/roomba_simulation.rb +++ b/lib/roomba_simulation.rb @@ -3,14 +3,14 @@ # ####### class RoombaSimulation < RobotSimulation def initialize(simulation) - world = simulation.world bumpers = [ Bumper.new(self, -45, 30, world), Bumper.new(self, 45, 30, world) ] serial = RoombaSerialSimulation.new(bumpers) modify_roomba_internals(simulation) real_robot = Roomba.new('simulation', 0, 115200, serial) + world = simulation.world world.spawn(self) - super(world, serial, real_robot, simulation.formatter) + super(world, serial, real_robot) self end diff --git a/lib/simulator/console.rb b/lib/simulator/console.rb deleted file mode 100644 index e9eeb1f..0000000 --- a/lib/simulator/console.rb +++ /dev/null @@ -1,46 +0,0 @@ -############### -# One of the possible formatters of our information. -# It outputs to the console. By default only info messages will be output -# create it with new(:debug) to make it much more verbose -############### -class Console - - def initialize(level=:info) - if available?(level) - @level = level - else - @level = :info - end - end - - def info(text) - puts(text) if should_print(:info) - end - - def debug(text) - puts(text) if should_print(:debug) - end - - private - def available_levels - [:debug, :info, :quiet] - end - - def available?(level) - available_levels.include?(level) - end - - def priority(level) - available_levels.index(level) - end - - def should_print(level) - priority(level) >= priority(@level) - end - - def puts(text) - return if defined?(Rails) && Rails.env == :test - Kernel.puts text - end - -end diff --git a/lib/simulator/robot_simulation.rb b/lib/simulator/robot_simulation.rb index d8844b2..7f66f73 100644 --- a/lib/simulator/robot_simulation.rb +++ b/lib/simulator/robot_simulation.rb @@ -8,7 +8,7 @@ class RobotSimulation attr_reader :pose - def initialize(world, driver, real_robot, formatter = nil) + def initialize(world, driver, real_robot) raise "A virtual robot needs virtual hardware" if driver.nil? raise "A virtual robot needs a real robot implementation" if real_robot.nil? raise "A virtual robot needs a simulation" if world.nil? @@ -19,8 +19,6 @@ def initialize(world, driver, real_robot, formatter = nil) # Set defaults if not set in the initializer block # These defaults match the previously hard-coded values @pose = Ein::Pose.new(Ein::Position.new(0,0),0) - #TODO: formatter has to be a singleton everyone can use - @formatter = formatter || Console.new yield self if block_given? end @@ -37,7 +35,7 @@ def physical_shape def step(step_time) @previous_pose = @pose.dup @pose = @pose.advance(@driver.calculate_distance(step_time), @driver.calculate_rotation(step_time)) - @formatter.debug "#@pose" + LOGGER.debug "#@pose" end #TODO: this is fugly, should be a better way to stop on obstacles diff --git a/lib/simulator/simulator.rb b/lib/simulator/simulator.rb index 7e91800..4161e05 100644 --- a/lib/simulator/simulator.rb +++ b/lib/simulator/simulator.rb @@ -8,3 +8,9 @@ class Simulator < Ein::Simulator end +##### +# Our logger, default level is info. +# Please use only .info and .debug levels +##### +LOGGER = Logger.new(STDOUT) +LOGGER.level = Logger::INFO diff --git a/test/unit/world_test.rb b/test/unit/world_test.rb deleted file mode 100644 index da7d798..0000000 --- a/test/unit/world_test.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'test_helper' - -# TO RUN: -# ruby -Itest test/unit/world_test.rb - -describe World do - - describe 'basic initialization' do - - let(:world) do - World.new(RoombaSimulation.new) - end - - it "creates an instance" do - world.must_be_instance_of World - end - - it "should create a default robot" do - world.robot.must_be_instance_of RoombaSimulation - end - - it "should not create more than 1 initial robot" do - world.robot(1).must_equal nil - end - - it "should render the boundaries" do - world.render.include?("boundaries").must_equal true - end - - it "should render the obstacles" do - world.render.include?("obstacles").must_equal true - end - end - -end - From b131d6d61701a544155e07363f4f6ed4e20aa4e1 Mon Sep 17 00:00:00 2001 From: JordiPolo Date: Sun, 10 Mar 2013 13:50:55 +0900 Subject: [PATCH 20/20] revert moving a line around --- lib/roomba_simulation.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/roomba_simulation.rb b/lib/roomba_simulation.rb index ea1a23e..ce9f7db 100644 --- a/lib/roomba_simulation.rb +++ b/lib/roomba_simulation.rb @@ -3,11 +3,11 @@ # ####### class RoombaSimulation < RobotSimulation def initialize(simulation) + world = simulation.world bumpers = [ Bumper.new(self, -45, 30, world), Bumper.new(self, 45, 30, world) ] serial = RoombaSerialSimulation.new(bumpers) modify_roomba_internals(simulation) real_robot = Roomba.new('simulation', 0, 115200, serial) - world = simulation.world world.spawn(self) super(world, serial, real_robot)