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 +4 -4
- data/.rubocop.yml +4 -0
- data/NEWS.md +10 -0
- data/VERSION +1 -1
- data/examples/bobbee.rb +75 -29
- data/examples/gyro-sensor.rb +19 -0
- data/lib/lignite.rb +12 -0
- data/lib/lignite/assembler.rb +48 -5
- data/lib/lignite/body_compiler.rb +35 -37
- data/lib/lignite/condition.rb +116 -0
- data/lib/lignite/connection.rb +4 -0
- data/lib/lignite/ev3_ops.rb +104 -12
- data/lib/lignite/ev3_tool.rb +9 -2
- data/lib/lignite/jump_offset.rb +18 -0
- data/lib/lignite/motors.rb +17 -8
- data/lib/lignite/op_compiler.rb +52 -26
- data/lib/lignite/parameter_declarer.rb +15 -15
- data/lib/lignite/variables.rb +27 -15
- data/lignite.gemspec +3 -0
- data/spec/data/HelloWorld.rb +2 -2
- data/spec/data/Performance.rb +1 -1
- data/spec/data/Performance.rbf +0 -0
- data/spec/data/ev3tool_download.yml +4 -2
- data/spec/data/everstorm.rbf +0 -0
- data/spec/ev3_tool_spec.rb +4 -1
- data/tools/ops_from_yml +9 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3fef446a97c670acf0bfc1944d3287d80e40799a
|
4
|
+
data.tar.gz: 034b4f841c015e98af6b1776b1705a3f20905936
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fd4a8eaaf713aef7ca9081a4755ff953b24aaba900c4c22b6547d90392390c7563125fc874f926cc6179aecc2428e8209f17df5799285fefda7b500c35d33c6a
|
7
|
+
data.tar.gz: f257ff8520fd6b5b124eb824a0869aac56c94db788c7b245cd116284e09a7a4cee20d467f634215329ffc37a664d738d419e934d768202172c3c7a6022d161d0
|
data/.rubocop.yml
CHANGED
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.
|
1
|
+
0.6.0
|
data/examples/bobbee.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
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
|
-
|
74
|
+
ready
|
60
75
|
end
|
61
76
|
|
62
77
|
# Lower the fork from above to the ground
|
63
78
|
def lower(wait: true)
|
64
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/lignite.rb
CHANGED
@@ -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
|
data/lib/lignite/assembler.rb
CHANGED
@@ -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
|
-
|
30
|
+
attr_accessor :objects
|
30
31
|
# @return [Variables]
|
31
|
-
|
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
|
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(
|
66
|
-
|
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
|
-
|
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 =
|
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(
|
67
|
+
jr(JumpOffset.new(- (subc.bytes.bytesize + 4)))
|
79
68
|
end
|
80
69
|
|
81
70
|
def loop_while_postcond(flag8, &body)
|
82
|
-
|
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 =
|
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
|
|