lignite 0.5.0 → 0.6.0

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.
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