rubotz 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/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
+