rubotz 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,95 @@
1
+ = Rubotz
2
+
3
+ Rubotz is a Ruby library which generates and compiles NXC programs for Lego Mindstorms
4
+ NXT robots.
5
+
6
+ You can program your robot in Ruby, including the compilation phase, and then simply
7
+ copy the generated executable from your computer to your Mindstorms robot with third-party
8
+ programs such as NXTBrowser.
9
+
10
+ = Proof-Of-Concept Release Only
11
+
12
+ Rubotz v0.0.1 is a proof-of-concept release. It implements only a subset of available motor
13
+ functionality and only one sensor (the ultrasonic sensor). Mindstorms ships with 4 sensors
14
+ and additional third-party sensors are available as well. NXC supports a multi-threaded
15
+ model, which is very cool; Rubotz currently only supports one ("main") thread.
16
+
17
+ Support for multiple threads is planned; support for all stock sensors and the full
18
+ range of motor functionality is planned as well.
19
+
20
+ = Documentation Is For Computers To Read
21
+
22
+ I don't really believe in documentation; I believe that your documentation should consist
23
+ entirely of your specs. Specs are documentation which you can run as code, and therefore
24
+ they are *self-verifying* documentation; any other kind of documentation is really just a
25
+ statement of opinion. However, I definitely want people who are interested to be able to
26
+ use the software!
27
+
28
+ The specs dir absolutely contains the best documentation, but I've also made an effort to
29
+ augment that with text and RDoc. However, if you're confused about something, the specs are
30
+ *by definition* the canonical example of how to do it.
31
+
32
+ == How To Program Your Robot In Ruby
33
+
34
+ Rubotz.compile takes an options hash, using it as faux keyword arguments in the Rails style.
35
+ Rubotz.program takes a block, with Ruby code which it then translates into NXC.
36
+
37
+ The examples directory contains an example as runnable code - logically enough - but I'm
38
+ repeating it here to demonstrate.
39
+
40
+ This code is based on a robot I built which aims an ultrasonic sensor at the floor and drives
41
+ forward, backing away and turning if it encounters a dropoff indicating a table edge.
42
+
43
+ Check it out on YouTube: http://www.youtube.com/watch?v=uQ4BwxH7uoY
44
+ (it's pretty straightforward).
45
+
46
+ Rubotz.compile :file => "rubotz",
47
+ :program => (Rubotz.program do
48
+ the_robot "drives around the table scanning its ultrasonic sensor"
49
+ initializing do
50
+ short ultrasonic_sensor
51
+ activate(UltrasonicSensor.port(4))
52
+ end
53
+ looping do
54
+ clear_screen
55
+ monitoring(UltrasonicSensor) do |ultrasonic_sensor|
56
+ ultrasonic_sensor.> 30 do
57
+ display :line => 7, :text => "Danger"
58
+ display :line => 8, :number => ultrasonic_sensor
59
+ sound "! Attention.rso"
60
+ motor :synchronized => true,
61
+ :direction => :reverse,
62
+ :ports => [:B, :C],
63
+ :power => 25,
64
+ :turn => 0
65
+ wait 1500
66
+ motor :synchronized => true,
67
+ :direction => :reverse,
68
+ :ports => [:B, :C],
69
+ :power => 25,
70
+ :turn => 100
71
+ wait 325
72
+ end
73
+ ultrasonic_sensor.<= 30 do
74
+ display :line => 7, :text => "Safe"
75
+ display :line => 8, :number => ultrasonic_sensor
76
+ motor :synchronized => true,
77
+ :direction => :forwards,
78
+ :ports => [:B, :C],
79
+ :power => 35,
80
+ :turn => 5
81
+ end
82
+ end
83
+ wait 25
84
+ end
85
+ end)
86
+
87
+ The above code successfully generates NXC code for the table robot, and successfully
88
+ compiles that code using my NXC install into a format which successfully runs on my Mindstorms
89
+ robot brain. To transfer the generated code to the robot brain, I use NXTBrowser on OS X:
90
+
91
+ http://web.mac.com/carstenm/Lego/NXT/Entries/2006/12/4_NXTBrowser_-_Bluetooth_connectivity_on_an_Intel_Mac.html
92
+
93
+ I'm very confident that alternatives exist for other platforms. I'm also very confident this will
94
+ work on other people's computers and Mindstorms brains as well as my own, but definitely correct me
95
+ if I got that wrong! (You can reach me by e-mail at gilesb@gmail.com.)
@@ -0,0 +1 @@
1
+ nxt_home: /Users/giles/programming/nxt
@@ -0,0 +1,66 @@
1
+ # If you don't know what you're doing here, please be careful, as you **are** using this software
2
+ # at your own risk. Some Mindstorms users are kids; while a kid could figure this out, you would
3
+ # have to be careful to use NXTBrowser correctly, and be careful with Ruby too. Using this software
4
+ # at your own risk means that if your Mindstorms is destroyed, I won't buy you a new one, and that
5
+ # goes double for your computer.
6
+
7
+ # This code is based on a robot I built which aims an ultrasonic sensor at the floor and drives
8
+ # forward, backing away and turning if it encounters a dropoff indicating a table edge.
9
+
10
+ # Check it out on YouTube: http://www.youtube.com/watch?v=uQ4BwxH7uoY
11
+ # (it's pretty straightforward).
12
+
13
+ # If you build that robot, or an equivalent robot, and you have an appropriate config file in
14
+ # the /config dir - replacing the default, which points to a subdir of my own home dir - you
15
+ # should be able to auto-generate an NXC program simply by running the Ruby code in this file.
16
+ # You can then transfer the .rxe generated executable to your Mindstorms NXT robot using
17
+ # a third-party app like (for instance) NXTBrowser:
18
+ # http://web.mac.com/carstenm/Lego/NXT/Entries/2006/12/4_NXTBrowser_-_Bluetooth_connectivity_on_an_Intel_Mac.html
19
+
20
+ # I used this program to drive development of Rubotz v0.0.1, so check out the table wanderer
21
+ # spec for a lot more detail on how to use Rubotz. You will need to have NXC installed on your
22
+ # system, with a working compiler.
23
+
24
+ require File.join(File.dirname(__FILE__), '../lib/rubotz')
25
+ include Rubotz
26
+
27
+ Rubotz.compile :file => "rubotz",
28
+ :program => (Rubotz.program do
29
+ the_robot "drives around the table scanning its ultrasonic sensor"
30
+ initializing do
31
+ short ultrasonic_sensor
32
+ activate(UltrasonicSensor.port(4))
33
+ end
34
+ looping do
35
+ clear_screen
36
+ monitoring(UltrasonicSensor) do |ultrasonic_sensor|
37
+ ultrasonic_sensor.> 30 do
38
+ display :line => 7, :text => "Danger"
39
+ display :line => 8, :number => ultrasonic_sensor
40
+ sound "! Attention.rso"
41
+ motor :synchronized => true,
42
+ :direction => :reverse,
43
+ :ports => [:B, :C],
44
+ :power => 25,
45
+ :turn => 0
46
+ wait 1500
47
+ motor :synchronized => true,
48
+ :direction => :reverse,
49
+ :ports => [:B, :C],
50
+ :power => 25,
51
+ :turn => 100
52
+ wait 325
53
+ end
54
+ ultrasonic_sensor.<= 30 do
55
+ display :line => 7, :text => "Safe"
56
+ display :line => 8, :number => ultrasonic_sensor
57
+ motor :synchronized => true,
58
+ :direction => :forwards,
59
+ :ports => [:B, :C],
60
+ :power => 35,
61
+ :turn => 5
62
+ end
63
+ end
64
+ wait 25
65
+ end
66
+ end)
@@ -0,0 +1,58 @@
1
+ %w{erb
2
+ yaml
3
+ lib/rubotz/generator
4
+ lib/rubotz/ultrasonic_sensor}.each {|lib| require lib}
5
+
6
+ module Rubotz
7
+ SIMPLE_WHILE = "#{File.dirname(__FILE__)}/../templates/simple_while.nxc.erb"
8
+ CONFIG = YAML.load_file("#{File.dirname(__FILE__)}/../config/nxt_home.yaml")
9
+
10
+ class << self
11
+ # Rubotz.compile takes two keyword arguments - a filename and a Rubotz.program
12
+ # call, which takes a block. Here's the simplest possible instance:
13
+ #
14
+ # Rubotz.compile(:filename => "f", :program => (Rubotz.program {true}))
15
+ #
16
+ # Technically even simpler instances are *possible*, but would probably do bad
17
+ # things.
18
+ #
19
+ # One bad thing to watch out for is that there is a hard limit on the number of
20
+ # characters you can use in a filename, and I'm not yet sure what that is. However,
21
+ # beyond a certain number of characters, Mindstorms won't recognize the .rxe
22
+ # suffix on your generated executable, and when that happens, it assumes the file
23
+ # is data rather than a program. The workaround is obvious: use short file names.
24
+ #
25
+ # One other caveat: compile saves your generated code, and will overwrite existing
26
+ # generated code if it was generated with the same :filename. Keep an eye on your
27
+ # nxt/code dir, or use unique filenames, or be bold and fearless. Whatever. It's up
28
+ # to you.
29
+ def compile(options = {})
30
+ filename = options[:file].gsub(/ /, "_")
31
+ File.open("#{Rubotz::CONFIG['nxt_home']}/code/#{filename}.nxc", "w") do |file|
32
+ file.puts options[:program]
33
+ end
34
+ system ("cd #{Rubotz::CONFIG['nxt_home']}; " +
35
+ "./nbc " +
36
+ "-O=bin/#{filename}.rxe " +
37
+ "code/#{filename}.nxc")
38
+ end
39
+ # Rubotz.generate is an internal method. It's not marked private because I don't
40
+ # really care, I kind of have a laissez-faire, Perlish attitude to methods, if you
41
+ # shoot yourself in the foot, hey, it's your foot. However, it's really only useful
42
+ # if you're writing new generator code.
43
+ def generate(&block)
44
+ Generator.new(&block).code
45
+ end
46
+ # Rubotz.program takes a block of Ruby code and evals it in the context of a Generator,
47
+ # which knows how to turn that code into NXC code. It then runs the generated NXC code
48
+ # through ERb, to mesh with existing boilerplate stuff in NXC. Note however that it does
49
+ # not save a file, it simply returns the generated code in the ERb template as text.
50
+ def program(&block)
51
+ generator = Generator.new(&block)
52
+ initialization_section = generator.initialization_section
53
+ loop_body = generator.loop_body
54
+ comment = generator.comment || ""
55
+ ERB.new(File.read(SIMPLE_WHILE)).result(binding)
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,103 @@
1
+ require 'lib/rubotz/motor_generator'
2
+ module Rubotz
3
+ # Pretty much all the methods that you program Rubotz robots with live in this class. (It
4
+ # may be wiser to isolate those methods in an API module and then mix that in, since that
5
+ # would make the documentation clearer, but this is how it works for now.)
6
+ #
7
+ # Instantiating an object of this class should never be necessary unless you're rewriting
8
+ # the main module or doing similar development. In the course of actually programming a robot,
9
+ # you just use the methods of the class as if they were regular Ruby keywords.
10
+ class Generator
11
+ attr_accessor :code,
12
+ :initialization_section,
13
+ :loop_body,
14
+ :comment
15
+ def initialize(&block)
16
+ @code = ""
17
+ instance_eval(&block)
18
+ end
19
+
20
+ # Example: activate(UltrasonicSensor.port(4)). You need to do this in the initialization
21
+ # section of your program, before going into the loop body and actually accessing the sensor.
22
+ def activate(sensor)
23
+ @code += sensor
24
+ end
25
+ # Does what it says on the tin.
26
+ def clear_screen
27
+ @code += "ClearScreen();\n"
28
+ end
29
+ # The display method takes a hash of symbols and strings, and uses that to write to the
30
+ # Mindstorms NXT display. For example:
31
+ #
32
+ # display :line => 7, :text => "Hello World"
33
+ # display :line => 8, :number => ultrasonic_sensor
34
+ #
35
+ # (Although support for variable interpolation is currently imperfect, it works perfectly
36
+ # for this particular method.)
37
+ #
38
+ # The NXT brain has 8 lines on its display, and you probably have to call clear_screen
39
+ # before you update it. Numbering starts at 1, not 0.
40
+ def display(options = {})
41
+ line_number = options[:line] || 1
42
+ if options.include?(:text)
43
+ @code += "TextOut(0, LCD_LINE#{line_number}, \"#{options[:text]}\");\n"
44
+ elsif options.include?(:number)
45
+ @code += "NumOut(0, LCD_LINE#{line_number}, #{options[:number]});\n"
46
+ end
47
+ end
48
+ # The current Rubotz model is very limited; you have one constantly-running loop, and an
49
+ # initialization section beforehand, which is where you declare variables and activate sensors.
50
+ # Use initializing to wrap code which goes in the initialization section of a loop, rather
51
+ # than in the loop itself.
52
+ def initializing(&block)
53
+ @initialization_section = Rubotz.generate(&block)
54
+ end
55
+ # The current Rubotz model is very limited; you have one constantly-running loop, and an
56
+ # initialization section beforehand, which is where you declare variables and activate sensors.
57
+ # Use looping to wrap code which goes in the constantly-running loop.
58
+ def looping(&block)
59
+ @loop_body = Rubotz.generate(&block)
60
+ end
61
+ # This method_missing assumes missing methods are actually references to as-yet-undefined
62
+ # variables. Although variable interpolation is currently imperfect in Rubotz, this method
63
+ # causes all its successes.
64
+ def method_missing(method, *args)
65
+ "#{method}"
66
+ end
67
+ # Use monitoring to wrap blocks which check a sensor for data and then make decisions based
68
+ # on that data. Its args are a sensor class, and the block you're wrapping in it.
69
+ def monitoring(sensor_class, &block)
70
+ @code += sensor_class.new.instance_eval(&block)
71
+ end
72
+ # The motor method simply passes a MotorGenerator the options you specify; see the MotorGenerator
73
+ # RDoc for details on how to use this method, and what those options should be.
74
+ def motor(options = {})
75
+ @code += MotorGenerator.new(options).generate
76
+ end
77
+ # The short method allows you to declare a variable of the short type.
78
+ def short(variable_name)
79
+ @code += "short #{variable_name};\n"
80
+ end
81
+ # This method plays a sound which already exists on your NXC. Third-party utilities such as
82
+ # NXTBrowser make transferring new sounds to your Mindstorms NXT a piece of cake.
83
+ def sound(filename)
84
+ @code += "PlayFile(\"#{filename}\");\n"
85
+ end
86
+ # This method merely generates a comment in the generated NXC source code.
87
+ def the_robot(does_something)
88
+ @comment = "// the robot #{does_something}\n"
89
+ end
90
+ # This method pauses program execution. It doesn't cause the _robot_ to wait, but the
91
+ # _program_. This makes it a vital method. After you turn your motors on and start moving
92
+ # in a given direction, you need to wait an arbitrary amount of time before you do anything
93
+ # else; otherwise you'll simply turn on your motors and the program will immediately jump
94
+ # to the next instruction without actually driving anywhere.
95
+ #
96
+ # I think wait works in terms of milliseconds, but honestly I don't know. The units are some
97
+ # kind of fraction of a second which appears to work in some order of magnitude. In practical
98
+ # terms, just use round numbers and experiment to see what works for you.
99
+ def wait(number)
100
+ @code += "Wait(#{number});\n"
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,49 @@
1
+ module Rubotz
2
+ # The MotorGenerator initializes with an options hash instead of a block. For example:
3
+ #
4
+ # motor :synchronized => true,
5
+ # :direction => :reverse,
6
+ # :ports => [:B, :C],
7
+ # :power => 25,
8
+ # :turn => 0
9
+ #
10
+ # The NXC API has a number of different options for motors. This seemed like the best way
11
+ # to implement them, but currently MotorGenerator implements only a subset of available
12
+ # motor functionality, and if/when that functionality expands, the structure of the class
13
+ # may change.
14
+ #
15
+ # Currently MotorGenerators can only generate Sync commands, and only know how to power
16
+ # ports B and C.
17
+ #
18
+ # The methods in this class are basically irrelevant from a user perspective. What matters
19
+ # is the keywords you use, or more precisely the symbol keys to the options hash. First the
20
+ # lame stuff: :synchronized makes no difference at all, and the only legal values for :ports
21
+ # is [:B, :C], and you have to specify that. Sorry.
22
+ #
23
+ # However, :direction, :power, and :turn are all actually valid and usable. They're all also
24
+ # reasonably obvious. :direction is either :reverse or :forwards. I'm pretty sure :power maxes
25
+ # out at 100, and I know it starts at 0. :turn also starts at 0 but honestly I have no idea
26
+ # what its legal maximum is, if any. Feel free to experiment with this!
27
+ class MotorGenerator
28
+ def initialize(options = {})
29
+ @options = options
30
+ end
31
+ def generate
32
+ "#{motor_command}(#{ports}, #{power_and_turn});\n"
33
+ end
34
+ def motor_command
35
+ if :reverse == @options[:direction] # && @options[:synchronized]
36
+ "OnRevSync"
37
+ elsif :forwards == @options[:direction] # && @options[:synchronized]
38
+ "OnFwdSync"
39
+ end
40
+ end
41
+ def power_and_turn
42
+ "#{@options[:power] || 0}, #{@options[:turn] || 0}"
43
+ end
44
+ def ports
45
+ "OUT_BC" if [:B, :C] == @options[:ports]
46
+ end
47
+ end
48
+ end
49
+
@@ -0,0 +1,57 @@
1
+ module Rubotz
2
+ # The UltrasonicSensor class is a code generator for the NXT ultrasonic sensor. It provides
3
+ # simple value comparison methods, value assignment NXC code, and an activation class method.
4
+ # Its variable is currently hard-coded; this is planned to change.
5
+ #
6
+ # Ultimately all XyzSensors will inherit from a base Sensor class. This class will very likely be
7
+ # the only one to override Sensor's #port method (see NXC docs for why).
8
+ class UltrasonicSensor
9
+ # Create the UltrasonicSensor, with a variable declaration and relevant NXC API code.
10
+ def initialize
11
+ @code = "ultrasonic_sensor = SensorUS(S4);\n"
12
+ end
13
+ class << self
14
+ # Activation code for initialization blocks.
15
+ def port(number)
16
+ "SetSensorLowspeed(S#{number});\n"
17
+ end
18
+ end
19
+
20
+ # Utility method for variable interpolation in code which refers to this sensor.
21
+ def to_s
22
+ "ultrasonic_sensor"
23
+ end
24
+ # UltrasonicSensor#> generates an if block around the >, the sensor itself (as an NXC
25
+ # variable), and the value to be compared against. It then executes arbitrary Ruby within
26
+ # the context of that if block. In practice this means turning Rubotz code into NXC code.
27
+ # (The instance method approach to value comparison is definitely a bit of a kludge, but it
28
+ # was a lot less work for me personally than implementing a parser.)
29
+ def > (number, &block)
30
+ @code += "if (ultrasonic_sensor > #{number}) {\n" + Rubotz.generate(&block) + "}\n"
31
+ end
32
+ # UltrasonicSensor#>= generates an if block around the >=, the sensor itself (as an NXC
33
+ # variable), and the value to be compared against. It then executes arbitrary Ruby within
34
+ # the context of that if block. In practice this means turning Rubotz code into NXC code.
35
+ # (The instance method approach to value comparison is definitely a bit of a kludge, but it
36
+ # was a lot less work for me personally than implementing a parser.)
37
+ def >= (number, &block)
38
+ @code += "if (ultrasonic_sensor >= #{number}) {\n" + Rubotz.generate(&block) + "}\n"
39
+ end
40
+ # UltrasonicSensor#< generates an if block around the <, the sensor itself (as an NXC
41
+ # variable), and the value to be compared against. It then executes arbitrary Ruby within
42
+ # the context of that if block. In practice this means turning Rubotz code into NXC code.
43
+ # (The instance method approach to value comparison is definitely a bit of a kludge, but it
44
+ # was a lot less work for me personally than implementing a parser.)
45
+ def < (number, &block)
46
+ @code += "if (ultrasonic_sensor < #{number}) {\n" + Rubotz.generate(&block) + "}\n"
47
+ end
48
+ # UltrasonicSensor#<= generates an if block around the <=, the sensor itself (as an NXC
49
+ # variable), and the value to be compared against. It then executes arbitrary Ruby within
50
+ # the context of that if block. In practice this means turning Rubotz code into NXC code.
51
+ # (The instance method approach to value comparison is definitely a bit of a kludge, but it
52
+ # was a lot less work for me personally than implementing a parser.)
53
+ def <= (number, &block)
54
+ @code += "if (ultrasonic_sensor <= #{number}) {\n" + Rubotz.generate(&block) + "}\n"
55
+ end
56
+ end
57
+ end
File without changes
@@ -0,0 +1,14 @@
1
+ require 'rubygems'
2
+ SPEC = Gem::Specification.new do |s|
3
+ s.name = s.rubyforge_project = "rubotz"
4
+ s.version = "0.0.1"
5
+ s.author = "Giles Bowkett"
6
+ s.email = "gilesb@gmail.com"
7
+ s.homepage = "http://rubotz.rubyforge.org"
8
+ s.platform = Gem::Platform::RUBY
9
+ s.summary = "Code generator for Lego Mindstorms NXT. Proof-of-concept release only."
10
+ s.files = Dir.glob("**/*")
11
+ s.require_path = "lib"
12
+ s.has_rdoc = true
13
+ s.extra_rdoc_files = ["README"]
14
+ end
@@ -0,0 +1,24 @@
1
+ # this spec is painfully slow. everything else runs fast, but this one example can take a
2
+ # second and a half. I'm breaking BDD rules here with such a slow, unmocked spec, but it was
3
+ # useful for me to make sure the compiler stuff really worked. this spec lives in slow_spec
4
+ # rather than spec so that you can do the usual "spec spec" and get the majority of specs
5
+ # taken care of super-quick.
6
+
7
+ require "lib/rubotz"
8
+
9
+ describe Rubotz, "compiling an empty program" do
10
+
11
+ before(:each) do
12
+ @blank_defaults = {:program => (Rubotz.program do ; end),
13
+ :file => "simple program"}
14
+ end
15
+
16
+ it "puts output in the output dir" do
17
+ Rubotz.compile(@blank_defaults)
18
+ File.exists?("#{Rubotz::CONFIG["nxt_home"]}/bin/simple_program.rxe").should be_true
19
+ end
20
+
21
+ after(:all) do
22
+ system("cd #{Rubotz::CONFIG["nxt_home"]}; rm code/simple_program.nxc bin/simple_program.rxe")
23
+ end
24
+ end
@@ -0,0 +1,17 @@
1
+ require "lib/rubotz"
2
+
3
+ describe Rubotz, "generating an empty program" do
4
+
5
+ it "generates blank programs in uncompiled NXC, not counting indentation" do
6
+ simple_while =<<SIMPLE_WHILE
7
+ #include "NXCDefs.h"
8
+ task main() {
9
+
10
+ while(true) {
11
+
12
+ }
13
+ }
14
+ SIMPLE_WHILE
15
+ (Rubotz.program do ; end).gsub(/\s+/, " ").should == simple_while.gsub(/\s+/, " ")
16
+ end
17
+ end
@@ -0,0 +1,211 @@
1
+ # This code is based on a robot I built which aims an ultrasonic sensor at the floor and drives
2
+ # forward, backing away and turning if it encounters a dropoff indicating a table edge.
3
+
4
+ # Check it out on YouTube: http://www.youtube.com/watch?v=uQ4BwxH7uoY
5
+ # (it's pretty straightforward).
6
+
7
+ require "lib/rubotz"
8
+ include Rubotz
9
+
10
+ describe Rubotz do
11
+ before(:each) do
12
+ @the_main_program_in_nxc =<<THE_MAIN_PROGRAM_IN_NXC
13
+ #include "NXCDefs.h"
14
+ // the robot drives around the table scanning its ultrasonic sensor
15
+ task main() {
16
+ short ultrasonic_sensor;
17
+ SetSensorLowspeed(S4);
18
+ while(true) {
19
+ ClearScreen();
20
+ ultrasonic_sensor = SensorUS(S4);
21
+ if (ultrasonic_sensor > 30) {
22
+ TextOut(0, LCD_LINE7, "Danger");
23
+ NumOut(0, LCD_LINE8, ultrasonic_sensor);
24
+ PlayFile("! Attention.rso");
25
+ OnRevSync(OUT_BC, 25, 0);
26
+ Wait(1500);
27
+ OnRevSync(OUT_BC, 25, 100);
28
+ Wait(325);
29
+ }
30
+ if (ultrasonic_sensor <= 30) {
31
+ TextOut(0, LCD_LINE7, "Safe");
32
+ NumOut(0, LCD_LINE8, ultrasonic_sensor);
33
+ OnFwdSync(OUT_BC, 35, 5);
34
+ }
35
+ Wait(25);
36
+ }
37
+ }
38
+ THE_MAIN_PROGRAM_IN_NXC
39
+ end
40
+
41
+ it "generates unindented single-threaded code for a robot which wanders a table without falling off" do
42
+ (Rubotz.program do
43
+ the_robot "drives around the table scanning its ultrasonic sensor"
44
+ initializing do
45
+ short ultrasonic_sensor
46
+ activate(UltrasonicSensor.port(4))
47
+ end
48
+ looping do
49
+ clear_screen
50
+ monitoring(UltrasonicSensor) do |ultrasonic_sensor|
51
+ ultrasonic_sensor.> 30 do
52
+ display(:line => 7, :text => "Danger")
53
+ display(:line => 8, :number => ultrasonic_sensor)
54
+ sound("! Attention.rso")
55
+ motor(:synchronized => true,
56
+ :direction => :reverse,
57
+ :ports => [:B, :C],
58
+ :power => 25,
59
+ :turn => 0)
60
+ wait(1500)
61
+ motor(:synchronized => true,
62
+ :direction => :reverse,
63
+ :ports => [:B, :C],
64
+ :power => 25,
65
+ :turn => 100)
66
+ wait(325)
67
+ end
68
+ ultrasonic_sensor.<= 30 do
69
+ display(:line => 7, :text => "Safe")
70
+ display(:line => 8, :number => ultrasonic_sensor)
71
+ motor(:synchronized => true,
72
+ :direction => :forwards,
73
+ :ports => [:B, :C],
74
+ :power => 35,
75
+ :turn => 5)
76
+ end
77
+ end
78
+ wait(25)
79
+ end
80
+ end).gsub(/\s+/, " ").should == @the_main_program_in_nxc.gsub(/\s+/, " ")
81
+ end
82
+
83
+ it "indents generated code correctly" do
84
+ # (Rubotz.indent(@the_main_program_in_nxc.gsub(/\s+/, " "))).should == @the_main_program_in_nxc
85
+ pending "implement"
86
+ end
87
+
88
+ it "somehow merges its indentation with the indentation it gets from ERb" do
89
+ pending "I have no idea"
90
+ end
91
+
92
+ it "creates initialization sections with arbitrary code" do
93
+ (Generator.new do
94
+ initializing do
95
+ short ultrasonic_sensor
96
+ end
97
+ end).initialization_section.should == "short ultrasonic_sensor;\n"
98
+ end
99
+
100
+ it "creates loop bodies with arbitrary code" do
101
+ (Generator.new do
102
+ looping do
103
+ short ultrasonic_sensor
104
+ end
105
+ end).loop_body.should == "short ultrasonic_sensor;\n"
106
+ end
107
+
108
+ it "declares short variables" do
109
+ (Rubotz.generate {short ultrasonic_sensor}).should == "short ultrasonic_sensor;\n"
110
+ end
111
+
112
+ it "has a #the_robot method which generates comments" do
113
+ (Generator.new do
114
+ the_robot "drives around the table scanning its ultrasonic sensor"
115
+ end).comment.should == "// the robot drives around the table scanning its ultrasonic sensor\n"
116
+ end
117
+
118
+ it "activates the ultrasonic sensor" do
119
+ (Rubotz.generate {activate(UltrasonicSensor.port(4))}).should == "SetSensorLowspeed(S4);\n"
120
+ end
121
+
122
+ it "generates variables and ifs for testing the ultrasonic sensor (without indentation)" do
123
+ ultrasonic_nxc =<<ULTRASONIC_NXC
124
+ ultrasonic_sensor = SensorUS(S4);
125
+ if (ultrasonic_sensor > 30) {
126
+ TextOut(0, LCD_LINE7, "greater than 30");
127
+ }
128
+ if (ultrasonic_sensor < 23) {
129
+ TextOut(0, LCD_LINE7, "less than 23");
130
+ }
131
+ if (ultrasonic_sensor >= 30) {
132
+ TextOut(0, LCD_LINE7, "greater than or equal to 30");
133
+ }
134
+ if (ultrasonic_sensor <= 23) {
135
+ TextOut(0, LCD_LINE7, "less than or equal to 23");
136
+ }
137
+ ULTRASONIC_NXC
138
+ (Rubotz.generate do
139
+ monitoring(UltrasonicSensor) do |ultrasonic_sensor|
140
+ ultrasonic_sensor.> 30 do
141
+ display(:line => 7, :text => "greater than 30")
142
+ end
143
+ ultrasonic_sensor.< 23 do
144
+ display(:line => 7, :text => "less than 23")
145
+ end
146
+ ultrasonic_sensor.>= 30 do
147
+ display(:line => 7, :text => "greater than or equal to 30")
148
+ end
149
+ ultrasonic_sensor.<= 23 do
150
+ display(:line => 7, :text => "less than or equal to 23")
151
+ end
152
+ end
153
+ end).gsub(/\s+/, " ").should == ultrasonic_nxc.gsub(/\s+/, " ")
154
+ end
155
+
156
+ it "generates clear screen code" do
157
+ (Rubotz.generate {clear_screen}).should == "ClearScreen();\n"
158
+ end
159
+
160
+ it "generates display code for text and numbers" do
161
+ (Rubotz.generate {display(:line => 7,
162
+ :text => "Safe")}).should == "TextOut(0, LCD_LINE7, \"Safe\");\n"
163
+ (Rubotz.generate {display(:line => 8,
164
+ :number => 25)}).should == "NumOut(0, LCD_LINE8, 25);\n"
165
+ end
166
+
167
+ it "generates display code with inline variables" do
168
+ (Rubotz.generate {display(:line => 8,
169
+ :number => ultrasonic_sensor)}).should == "NumOut(0, LCD_LINE8, ultrasonic_sensor);\n"
170
+ end
171
+
172
+ it "generates play sound code" do
173
+ (Rubotz.generate {sound("! Attention.rso")}).should == "PlayFile(\"! Attention.rso\");\n"
174
+ end
175
+
176
+ it "generates motor control code" do
177
+ (Rubotz.generate {motor(:synchronized => true,
178
+ :direction => :reverse,
179
+ :ports => [:B, :C],
180
+ :power => 25,
181
+ :turn => 100)}).should == "OnRevSync(OUT_BC, 25, 100);\n"
182
+ (Rubotz.generate {motor(:synchronized => true,
183
+ :direction => :forwards,
184
+ :ports => [:B, :C],
185
+ :power => 25,
186
+ :turn => 100)}).should == "OnFwdSync(OUT_BC, 25, 100);\n"
187
+ (Rubotz.generate {motor(:direction => :forwards,
188
+ :ports => [:B, :C],
189
+ :power => 50)}).should == "OnFwdSync(OUT_BC, 50, 0);\n"
190
+ (Rubotz.generate {motor(:direction => :banana,
191
+ :ports => :B,
192
+ :power => "houseplant")}).should_not == "OnFwdSync(OUT_BC, 50, 0);\n"
193
+ end
194
+
195
+ it "generates motor control code with inline variables" do
196
+ (Rubotz.generate {motor(:direction => :forwards,
197
+ :ports => [:B, :C],
198
+ :power => power,
199
+ :turn => turn)}).should == "OnFwdSync(OUT_BC, power, turn);\n"
200
+ end
201
+
202
+ it "generates wait commands" do
203
+ (Rubotz.generate {wait(325)}).should == "Wait(325);\n"
204
+ (Rubotz.generate {wait(1000)}).should == "Wait(1000);\n"
205
+ end
206
+
207
+ it "generates wait commands with inline variables" do
208
+ (Rubotz.generate {wait(delay)}).should == "Wait(delay);\n"
209
+ end
210
+
211
+ end
@@ -0,0 +1,8 @@
1
+ #include "NXCDefs.h"
2
+ <%= comment %>
3
+ task main() {
4
+ <%= initialization_section %>
5
+ while(true) {
6
+ <%= loop_body %>
7
+ }
8
+ }
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubotz
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Giles Bowkett
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-01-12 00:00:00 -08:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: gilesb@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files:
23
+ - README
24
+ files:
25
+ - config
26
+ - config/nxt_home.yaml
27
+ - examples
28
+ - examples/table_wanderer.rb
29
+ - lib
30
+ - lib/rubotz
31
+ - lib/rubotz/generator.rb
32
+ - lib/rubotz/motor_generator.rb
33
+ - lib/rubotz/ultrasonic_sensor.rb
34
+ - lib/rubotz.rb
35
+ - README
36
+ - rubotz-0.0.1.gem
37
+ - rubotz.gemspec
38
+ - slow_spec
39
+ - slow_spec/compiler_spec.rb
40
+ - spec
41
+ - spec/generating_spec.rb
42
+ - spec/table_wanderer_spec.rb
43
+ - templates
44
+ - templates/simple_while.nxc.erb
45
+ has_rdoc: true
46
+ homepage: http://rubotz.rubyforge.org
47
+ post_install_message:
48
+ rdoc_options: []
49
+
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ">="
55
+ - !ruby/object:Gem::Version
56
+ version: "0"
57
+ version:
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: "0"
63
+ version:
64
+ requirements: []
65
+
66
+ rubyforge_project: rubotz
67
+ rubygems_version: 1.0.0
68
+ signing_key:
69
+ specification_version: 2
70
+ summary: Code generator for Lego Mindstorms NXT. Proof-of-concept release only.
71
+ test_files: []
72
+