lignite 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b479941700df01dc46fa526001f014920e528ad3
4
- data.tar.gz: 00d321b86f44d215261ad1bfbe1d88158c62f3e5
3
+ metadata.gz: 3fef446a97c670acf0bfc1944d3287d80e40799a
4
+ data.tar.gz: 034b4f841c015e98af6b1776b1705a3f20905936
5
5
  SHA512:
6
- metadata.gz: 1c9836db38d2bc17c096e3e73e1e26f54d6701f8be6cec34d0450dd79a6752afabd9db1b20087c7839df612799b5e3c02a5450740576759eaa75e651fc0b6aa4
7
- data.tar.gz: fb64659ace34de87e7b133e704eb7577b07aadcd20e80868cc02871c7e5f4725d69b197d319821152b20ef42cf721fab9ef471832384e6a776e228cc504f0634
6
+ metadata.gz: fd4a8eaaf713aef7ca9081a4755ff953b24aaba900c4c22b6547d90392390c7563125fc874f926cc6179aecc2428e8209f17df5799285fefda7b500c35d33c6a
7
+ data.tar.gz: f257ff8520fd6b5b124eb824a0869aac56c94db788c7b245cd116284e09a7a4cee20d467f634215329ffc37a664d738d419e934d768202172c3c7a6022d161d0
@@ -32,10 +32,14 @@ Metrics/BlockLength:
32
32
  - spec/**/*.rb
33
33
 
34
34
  Metrics/ClassLength:
35
+ Max: 120
35
36
  Exclude:
36
37
  - lib/lignite/ev3_tool.rb
37
38
  - lib/lignite/motors.rb
38
39
 
40
+ Metrics/CyclomaticComplexity:
41
+ Max: 8
42
+
39
43
  Metrics/MethodLength:
40
44
  Max: 25
41
45
 
data/NEWS.md CHANGED
@@ -2,6 +2,16 @@
2
2
 
3
3
  ## unreleased
4
4
 
5
+ ## 0.6.0, 2018-03-25
6
+
7
+ - Fixed silent corruption of files obtained by `ev3tool download`
8
+ - Added `ev3tool asm foo.rb foo.rbf`
9
+ - Added SimpleAssembler
10
+ - Added BodyCompiler#if_else, #loop_until_pre
11
+ - Implemented array_init* (PARVALUES)
12
+ - Added JumpOffset, replacing the Complex hack
13
+ - Variables are now aligned automatically; subroutine argument alignment is checked
14
+
5
15
  ## 0.5.0, 2018-03-05
6
16
 
7
17
  - Ev3Ops: added missing array_* ops (with PARV in the signature).
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.0
1
+ 0.6.0
@@ -13,14 +13,20 @@ class Bobbee
13
13
  # Interface to all other commands
14
14
  # @return [Lignite::DirectCommands]
15
15
  attr_reader :dc
16
+ # The robot is built with a https://en.wikipedia.org/wiki/Worm_drive
17
+ # lift mechanism
18
+ # @return [Boolean]
19
+ attr_reader :worm_lift
16
20
 
17
21
  def initialize(drive: Lignite::PORT_B | Lignite::PORT_C,
18
22
  lift: Lignite::PORT_A,
19
- dc: Lignite::DirectCommands.new)
23
+ dc: Lignite::DirectCommands.new,
24
+ worm_lift: false)
20
25
  layer = 0
21
26
  @drive = Lignite::Motors.new(layer, drive, dc)
22
27
  @lift = Lignite::Motors.new(layer, lift, dc)
23
28
  @dc = dc
29
+ @worm_lift = worm_lift
24
30
  end
25
31
 
26
32
  # @param speed [Integer] -100..100
@@ -43,34 +49,51 @@ class Bobbee
43
49
  end
44
50
 
45
51
  LIFT_FULL = 220
52
+ WORM_LIFT_FULL = 2400
46
53
 
47
54
  # Raise the fork from the ground up
48
55
  def raise(wait: true)
49
- lift.step_power(30, 10, LIFT_FULL - 20, 10)
56
+ if worm_lift
57
+ lift.step_power(-30, 10, WORM_LIFT_FULL - 20, 10)
58
+ else
59
+ lift.step_power(30, 10, LIFT_FULL - 20, 10)
60
+ end
50
61
  lift.ready if wait
51
62
  end
52
63
 
53
64
  # Raise the fork one third of the way from the ground up
54
65
  def third_raise(wait: true)
55
- lift.step_power(30, 10, LIFT_FULL / 3 - 20, 10)
66
+ if worm_lift
67
+ lift.step_power(-30, 10, WORM_LIFT_FULL / 3 - 20, 10)
68
+ else
69
+ lift.step_power(30, 10, LIFT_FULL / 3 - 20, 10)
70
+ end
56
71
  lift.ready if wait
57
72
 
58
73
  beep
59
- sleep 3
74
+ ready
60
75
  end
61
76
 
62
77
  # Lower the fork from above to the ground
63
78
  def lower(wait: true)
64
- lift.step_power(-1, 10, LIFT_FULL - 20, 10) # , Lignite::COAST)
79
+ if worm_lift
80
+ lift.step_power(30, 10, WORM_LIFT_FULL - 20, 10)
81
+ else
82
+ lift.step_power(-1, 10, LIFT_FULL - 20, 10)
83
+ end
65
84
  lift.ready if wait
66
85
 
67
86
  beep
68
- sleep 3
87
+ ready
69
88
  end
70
89
 
71
90
  # Lower the fork one third of the way from above to the ground
72
91
  def third_lower(wait: true)
73
- lift.step_power(-1, 10, LIFT_FULL / 3 - 20, 10)
92
+ if worm_lift
93
+ lift.step_power(30, 10, WORM_LIFT_FULL / 3 - 20, 10)
94
+ else
95
+ lift.step_power(-1, 10, LIFT_FULL / 3 - 20, 10)
96
+ end
74
97
  lift.ready if wait
75
98
  end
76
99
 
@@ -87,14 +110,14 @@ class Bobbee
87
110
  def forward(steps = SQUARE_STEPS)
88
111
  step_sync(50, 0, steps)
89
112
  beep
90
- sleep 3
113
+ ready
91
114
  end
92
115
 
93
116
  # Drive backward 1 square on the Boost mat
94
117
  def back(steps = SQUARE_STEPS)
95
118
  step_sync(-50, 0, back_factor(steps))
96
119
  beep
97
- sleep 3
120
+ ready
98
121
  end
99
122
 
100
123
  # Compensation factor when moving backward
@@ -121,37 +144,49 @@ class Bobbee
121
144
 
122
145
  # Motor degrees needed to turn the robot 90 degrees when using turn=200
123
146
  TURN_90_AT_200_STEPS = 600
147
+ # Similar, but when we're carrying 100 grams, turning becomes harder!
148
+ LOADED_TURN_90_AT_200_STEPS = 750
124
149
 
125
150
  # Turn 90 degrees left, simply by moving tracks in opposite directions
126
- def left_immediate
127
- step_sync(50, -200, TURN_90_AT_200_STEPS)
151
+ def left_immediate(loaded: false)
152
+ steps = loaded ? LOADED_TURN_90_AT_200_STEPS : TURN_90_AT_200_STEPS
153
+ step_sync(50, -200, steps)
128
154
  beep(100)
129
- sleep 3
155
+ ready
130
156
  end
131
157
 
132
158
  # Turn 90 degrees right, simply by moving tracks in opposite directions
133
- def right_immediate
134
- step_sync(50, 200, TURN_90_AT_200_STEPS)
159
+ def right_immediate(loaded: false)
160
+ steps = loaded ? LOADED_TURN_90_AT_200_STEPS : TURN_90_AT_200_STEPS
161
+ step_sync(50, 200, steps)
135
162
  beep(100)
136
- sleep 3
163
+ ready
137
164
  end
138
165
 
139
166
  # Turn 90 degrees left, starting and ending inside a Boost mat square
140
- def left
167
+ def left(loaded: false)
141
168
  align_centers_for_turning do
142
- left_immediate
169
+ left_immediate(loaded: loaded)
143
170
  end
144
171
  end
145
172
 
146
173
  # Turn 90 degrees right, starting and ending inside a Boost mat square
147
- def right
174
+ def right(loaded: false)
148
175
  align_centers_for_turning do
149
- right_immediate
176
+ right_immediate(loaded: loaded)
150
177
  end
151
178
  end
152
- end
153
179
 
154
- bb = Bobbee.new
180
+ # Wait until the previous motor movements are complete
181
+ def ready
182
+ return unless @dc.is_a? Lignite::DirectCommands
183
+
184
+ print "Ready... "
185
+ drive.test
186
+ lift.test
187
+ puts "OK"
188
+ end
189
+ end
155
190
 
156
191
  # Put Bobb3e (B) on the blue arrow (^). Move it one square left, and forward.
157
192
  # Put the container on a raised platform on the "twins" square (T).
@@ -169,18 +204,18 @@ def from_twins_to_fire(bb)
169
204
 
170
205
  right
171
206
  forward
172
- third_raise
207
+ 2.times { third_raise }
173
208
  back
174
- left
209
+ left(loaded: true)
175
210
  # we're at the starting position, but carrying the load
176
211
 
177
212
  # move towards the recipient
178
213
  2.times { forward }
179
- right
214
+ right(loaded: true)
180
215
 
181
216
  # deliver and unload
182
217
  forward
183
- third_lower
218
+ 2.times { third_lower }
184
219
  back
185
220
 
186
221
  # resting position
@@ -202,17 +237,17 @@ def from_fire_to_twins(bb)
202
237
 
203
238
  # load
204
239
  forward
205
- third_raise
240
+ 2.times { third_raise }
206
241
  back
207
242
 
208
243
  # starting position
209
- left
244
+ left(loaded: true)
210
245
  2.times { back }
211
246
 
212
247
  # deliver
213
- right
248
+ right(loaded: true)
214
249
  forward
215
- third_lower
250
+ 2.times { third_lower }
216
251
  back
217
252
  left
218
253
 
@@ -228,6 +263,17 @@ def calibrate_forward_and_back(bb)
228
263
  end
229
264
  end
230
265
 
266
+ mode = ARGV.first || "direct"
267
+ dc = if mode == "rbf"
268
+ Lignite::SimpleAssembler.new
269
+ else
270
+ Lignite::DirectCommands.new
271
+ end
272
+
273
+ bb = Bobbee.new(dc: dc, worm_lift: true)
274
+
231
275
  # calibrate_forward_and_back(bb)
232
276
  from_twins_to_fire(bb)
233
277
  # from_fire_to_twins(bb)
278
+
279
+ dc.write("bobbee.rbf") if mode == "rbf"
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/ruby
2
+ require "lignite"
3
+
4
+ dc = Lignite::DirectCommands.new
5
+
6
+ # Read gyro sensor angle on port 2
7
+ LAYER0 = 0
8
+ MODE = 0
9
+ pct = dc.with_reply do
10
+ # global vars
11
+ dataf :angle
12
+
13
+ block do
14
+ input_device_ready_si(LAYER0, Lignite::PORT_2, Lignite::TYPE_GYRO, MODE, :angle)
15
+ end
16
+ end
17
+ puts "Gyro sensor angle: #{pct}"
18
+
19
+ dc.close
@@ -4,11 +4,13 @@ require "lignite/logger"
4
4
  require "lignite/assembler"
5
5
  require "lignite/body_compiler"
6
6
  require "lignite/connection"
7
+ require "lignite/condition"
7
8
  require "lignite/connection/bluetooth"
8
9
  require "lignite/connection/replay"
9
10
  require "lignite/connection/tap"
10
11
  require "lignite/connection/usb"
11
12
  require "lignite/direct_commands"
13
+ require "lignite/jump_offset"
12
14
  require "lignite/message"
13
15
  require "lignite/motors"
14
16
  require "lignite/op_compiler"
@@ -17,7 +19,10 @@ require "lignite/system_commands"
17
19
  require "lignite/variables"
18
20
  require "lignite/version"
19
21
 
22
+ # The main namespace
20
23
  module Lignite
24
+ LAYER_0 = 0
25
+
21
26
  PORT_A = 1
22
27
  PORT_B = 2
23
28
  PORT_C = 4
@@ -39,4 +44,11 @@ module Lignite
39
44
  # Represents an error returned by the robot
40
45
  class VMError < RuntimeError
41
46
  end
47
+
48
+ def program(&block)
49
+ p = Assembler.new
50
+ p.compile(&block)
51
+ p
52
+ end
53
+ module_function :program
42
54
  end
@@ -17,6 +17,7 @@ module Lignite
17
17
  class Assembler
18
18
  include Bytes
19
19
  include Logger
20
+ include Lignite # for constants
20
21
 
21
22
  HEADER_SIZE = 16
22
23
  SIGNATURE = "LEGO".freeze
@@ -26,9 +27,14 @@ module Lignite
26
27
  end
27
28
 
28
29
  # @return [Array<RbfObject>]
29
- attr_reader :objects
30
+ attr_accessor :objects
30
31
  # @return [Variables]
31
- attr_reader :globals
32
+ attr_accessor :globals
33
+
34
+ def initialize
35
+ @objects = []
36
+ @globals = Variables.new
37
+ end
32
38
 
33
39
  # Assemble a complete RBF program file.
34
40
  # (it is OK to reuse an Assembler and call this several times in a sequence)
@@ -36,9 +42,8 @@ module Lignite
36
42
  # @param rb_filename [String] input
37
43
  # @param rbf_filename [String] output
38
44
  def assemble(rb_filename, rbf_filename, version: 109)
45
+ initialize
39
46
  rb_text = File.read(rb_filename)
40
- @objects = []
41
- @globals = Variables.new
42
47
 
43
48
  @declarer = RbfDeclarer.new
44
49
  @declarer.instance_eval(rb_text, rb_filename, 1) # 1 is the line number
@@ -47,7 +52,13 @@ module Lignite
47
52
  write(rbf_filename, version)
48
53
  end
49
54
 
50
- def write(rbf_filename, version)
55
+ def compile(&block)
56
+ @declarer = RbfDeclarer.new
57
+ @declarer.instance_exec(&block)
58
+ instance_exec(&block)
59
+ end
60
+
61
+ def write(rbf_filename, version = 109)
51
62
  image_size = HEADER_SIZE + @objects.map(&:size).reduce(0, :+)
52
63
 
53
64
  File.open(rbf_filename, "w") do |f|
@@ -99,4 +110,36 @@ module Lignite
99
110
  local_bytes: @locals.bytesize)
100
111
  end
101
112
  end
113
+
114
+ # Acts like DirectCommands but instead of executing, assembles them to a RBF file
115
+ class SimpleAssembler
116
+ def initialize
117
+ @globals = Variables.new
118
+ @locals = Variables.new
119
+ @declarer = RbfDeclarer::Dummy.new
120
+
121
+ @interp = BodyCompiler.new(@globals, @locals, @declarer)
122
+ end
123
+
124
+ def write(rbf_filename)
125
+ @interp.object_end
126
+ vmthread = RbfObject.vmthread(body: @interp.bytes, local_bytes: @locals.bytesize)
127
+
128
+ asm = Assembler.new
129
+ asm.objects = [vmthread]
130
+ asm.globals = @globals
131
+ asm.write(rbf_filename)
132
+ end
133
+
134
+ # Delegate the ops to the {BodyCompiler},
135
+ def method_missing(name, *args, &block)
136
+ super unless @interp.respond_to?(name)
137
+
138
+ @interp.public_send(name, *args, &block)
139
+ end
140
+
141
+ def respond_to_missing?(name, _include_private)
142
+ @interp.respond_to?(name) || super
143
+ end
144
+ end
102
145
  end
@@ -1,30 +1,4 @@
1
1
  module Lignite
2
- # Less-than (32 bit)
3
- class Lt32 # < Condition
4
- def initialize(a, b)
5
- @a = a
6
- @b = b
7
- end
8
-
9
- def not
10
- Ge32.new(@a, @b)
11
- end
12
-
13
- def jump_forward(compiler, body_size)
14
- compiler.jr_lt32(@a, @b, Complex(body_size, 2))
15
- end
16
-
17
- def jump_back(compiler, body_size, self_size = nil)
18
- if self_size.nil?
19
- fake = compiler.clone_context
20
- jump_back(fake, body_size, 0)
21
- self_size = fake.bytes.bytesize
22
- end
23
-
24
- compiler.jr_lt32(@a, @b, Complex(- (body_size + self_size), 2))
25
- end
26
- end
27
-
28
2
  # Extends {OpCompiler} by
29
3
  # - variable declarations: {VariableDeclarer}
30
4
  # - high level flow control: {#loop}
@@ -62,28 +36,39 @@ module Lignite
62
36
  BodyCompiler.new(@globals, @locals, @declared_objects)
63
37
  end
64
38
 
65
- def if(flag8, &body)
66
- subc = BodyCompiler.new(@globals, @locals, @declared_objects)
39
+ def if(cond, &body)
40
+ cond = Flag.new(cond) unless cond.is_a? Condition
41
+
42
+ subc = clone_context
67
43
  subc.instance_exec(&body)
68
44
 
69
- jr_false(flag8, Complex(subc.bytes.bytesize, 2))
45
+ cond.not.jump_forward(self, subc.bytes.bytesize)
70
46
  @bytes << subc.bytes
71
47
  end
72
48
 
49
+ def if_else(flag8, body_true, body_false)
50
+ truec = clone_context
51
+ falsec = clone_context
52
+ truec.instance_exec(&body_true)
53
+ falsec.instance_exec(&body_false)
54
+
55
+ # 4 is the unconditional jump size
56
+ jr_false(flag8, JumpOffset.new(truec.bytes.bytesize + 4))
57
+ @bytes << truec.bytes
58
+ jr(JumpOffset.new(falsec.bytes.bytesize))
59
+ @bytes << falsec.bytes
60
+ end
61
+
73
62
  def loop(&body)
74
- subc = BodyCompiler.new(@globals, @locals, @declared_objects)
63
+ subc = clone_context
75
64
  subc.instance_exec(&body)
76
65
  @bytes << subc.bytes
77
66
  # the jump takes up 4 bytes: JR, LC2, LO, HI
78
- jr(Complex(- (subc.bytes.bytesize + 4), 2))
67
+ jr(JumpOffset.new(- (subc.bytes.bytesize + 4)))
79
68
  end
80
69
 
81
70
  def loop_while_postcond(flag8, &body)
82
- subc = BodyCompiler.new(@globals, @locals, @declared_objects)
83
- subc.instance_exec(&body)
84
- @bytes << subc.bytes
85
- # the jump takes up 5 bytes: JR_TRUE, LV0(flag8), LC2, LO, HI
86
- jr_true(flag8, Complex(- (subc.bytes.bytesize + 5), 2))
71
+ loop_while(body, Flag.new(flag8))
87
72
  end
88
73
 
89
74
  def loop_while(a, b)
@@ -95,18 +80,31 @@ module Lignite
95
80
  end
96
81
 
97
82
  def loop_while_post(condition, &body)
98
- subc = BodyCompiler.new(@globals, @locals, @declared_objects)
83
+ subc = clone_context
99
84
  subc.instance_exec(&body)
100
85
  @bytes << subc.bytes
101
86
  body_size = subc.bytes.bytesize
102
87
  condition.jump_back(self, body_size)
103
88
  end
104
89
 
90
+ def loop_until_pre(condition, &body)
91
+ subc = clone_context
92
+ subc.instance_exec(&body)
93
+ ofs1 = @bytes.bytesize
94
+ condition.jump_forward(self, subc.bytes.bytesize + 4)
95
+ ofs2 = @bytes.bytesize
96
+ fw_jump_size = ofs2 - ofs1
97
+ @bytes << subc.bytes
98
+ jr(JumpOffset.new(-(fw_jump_size + subc.bytes.bytesize + 4)))
99
+ end
100
+
105
101
  def call(name, *args)
106
102
  obj_id = declared_objects.index_of(name)
107
103
  raise "Name #{name} not found" if obj_id.nil?
108
104
 
109
105
  # TODO: check that args match their declaration
106
+ # In particular, mixing up data32 with dataf passes the VM validity check
107
+ # but then misinterprets the bits.
110
108
  super(obj_id, *args) # Ev3Ops::call
111
109
  end
112
110