lignite 0.1.2 → 0.2.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: 69c552fbf1d67d88480d8d1ffd3909ea41eebadf
4
- data.tar.gz: c53de990e8d76b4960bfa8157f5aabb14c652fbf
3
+ metadata.gz: 4ffc33346a586d1761204e68f028b383303cc742
4
+ data.tar.gz: a57b880c420ef54dac5ea3b0023293867f5e9bdf
5
5
  SHA512:
6
- metadata.gz: fb091ed264555daa9ea360e59cd3c27b097710d4d0f33105445a83d2640d8259a19f83e40cdd9625dd52588606a36a47f721384e2511b3ccf92b2dc5202b0fb2
7
- data.tar.gz: 865cb7e2dadc5614708fde9aa728f7b9af07b78c5e0de4ac1642480c344026d986356a4500980ed24f15e449fefc534fb6aa69dd5b650c74f1c0da8c469a5aa8
6
+ metadata.gz: 41357f5c2e71a45aef56e9de73a36b2010462417210b18ea9fbd3cd654889a0103ff1aaf82ebb81f29571fa88f0f4bf43020e7dc56ac1f5862147d6b7278dd2e
7
+ data.tar.gz: 8a278113af2c9a7500d4ca7774dcb5e99a4ce163ad24e2446eb06e0b7ae4baddae676bbd736741b2d33491b3f52c75ec9f6b0bb2e8be05eff4386cf053292535
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --files NEWS.md
data/NEWS.md ADDED
@@ -0,0 +1,16 @@
1
+ # News
2
+
3
+ ## unreleased
4
+
5
+ - (none)
6
+
7
+ ## 0.2.0, 2018-02-26
8
+
9
+ - DirectCommand can handle replies, allowing to read sensor data
10
+ - Motors, for easy DirectCommands to motors
11
+ - with an example program for a [Tea Serving Robot][tea], which is a standard [Bobb3e][]
12
+ - Assembler can compile global variables
13
+ - More control flow: `if`, `loop_while_postcond`
14
+
15
+ [Bobb3e]: https://www.lego.com/mindstorms/build-a-robot/bobb3e
16
+ [tea]: https://www.youtube.com/watch?v=0KOEvz09kkA
data/README.md CHANGED
@@ -41,9 +41,13 @@ section of the
41
41
  Running the built-in demo program: Connect the brick with a USB cable. Run
42
42
 
43
43
  ```sh
44
- sudo ev3tool run ../prjs/BrkProg_SAVE/Demo.rpf
44
+ sudo ev3tool run BrkProg_SAVE/Demo.rpf
45
45
  ```
46
46
 
47
+ In the usual case where the folder and the program name are the same, a
48
+ shortcut works: `ev3tool run everstorm` is like `ev3tool run
49
+ everstorm/everstorm.rbf`.
50
+
47
51
  > The `sudo` is needed to access the USB device.
48
52
  > With udev you can configure the system to allow access to all users like this:
49
53
  >
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.1.2
1
+ 0.2.0
@@ -0,0 +1,211 @@
1
+ #!/usr/bin/ruby
2
+ # coding: utf-8
3
+ # https://www.lego.com/mindstorms/build-a-robot/bobb3e
4
+ # A Bobcat® like two-tracked forklift
5
+
6
+ require "lignite"
7
+
8
+ class Bobbee
9
+ # @return [Lignite::Motors]
10
+ attr :drive
11
+ # @return [Lignite::Motors]
12
+ attr :lift
13
+ attr :dc
14
+
15
+ def initialize(drive: Lignite::PORT_B | Lignite::PORT_C,
16
+ lift: Lignite::PORT_A,
17
+ dc: Lignite::DirectCommands.new)
18
+ layer = 0
19
+ @drive = Lignite::Motors.new(layer, drive, dc)
20
+ @lift = Lignite::Motors.new(layer, lift, dc)
21
+ @dc = dc
22
+ end
23
+
24
+ # @param turn [Integer] -200..200
25
+ def time_sync(speed, turn, ms, brake = Lignite::BRAKE, wait: true)
26
+ # 1500 ms is about 1 boost square at speed 100
27
+ drive.time_sync(-speed, turn, ms, brake)
28
+ end
29
+
30
+ # @param turn [Integer] -200..200
31
+ def step_sync(speed, turn, degrees, brake = Lignite::BRAKE, wait: true)
32
+ # turn 0 (straight)
33
+ # - 900 (2.5 * 360) degrees is about 1 boost square
34
+ # turn 200 (full right)
35
+ # - 2400 degrees is a full turn, 600 deg is an about-right turn
36
+ # where the center of rotation is the battery-cover of the brick,
37
+ # or between the rear axles.
38
+ # turn 100 (semi right)
39
+ # - 4800 degrees is a full turn, about one of the rear axles
40
+ drive.step_sync(-speed, turn, degrees, brake)
41
+ drive.ready if wait
42
+ end
43
+
44
+ LIFT_FULL = 220
45
+
46
+ def raise(wait: true)
47
+ lift.step_power(30, 10, LIFT_FULL - 20, 10)
48
+ lift.ready if wait
49
+ end
50
+
51
+ def third_raise(wait: true)
52
+ lift.step_power(30, 10, LIFT_FULL/3 - 20, 10)
53
+ lift.ready if wait
54
+
55
+ beep
56
+ sleep 3
57
+ end
58
+
59
+ def lower(wait: true)
60
+ lift.step_power(-1, 10, LIFT_FULL - 20, 10) #, Lignite::COAST)
61
+ lift.ready if wait
62
+
63
+ beep
64
+ sleep 3
65
+ end
66
+
67
+ def third_lower(wait: true)
68
+ lift.step_power(-1, 10, LIFT_FULL/3 - 20, 10)
69
+ lift.ready if wait
70
+ end
71
+
72
+ def beep(ms = 300)
73
+ dc.sound_tone(20, 1760, ms)
74
+ dc.sound_ready
75
+ end
76
+
77
+ SQUARE_STEPS = 920
78
+ def f(steps = SQUARE_STEPS)
79
+ step_sync(50, 0, steps)
80
+ beep
81
+ sleep 3
82
+ end
83
+
84
+ def b(steps = SQUARE_STEPS)
85
+ step_sync(-50, 0, back_factor(steps))
86
+ beep
87
+ sleep 3
88
+ end
89
+
90
+ # it moves faster when going backwards!?
91
+ def back_factor(n)
92
+ (n * 0.95).to_i
93
+ end
94
+
95
+ ROTATION_OFFSET = 350
96
+ def f1
97
+ f(ROTATION_OFFSET)
98
+ beep(100)
99
+ end
100
+
101
+ TURN_90_AT_200_STEPS = 600
102
+ def l2
103
+ step_sync(50, -200, TURN_90_AT_200_STEPS)
104
+ beep(100)
105
+ sleep 3
106
+ end
107
+
108
+ def r2
109
+ step_sync(50, 200, TURN_90_AT_200_STEPS)
110
+ beep(100)
111
+ sleep 3
112
+ end
113
+
114
+ def f3
115
+ b(ROTATION_OFFSET)
116
+ beep(100)
117
+ end
118
+
119
+ def l
120
+ # a 3 part maneuver: forward, left, and back-a-little
121
+ # to turn 1 BS left
122
+ f1
123
+ l2
124
+ f3
125
+ end
126
+
127
+ def r
128
+ # a 3 part maneuver: forward, right, and back-a-little
129
+ # to turn 1 BS right
130
+ f1
131
+ r2
132
+ f3
133
+ end
134
+ end
135
+
136
+ bb = Bobbee.new
137
+ # 1 BS
138
+ # bb.time_sync(100, 0, 1500)
139
+
140
+ # Put Bobb3e on the blue arrow. Move it one square left, and forward.
141
+ # Put the container on a raised platform on the "twins" square.
142
+ # Put a raised platform on the "fire" square
143
+ def from_twins_to_fire(bb)
144
+ bb.instance_exec do
145
+ beep
146
+
147
+ third_raise
148
+ third_raise
149
+ r
150
+ f
151
+ third_raise
152
+ b
153
+ l
154
+ # we're at the starting position, but carrying the load
155
+
156
+ # move towards the recipient
157
+ f; f; r
158
+
159
+ # deliver and unload
160
+ f
161
+ third_lower
162
+ b
163
+
164
+ # resting position
165
+ l; b; b
166
+ third_lower
167
+ third_lower
168
+ end
169
+ end
170
+
171
+ def from_fire_to_twins(bb)
172
+ bb.instance_exec do
173
+ beep
174
+
175
+ third_raise
176
+ third_raise
177
+
178
+ # move towards the load
179
+ f; f; r
180
+
181
+ # load
182
+ f
183
+ third_raise
184
+ b
185
+
186
+ # starting position
187
+ l; b; b
188
+
189
+ # deliver
190
+ r
191
+ f
192
+ third_lower
193
+ b
194
+ l
195
+
196
+ # resting position
197
+ third_lower
198
+ third_lower
199
+ end
200
+ end
201
+
202
+ def calibrate_forward_and_back(bb)
203
+ bb.instance_exec do
204
+ f; f; f; f
205
+ b; b; b; b
206
+ end
207
+ end
208
+
209
+ # calibrate_forward_and_back(bb)
210
+ from_twins_to_fire(bb)
211
+ # from_fire_to_twins(bb)
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/ruby
2
+ require "lignite"
3
+
4
+ dc = Lignite::DirectCommands.new
5
+
6
+ # see 28/31 of Comm dev kit, read light sensor value on port 3
7
+ LAYER0 = 0
8
+ MODE = 0
9
+ count = 1
10
+ pct = dc.with_reply do
11
+ # global vars
12
+ dataf :light
13
+
14
+ # TODO: make the block optional if there are no locals. or #with_locals
15
+ block do
16
+ input_device_ready_si(LAYER0, Lignite::PORT_3, Lignite::TYPE_KEEP, MODE, count, :light)
17
+ end
18
+ end
19
+ puts "Light sensor percentage: #{pct}"
data/examples/motors.rb CHANGED
@@ -15,5 +15,5 @@ dc.output_step_speed(layer, nos, speed,
15
15
  dc.output_ready(layer, nos)
16
16
 
17
17
  speed = -100
18
- dc.output_step_speed(layer, nos, speed,
19
- step_begin, step_do, step_end, brake)
18
+ m = Lignite::Motors.new(layer, nos, dc)
19
+ m.step_speed(speed, step_begin, step_do, step_end)
data/lib/lignite.rb CHANGED
@@ -9,6 +9,7 @@ require "lignite/connection/usb"
9
9
  require "lignite/direct_commands"
10
10
  require "lignite/message"
11
11
  require "lignite/message_sender"
12
+ require "lignite/motors"
12
13
  require "lignite/op_compiler"
13
14
  require "lignite/rbf_object"
14
15
  require "lignite/system_commands"
@@ -20,6 +21,11 @@ module Lignite
20
21
  PORT_C = 4
21
22
  PORT_D = 8
22
23
 
24
+ PORT_1 = 0
25
+ PORT_2 = 1
26
+ PORT_3 = 2
27
+ PORT_4 = 3
28
+
23
29
  COAST = 0
24
30
  BRAKE = 1
25
31
 
@@ -1,4 +1,7 @@
1
+ require "lignite/variables"
2
+
1
3
  module Lignite
4
+ # Assemble a complete RBF program file.
2
5
  class Assembler
3
6
  include Bytes
4
7
  include Logger
@@ -9,12 +12,22 @@ module Lignite
9
12
  u32(global_bytes)
10
13
  end
11
14
 
12
- def assemble(rb_filename, rbf_filename)
15
+ # @return [Array<RbfObject>]
16
+ attr :objects
17
+ # @return [Variables]
18
+ attr :globals
19
+
20
+ # Assemble a complete RBF program file.
21
+ # (it is OK to reuse an Assembler and call this several times in a sequence)
22
+ # TODO: redesign for Assembler.new(rb_filename).assemble(rbf_filename)?
23
+ # @param rb_filename [String] input
24
+ # @param rbf_filename [String] output
25
+ def assemble(rb_filename, rbf_filename, version: 109)
13
26
  rb_text = File.read(rb_filename)
14
27
  @objects = []
15
- @global_bytes = 0
28
+ @globals = Variables.new
16
29
 
17
- instance_eval(rb_text, rb_filename, 1)
30
+ instance_eval(rb_text, rb_filename, 1) # 1 is the line number
18
31
 
19
32
  File.open(rbf_filename, "w") do |f|
20
33
  dummy_header = image_header(image_size:0, version: 0, object_count: 0, global_bytes: 0)
@@ -28,18 +41,24 @@ module Lignite
28
41
  size = f.tell
29
42
  f.pos = 0
30
43
  header = image_header(image_size: size,
31
- version: 109,
44
+ version: version,
32
45
  object_count: @objects.size,
33
- global_bytes: @global_bytes)
46
+ global_bytes: @globals.bytesize)
34
47
  f.write(header)
35
48
  end
36
49
  end
37
50
 
51
+ def variables
52
+ globals
53
+ end
54
+ include VariableDeclarer
55
+
38
56
  def vmthread(id, &body)
39
57
  @locals = Variables.new
40
- bodyc = BodyCompiler.new(@locals)
58
+ bodyc = BodyCompiler.new(@globals, @locals)
41
59
  bodyc.instance_exec(&body)
42
60
  bodyc.instance_exec { object_end }
61
+ # FIXME: id is not written?!
43
62
  logger.debug "VMTHREAD #{id}"
44
63
  logger.debug " size #{bodyc.bytes.bytesize}"
45
64
  logger.debug " " + hexdump(bodyc.bytes)
@@ -3,36 +3,49 @@ module Lignite
3
3
  # - variable declarations: {VariableDeclarer}
4
4
  # - high level flow control: {#loop}
5
5
  class BodyCompiler
6
+ # @return [ByteString]
7
+ attr_reader :bytes
8
+ # @return [Variables]
9
+ attr_reader :locals
6
10
 
7
- # {#locals} are {Variables}
8
- module VariableDeclarer
9
- def data32(id)
10
- locals.add(id, 4)
11
- end
12
-
13
- def datas(id, size)
14
- locals.add(id, size)
15
- end
11
+ def variables
12
+ locals
16
13
  end
17
-
18
14
  include VariableDeclarer
19
- attr_reader :bytes
20
- attr_reader :locals
21
15
 
22
- def initialize(locals)
16
+ def initialize(globals, locals)
23
17
  @bytes = ""
18
+ @globals = globals
24
19
  @locals = locals
25
- @op_compiler = OpCompiler.new(nil, @locals)
20
+ @op_compiler = OpCompiler.new(@globals, @locals)
21
+ end
22
+
23
+ def if(flag8, &body)
24
+ subc = BodyCompiler.new(@globals, @locals)
25
+ subc.instance_exec(&body)
26
+
27
+ jr_false(flag8, Complex(subc.bytes.bytesize, 2))
28
+ @bytes << subc.bytes
26
29
  end
27
30
 
28
31
  def loop(&body)
29
- subc = BodyCompiler.new(@locals)
32
+ subc = BodyCompiler.new(@globals, @locals)
30
33
  subc.instance_exec(&body)
31
34
  @bytes << subc.bytes
32
35
  # the jump takes up 4 bytes: JR, LC2, LO, HI
33
36
  jr(Complex(- (subc.bytes.bytesize + 4), 2))
34
37
  end
35
38
 
39
+ def loop_while_postcond(flag8, &body)
40
+ subc = BodyCompiler.new(@globals, @locals)
41
+ subc.instance_exec(&body)
42
+ @bytes << subc.bytes
43
+ # the jump takes up 4 bytes: JR_TRUE, LV0(flag8), LC2, LO, HI
44
+ jr_true(flag8, Complex(- (subc.bytes.bytesize + 5), 2))
45
+ end
46
+
47
+ # Delegate the ops to the {OpCompiler},
48
+ # but also aggregate the result in @bytes.
36
49
  def method_missing(name, *args)
37
50
  super unless @op_compiler.respond_to?(name)
38
51
 
data/lib/lignite/bytes.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  module Lignite
2
+ # Shortcut methods to convert between data and their byte representation
2
3
  module Bytes
3
4
  def u8(n)
4
5
  (n & 0xff).chr
@@ -28,6 +29,10 @@ module Lignite
28
29
  s.unpack("L<").first
29
30
  end
30
31
 
32
+ def unpack_f32(s)
33
+ s.unpack("e").first
34
+ end
35
+
31
36
  def hexdump(s)
32
37
  s.unpack("H*").first
33
38
  end
@@ -1,17 +1,37 @@
1
1
  module Lignite
2
- # FIXME: cannot handle replies
3
2
  class DirectCommands
4
3
  # @param conn [Connection]
5
4
  def initialize(conn = Connection.create)
6
5
  @op_compiler = OpCompiler.new
7
6
  @sender = MessageSender.new(conn)
7
+ @globals = nil
8
+ end
9
+
10
+ def variables
11
+ @globals
12
+ end
13
+ include VariableDeclarer
14
+
15
+ def with_reply(&body)
16
+ @globals = Variables.new
17
+ ret_bytes = instance_exec(&body)
18
+ ret = @globals.unpack(ret_bytes)
19
+ @globals = nil
20
+ ret # TODO decode according to type
8
21
  end
9
22
 
10
23
  def block(&body)
11
24
  locals = Variables.new
12
- bodyc = BodyCompiler.new(locals)
25
+ bodyc = BodyCompiler.new(@globals, locals)
13
26
  bodyc.instance_exec(&body)
14
- @sender.direct_command(bodyc.bytes, local_size: locals.bytesize)
27
+
28
+ bs = bodyc.bytes
29
+ lsize = locals.bytesize
30
+ if @globals
31
+ @sender.direct_command_with_reply(bs, global_size: @globals.bytesize, local_size: lsize)
32
+ else
33
+ @sender.direct_command(bs, global_size: 0, local_size: lsize)
34
+ end
15
35
  end
16
36
 
17
37
  def method_missing(name, *args)
@@ -85,7 +85,7 @@ module Lignite
85
85
  end
86
86
 
87
87
  class DirectReply < Message
88
- def new(msgid:, error:, body:)
88
+ def initialize(msgid:, error:, body:)
89
89
  @msgid = msgid
90
90
  @error = error
91
91
  @globals = body
@@ -31,7 +31,7 @@ module Lignite
31
31
  raise VMError # no details?
32
32
  end
33
33
 
34
- reply.data
34
+ reply.globals
35
35
  end
36
36
 
37
37
  def system_command_with_reply(instr_bytes)
@@ -0,0 +1,165 @@
1
+ module Lignite
2
+ # Send {DirectCommands} to motors.
3
+ # It's called Motors instead of Motor because some methods are designed
4
+ # to work on a pair if motors driving a vehicle.
5
+ class Motors
6
+ # do the DCs spawn independent threads??
7
+ # must run ready in the same block?
8
+
9
+ attr :layer
10
+ attr :nos
11
+ # @return [Lignite::DirectCommands]
12
+ attr :dc
13
+
14
+ # 0x02 | 0x04 | 0x08 -> [1, 2, 3]
15
+ def nos_as_indices
16
+ [0, 1, 2, 3].find_all do |n|
17
+ (nos & (1 << n)) != 0
18
+ end
19
+ end
20
+
21
+ def initialize(layer, nos, dc = Lignite::DirectCommands.new)
22
+ @layer = layer
23
+ @nos = nos
24
+ @dc = dc
25
+ end
26
+
27
+ # TODO filter out support: official: no
28
+
29
+ # the type is an OUT param so the VM SETs and we GET to learn the type?
30
+ def set_type
31
+ layer = @layer
32
+ nos_as_indices.map do |no|
33
+ type = dc.with_reply do
34
+ data8 :type
35
+ block do
36
+ output_set_type(layer, no, :type)
37
+ end
38
+ end
39
+ type
40
+ end
41
+ end
42
+
43
+ # TODO: direct commands can do outputs? not yet
44
+
45
+ # ATTR tacho, like degrees
46
+ def reset
47
+ dc.output_reset(lay, nos) # tacho counts
48
+ end
49
+
50
+ # ATTR running
51
+ def stop(brake = Lignite::BRAKE)
52
+ dc.output_stop(layer, nos, brake)
53
+ end
54
+
55
+ # ATTR power ~ speed ?
56
+ def power(power)
57
+ dc.output_power(layer, nos, power) # does this start them also?
58
+ end
59
+
60
+ def speed(speed)
61
+ dc.output_speed(layer, nos, speed) # does this start them also?
62
+ end
63
+
64
+ def start
65
+ dc.output_start(layer, nos) # apparently not
66
+ end
67
+
68
+ # ATTR ~polarity
69
+ def polarity(pol)
70
+ dc.output_polarity(layer, nos, pol)
71
+ end
72
+
73
+ def read
74
+ layer = @layer
75
+ nos_as_indices.map do |no|
76
+ speed_tacho_pair = dc.with_reply do
77
+ data32 :tacho
78
+ data8 :speed
79
+ block do
80
+ output_read(layer, no, :speed, :tacho)
81
+ end
82
+ end
83
+ speed_tacho_pair
84
+ end
85
+ end
86
+
87
+ # ATTR running?
88
+ def test
89
+ layer = @layer
90
+ nos_as_indices.map do |no|
91
+ busy = dc.with_reply do
92
+ data8 :busy
93
+ block do
94
+ output_test(layer, no, :busy)
95
+ end
96
+ end
97
+ busy
98
+ end
99
+ end
100
+
101
+ # which commands are affected? not output_start they say
102
+ def ready
103
+ dc.output_ready(layer, nos)
104
+ end
105
+
106
+ # @param power [Integer] -100..100
107
+ # TODO better param protocol?
108
+ def step_power(power, deg1, deg2, deg3, brake = Lignite::BRAKE)
109
+ dc.output_step_power(layer, nos, power, deg1, deg2, deg3, brake)
110
+ end
111
+
112
+ # @param power [Integer] -100..100
113
+ # msec
114
+ def time_power(power, ms1, ms2, ms3, brake = Lignite::BRAKE)
115
+ dc.output_time_power(layer, nos, power, ms1, ms2, ms3, brake)
116
+ end
117
+
118
+ # @param speed [Integer] -100..100
119
+ # tachos
120
+ def step_speed(speed, deg1, deg2, deg3, brake = Lignite::BRAKE)
121
+ dc.output_step_speed(layer, nos, speed, deg1, deg2, deg3, brake)
122
+ end
123
+
124
+ # @param speed [Integer] -100..100
125
+ # msec
126
+ def time_speed(speed, ms1, ms2, ms3, brake = Lignite::BRAKE)
127
+ dc.output_time_speed(layer, nos, speed, ms1, ms2, ms3, brake)
128
+ end
129
+
130
+ # @param speed [Integer] -100..100
131
+ # @param turn [Integer] -200..200:
132
+ # 0 straight, 100 stops the right motor, -100 stops the left motor
133
+ # > 100 reverses the right motor, < -100 reverses the left motor
134
+ def step_sync(speed, turn, tachos, brake = Lignite::BRAKE)
135
+ dc.output_step_sync(layer, nos, speed, turn, tachos, brake)
136
+ end
137
+
138
+ def time_sync(speed, turn, ms, brake = Lignite::BRAKE)
139
+ dc.output_time_sync(layer, nos, speed, turn, ms, brake)
140
+ end
141
+
142
+ # zero tachos, for use as sensor
143
+ def clr_count
144
+ dc.output_clr_count(layer, nos)
145
+ end
146
+
147
+ def get_count
148
+ layer = @layer
149
+ nos_as_indices.map do |no|
150
+ tachos = dc.with_reply do
151
+ data32 :tachos
152
+ block do
153
+ output_get_count(layer, no, :tachos)
154
+ end
155
+ end
156
+ tachos
157
+ end
158
+ end
159
+
160
+ # WTF?
161
+ def prg_stop
162
+ dc.output_prg_stop
163
+ end
164
+ end
165
+ end
@@ -1,11 +1,21 @@
1
1
  require "yaml"
2
2
 
3
3
  module Lignite
4
+ # Dynamically constructs methods for all the instructions in ev3.yml
5
+ # The methods return the {ByteString}s corresponding to the ops.
4
6
  class OpCompiler
7
+ # TODO: doing it dynamically
8
+ # - is slow
9
+ # - makes the implementation harder to understand
10
+ # - means we cannot use YARD to document the API
11
+ # Therefore we should generate (most of) op_compiler.rb statically from
12
+ # ev3.yml ahead of the time.
13
+
5
14
  include Bytes
6
15
  include Logger
7
16
  extend Logger
8
17
 
18
+ # A marker for features that are not implemented yet
9
19
  class TODO < StandardError
10
20
  end
11
21
 
@@ -118,6 +128,8 @@ module Lignite
118
128
  @loaded = true
119
129
  end
120
130
 
131
+ # @param globals [Variables,nil]
132
+ # @param locals [Variables,nil]
121
133
  def initialize(globals = nil, locals = nil)
122
134
  self.class.load_yml
123
135
  @globals = globals
@@ -190,6 +202,8 @@ module Lignite
190
202
  n & 0xff, (n >> 8) & 0xff, (n >> 16) & 0xff, (n >> 24) & 0xff]
191
203
  end
192
204
 
205
+ # Reference a variable.
206
+ # (For declaring, see {VariableDeclarer}.)
193
207
  def make_var(sym)
194
208
  raise "No variables declared, cannot process symbols" if @locals.nil? && @globals.nil?
195
209
  if @locals.key?(sym)
@@ -209,6 +223,7 @@ module Lignite
209
223
  when Integer
210
224
  make_lc(x).map(&:chr).join("")
211
225
  when Complex
226
+ # a Complex number: real: just like an Integer above, but imag tells how many bytes to use for it
212
227
  make_lc(x.real, x.imag).map(&:chr).join("")
213
228
  when String
214
229
  u8(0x80) + x + u8(0x00)
@@ -2,18 +2,24 @@ module Lignite
2
2
  # Part of an assembled RBF file
3
3
  class RbfObject
4
4
  include Bytes
5
+ # @return [RbfObject] make a Vmthread object
5
6
  def self.vmthread(body:, local_bytes:)
6
7
  new(owner: 0, triggers: 0, local_bytes: local_bytes, body: body)
7
8
  end
8
9
 
10
+ # @return [RbfObject] make a Subcall object
9
11
  def self.subcall(body:, local_bytes:)
10
12
  new(owner: 0, triggers: 1, local_bytes: local_bytes, body: body)
11
13
  end
12
14
 
15
+ # @return [RbfObject] make a Block object
13
16
  def self.block(owner:, triggers:, body:)
14
17
  new(owner: owner, triggers: triggers, local_bytes: 0, body: body)
15
18
  end
16
19
 
20
+ # @return [ByteString] an already assembled body of the object
21
+ attr :body
22
+
17
23
  def initialize(owner:, triggers:, local_bytes:, body:)
18
24
  @owner = owner
19
25
  @triggers = triggers
@@ -25,9 +31,5 @@ module Lignite
25
31
  u32(pos_before_header + 12) + # size of header
26
32
  u16(@owner) + u16(@triggers) + u32(@local_bytes)
27
33
  end
28
-
29
- def body
30
- @body
31
- end
32
34
  end
33
35
  end
@@ -1,27 +1,70 @@
1
1
  module Lignite
2
2
  # Allocate local or global variables
3
+ # FIXME: the user can make bad alignment, resulting in VMError at runtime
4
+ # bad: data8 :speed; data32 :tacho; output_read(...)
5
+ # good data32 :tacho; data8 :speed; output_read(...)
3
6
  class Variables
4
7
  def initialize
5
8
  @offset = 0
9
+ # for proper decoding of direct replies according to declared types
10
+ @unpacker = ""
6
11
  @vars = {}
7
12
  end
8
13
 
9
- def add(id, size)
14
+ # declare
15
+ def add(id, size, unpacker)
10
16
  raise "Duplicate variable #{id}" if @vars.key?(id)
11
17
  @vars[id] = {offset: @offset, size: size}
12
18
  @offset += size
19
+ @unpacker += unpacker
13
20
  end
14
21
 
15
- def bytesize
16
- @offset
17
- end
18
-
22
+ # use
19
23
  def key?(sym)
20
24
  @vars.key?(sym)
21
25
  end
22
26
 
27
+ # use
23
28
  def offset(sym)
24
29
  @vars[sym][:offset]
25
30
  end
31
+
32
+ # compile
33
+ def bytesize
34
+ @offset
35
+ end
36
+
37
+ # decode reply
38
+ def unpack(buf)
39
+ values = buf.unpack(@unpacker)
40
+ values.size == 1 ? values.first : values
41
+ end
42
+ end
43
+
44
+ # {#variables} are {Variables}
45
+ module VariableDeclarer
46
+ def data8(id)
47
+ variables.add(id, 1, "C")
48
+ end
49
+
50
+ def data16(id)
51
+ variables.add(id, 2, "S<")
52
+ end
53
+
54
+ def data32(id)
55
+ variables.add(id, 4, "L<")
56
+ end
57
+
58
+ def dataf(id)
59
+ variables.add(id, 4, "e")
60
+ end
61
+
62
+ def datas(id, size)
63
+ variables.add(id, size, "a#{size}")
64
+ end
65
+
66
+ def array8(id, count)
67
+ variables.add(id, count * 1, "C#{count}")
68
+ end
26
69
  end
27
70
  end
data/lignite.gemspec CHANGED
@@ -16,13 +16,15 @@ TXT
16
16
  s.homepage = "https://github.com/mvidner/lignite"
17
17
  s.license = "GPL-3.0-only"
18
18
 
19
- # ruby -e 'puts `git ls-files`.lines.map { |f| " %s,\n" % f.strip.inspect }'
19
+ # ruby -e 'puts `git ls-files`.lines.map { |f| " %s" % f.strip.inspect }.join ",\n"'
20
20
  s.files = [
21
21
  ".coveralls.yml",
22
22
  ".gitignore",
23
23
  ".travis.yml",
24
+ ".yardopts",
24
25
  "COPYING",
25
26
  "Gemfile",
27
+ "NEWS.md",
26
28
  "README.md",
27
29
  "Rakefile",
28
30
  "VERSION",
@@ -30,7 +32,9 @@ TXT
30
32
  "data/ev3.yml",
31
33
  "data/lignite-btaddr",
32
34
  "data/sysops.yml",
35
+ "examples/bobbee.rb",
33
36
  "examples/hello.rb",
37
+ "examples/light-sensor.rb",
34
38
  "examples/lights.rb",
35
39
  "examples/motors.rb",
36
40
  "examples/sound.rb",
@@ -46,6 +50,7 @@ TXT
46
50
  "lib/lignite/logger.rb",
47
51
  "lib/lignite/message.rb",
48
52
  "lib/lignite/message_sender.rb",
53
+ "lib/lignite/motors.rb",
49
54
  "lib/lignite/op_compiler.rb",
50
55
  "lib/lignite/rbf_object.rb",
51
56
  "lib/lignite/system_commands.rb",
@@ -53,11 +58,17 @@ TXT
53
58
  "lib/lignite/version.rb",
54
59
  "lignite.gemspec",
55
60
  "spec/assembler_spec.rb",
61
+ "spec/data/ColorReadout.lms",
62
+ "spec/data/ColorReadout.rb",
63
+ "spec/data/ColorReadout.rbf",
56
64
  "spec/data/HelloWorld-subop.rb",
57
65
  "spec/data/HelloWorld-subop.rbf",
58
66
  "spec/data/HelloWorld.lms",
59
67
  "spec/data/HelloWorld.rb",
60
68
  "spec/data/HelloWorld.rbf",
69
+ "spec/data/NoDebug.lms",
70
+ "spec/data/NoDebug.rb",
71
+ "spec/data/NoDebug.rbf",
61
72
  "spec/data/VernierReadout.lms",
62
73
  "spec/data/VernierReadout.rb",
63
74
  "spec/data/VernierReadout.rbf",
@@ -4,7 +4,7 @@ require "lignite"
4
4
  require "fileutils"
5
5
 
6
6
  describe Lignite::Assembler do
7
- shared_examples "compiles" do |base|
7
+ shared_examples "compiles" do |base, asm_version|
8
8
  let(:datadir) { File.expand_path("../data", __FILE__) }
9
9
 
10
10
  let(:in_rb) { "#{datadir}/#{base}.rb" }
@@ -12,13 +12,15 @@ describe Lignite::Assembler do
12
12
  let(:expected_rbf) { "#{datadir}/#{base}.rbf" }
13
13
 
14
14
  it "correctly assembles #{base}" do
15
- subject.assemble(in_rb, actual_rbf)
15
+ subject.assemble(in_rb, actual_rbf, version: asm_version)
16
16
  expect(FileUtils.compare_file(expected_rbf, actual_rbf)).to be_truthy
17
17
  FileUtils.rm actual_rbf
18
18
  end
19
19
  end
20
20
 
21
- include_examples "compiles", "HelloWorld"
22
- include_examples "compiles", "HelloWorld-subop"
23
- include_examples "compiles", "VernierReadout"
21
+ it_behaves_like "compiles", "HelloWorld", 109
22
+ it_behaves_like "compiles", "HelloWorld-subop", 109
23
+ it_behaves_like "compiles", "VernierReadout", 109
24
+ it_behaves_like "compiles", "ColorReadout", 106
25
+ it_behaves_like "compiles", "NoDebug", 109
24
26
  end
@@ -0,0 +1,75 @@
1
+ define PORT 1
2
+
3
+ DATA16 Cmd
4
+ DATA8 Type
5
+ DATA8 Mode
6
+ ARRAY8 Data 6
7
+
8
+ vmthread MAIN
9
+ {
10
+ DATA32 Timer
11
+ DATA32 Data1
12
+ DATA32 Data2
13
+ DATA32 Data3
14
+ DATAS String 8
15
+ DATA8 Mode
16
+ DATA8 State
17
+
18
+
19
+ MOVE8_8(3,Mode)
20
+
21
+ Loop:
22
+
23
+ UI_DRAW(FILLWINDOW,0x00,0,0)
24
+ UI_DRAW(SELECT_FONT,SMALL_FONT)
25
+ UI_DRAW(TEXT,FG_COLOR,0,10,' Color Readout V0.00 ')
26
+ UI_DRAW(TEXT,FG_COLOR,0,30,'Mode ')
27
+
28
+ UI_DRAW(TEXT,FG_COLOR,0,50,'Raw 1')
29
+ UI_DRAW(TEXT,FG_COLOR,0,65,'Raw 2')
30
+ UI_DRAW(TEXT,FG_COLOR,0,80,'Raw 3')
31
+ UI_DRAW(TEXT,FG_COLOR,0,118,'Left = 3 Right = 4')
32
+ UI_DRAW(SELECT_FONT,NORMAL_FONT)
33
+
34
+ MOVE8_32(Mode,Data1)
35
+ AND32(Data1,0xFF,Data1)
36
+ STRINGS(NUMBER_FORMATTED,Data1,'%1d',8,String)
37
+ UI_DRAW(TEXT,FG_COLOR,64,30,String)
38
+
39
+ INPUT_DEVICE(READY_RAW,0,PORT,0,Mode,3,Data1,Data2,Data3)
40
+
41
+ AND32(Data1,0xFFFF,Data1)
42
+ STRINGS(NUMBER_FORMATTED,Data1,'%-5d',8,String)
43
+ UI_DRAW(TEXT,FG_COLOR,64,50,String)
44
+
45
+ AND32(Data2,0xFFFF,Data2)
46
+ STRINGS(NUMBER_FORMATTED,Data2,'%-5d',8,String)
47
+ UI_DRAW(TEXT,FG_COLOR,64,65,String)
48
+
49
+ AND32(Data3,0xFFFF,Data3)
50
+ STRINGS(NUMBER_FORMATTED,Data3,'%-5d',8,String)
51
+ UI_DRAW(TEXT,FG_COLOR,64,80,String)
52
+
53
+ UI_DRAW(UPDATE)
54
+
55
+
56
+ UI_BUTTON(SHORTPRESS,LEFT_BUTTON,State)
57
+ JR_FALSE(State,Not3)
58
+
59
+ MOVE8_8(3,Mode)
60
+
61
+ Not3:
62
+
63
+ UI_BUTTON(SHORTPRESS,RIGHT_BUTTON,State)
64
+ JR_FALSE(State,Not4)
65
+
66
+ MOVE8_8(4,Mode)
67
+
68
+ Not4:
69
+
70
+ TIMER_WAIT(250,Timer)
71
+ TIMER_READY(Timer)
72
+
73
+ JR(Loop)
74
+ }
75
+
@@ -0,0 +1,67 @@
1
+ PORT = 1
2
+
3
+ # global vars - unused?!
4
+ data16 :cmd
5
+ data8 :type
6
+ data8 :mode
7
+ array8 :data, 6
8
+
9
+ vmthread :MAIN do
10
+ data32 :Timer
11
+ data32 :Data1
12
+ data32 :Data2
13
+ data32 :Data3
14
+ datas :String, 8
15
+ data8 :Mode
16
+ data8 :State
17
+
18
+
19
+ move8_8(3, :Mode)
20
+
21
+ self.loop do
22
+ ui_draw(FILLWINDOW, 0x00, 0, 0)
23
+ ui_draw(SELECT_FONT, SMALL_FONT)
24
+ ui_draw(TEXT, FG_COLOR, 0, 10, ' Color Readout V0.00 ')
25
+ ui_draw(TEXT, FG_COLOR, 0, 30, 'Mode ')
26
+
27
+ ui_draw(TEXT, FG_COLOR, 0, 50, 'Raw 1')
28
+ ui_draw(TEXT, FG_COLOR, 0, 65, 'Raw 2')
29
+ ui_draw(TEXT, FG_COLOR, 0, 80, 'Raw 3')
30
+ ui_draw(TEXT, FG_COLOR, 0, 118, 'Left = 3 Right = 4')
31
+ ui_draw(SELECT_FONT, NORMAL_FONT)
32
+
33
+ move8_32(:Mode, :Data1)
34
+ and32(:Data1, 0xFF, :Data1)
35
+ strings(NUMBER_FORMATTED, :Data1, '%1d', 8, :String)
36
+ ui_draw(TEXT, FG_COLOR, 64, 30, :String)
37
+
38
+ input_device(READY_RAW, 0, PORT, 0, :Mode, 3, :Data1, :Data2, :Data3)
39
+
40
+ and32(:Data1, 0xFFFF, :Data1)
41
+ strings(NUMBER_FORMATTED, :Data1, '%-5d', 8, :String)
42
+ ui_draw(TEXT, FG_COLOR, 64, 50, :String)
43
+
44
+ and32(:Data2, 0xFFFF, :Data2)
45
+ strings(NUMBER_FORMATTED, :Data2, '%-5d', 8, :String)
46
+ ui_draw(TEXT, FG_COLOR, 64, 65, :String)
47
+
48
+ and32(:Data3, 0xFFFF, :Data3)
49
+ strings(NUMBER_FORMATTED, :Data3, '%-5d', 8, :String)
50
+ ui_draw(TEXT, FG_COLOR, 64, 80, :String)
51
+
52
+ ui_draw(UPDATE)
53
+
54
+ ui_button(SHORTPRESS, LEFT_BUTTON, :State)
55
+ self.if(:State) do
56
+ move8_8(3, :Mode)
57
+ end
58
+
59
+ ui_button(SHORTPRESS, RIGHT_BUTTON, :State)
60
+ self.if(:State) do
61
+ move8_8(4, :Mode)
62
+ end
63
+
64
+ timer_wait(250, :Timer)
65
+ timer_ready(:Timer)
66
+ end
67
+ end
Binary file
@@ -0,0 +1,62 @@
1
+ /*
2
+ * LEGO® MINDSTORMS EV3
3
+ *
4
+ * Copyright (C) 2010-2013 The LEGO Group
5
+ *
6
+ * This program is free software; you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation; either version 2 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * This program is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with this program; if not, write to the Free Software
18
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
+ */
20
+
21
+
22
+ // TCP 08.04.2013
23
+ //! \page testnodebugappcode NoDebug Application
24
+ //!
25
+ //! <hr size="1"/>
26
+ //!
27
+ //! \verbatim
28
+ //**********************************************************************
29
+ define appv 'NoDebug V1.02' //
30
+ //**********************************************************************
31
+ //
32
+ //
33
+ vmthread MAIN // void MAIN(void)
34
+ { // {
35
+ DATA8 ShowVersion //
36
+ //
37
+ UI_BUTTON(PRESSED,RIGHT_BUTTON,ShowVersion) // UI_BUTTON(PRESSED,RIGHT_BUTTON,ShowVersion)
38
+ JR_FALSE(ShowVersion,DontShowVersion) // if (ShowVersion)
39
+ // {
40
+ UI_DRAW(FILLRECT,BG_COLOR,4,50,170,28) // UI_DRAW(FILLRECT,BG_COLOR,4,50,170,28)
41
+ UI_DRAW(RECT,FG_COLOR,6,52,166,24) // UI_DRAW(RECT,FG_COLOR,6,52,166,24)
42
+ UI_DRAW(TEXT,FG_COLOR,13,60,appv) // UI_DRAW(TEXT,FG_COLOR,13,60,appv)
43
+ UI_DRAW(UPDATE) // UI_DRAW(UPDATE)
44
+ //
45
+ ShowVersionWait: // do
46
+ // {
47
+ UI_BUTTON(PRESSED,RIGHT_BUTTON,ShowVersion) // UI_BUTTON(PRESSED,RIGHT_BUTTON,ShowVersion)
48
+ // }
49
+ JR_TRUE(ShowVersion,ShowVersionWait) // while (ShowVersion)
50
+ //
51
+ UI_BUTTON(FLUSH) // UI_BUTTON(FLUSH)
52
+ DontShowVersion: // }
53
+ //
54
+ UI_DRAW(RESTORE,0) // UI_DRAW(RESTORE,0)
55
+ UI_DRAW(TOPLINE,1) // UI_DRAW(TOPLINE,1)
56
+ UI_WRITE(LED,LED_GREEN) // UI_WRITE(LED,LED_GREEN)
57
+ //
58
+ UI_WRITE(TERMINAL,0) // UI_WRITE(TERMINAL,0)
59
+ } // }
60
+ //
61
+ //
62
+ //! \endverbatim
@@ -0,0 +1,25 @@
1
+ appv = 'NoDebug V1.02'
2
+
3
+ vmthread :MAIN do
4
+ data8 :ShowVersion
5
+
6
+ ui_button(PRESSED, RIGHT_BUTTON, :ShowVersion)
7
+ self.if :ShowVersion do
8
+ ui_draw(FILLRECT, BG_COLOR, 4, 50, 170, 28)
9
+ ui_draw(RECT, FG_COLOR, 6, 52, 166, 24)
10
+ ui_draw(TEXT, FG_COLOR, 13, 60, appv)
11
+ ui_draw(UPDATE)
12
+
13
+ loop_while_postcond(:ShowVersion) do
14
+ ui_button(PRESSED, RIGHT_BUTTON, :ShowVersion)
15
+ end
16
+
17
+ ui_button(FLUSH)
18
+ end
19
+
20
+ ui_draw(RESTORE, 0)
21
+ ui_draw(TOPLINE, 1)
22
+ ui_write(LED, LED_GREEN)
23
+
24
+ ui_write(TERMINAL, 0)
25
+ end
Binary file
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lignite
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Martin Vidner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-20 00:00:00.000000000 Z
11
+ date: 2018-02-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: libusb
@@ -112,8 +112,10 @@ files:
112
112
  - ".coveralls.yml"
113
113
  - ".gitignore"
114
114
  - ".travis.yml"
115
+ - ".yardopts"
115
116
  - COPYING
116
117
  - Gemfile
118
+ - NEWS.md
117
119
  - README.md
118
120
  - Rakefile
119
121
  - VERSION
@@ -121,7 +123,9 @@ files:
121
123
  - data/ev3.yml
122
124
  - data/lignite-btaddr
123
125
  - data/sysops.yml
126
+ - examples/bobbee.rb
124
127
  - examples/hello.rb
128
+ - examples/light-sensor.rb
125
129
  - examples/lights.rb
126
130
  - examples/motors.rb
127
131
  - examples/sound.rb
@@ -137,6 +141,7 @@ files:
137
141
  - lib/lignite/logger.rb
138
142
  - lib/lignite/message.rb
139
143
  - lib/lignite/message_sender.rb
144
+ - lib/lignite/motors.rb
140
145
  - lib/lignite/op_compiler.rb
141
146
  - lib/lignite/rbf_object.rb
142
147
  - lib/lignite/system_commands.rb
@@ -144,11 +149,17 @@ files:
144
149
  - lib/lignite/version.rb
145
150
  - lignite.gemspec
146
151
  - spec/assembler_spec.rb
152
+ - spec/data/ColorReadout.lms
153
+ - spec/data/ColorReadout.rb
154
+ - spec/data/ColorReadout.rbf
147
155
  - spec/data/HelloWorld-subop.rb
148
156
  - spec/data/HelloWorld-subop.rbf
149
157
  - spec/data/HelloWorld.lms
150
158
  - spec/data/HelloWorld.rb
151
159
  - spec/data/HelloWorld.rbf
160
+ - spec/data/NoDebug.lms
161
+ - spec/data/NoDebug.rb
162
+ - spec/data/NoDebug.rbf
152
163
  - spec/data/VernierReadout.lms
153
164
  - spec/data/VernierReadout.rb
154
165
  - spec/data/VernierReadout.rbf