lego_ev3 0.9.0 → 0.9.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.
- checksums.yaml +4 -4
- data/README.md +112 -29
- data/bin/lego-ev3 +66 -13
- data/bin/lego-ev3-tcp-server +19 -0
- data/lib/brick.rb +8 -1
- data/lib/commands/builder.rb +10 -13
- data/lib/commands/lego_sensor.rb +2 -0
- data/lib/commands/tacho_motor.rb +1 -1
- data/lib/connection/base.rb +11 -14
- data/lib/connection/connection.rb +16 -18
- data/lib/connection/local.rb +48 -3
- data/lib/connection/ssh.rb +84 -0
- data/lib/connection/ssh_command.rb +44 -0
- data/lib/connection/tcp_client.rb +64 -0
- data/lib/connection/tcp_server.rb +63 -0
- data/lib/connection/upload.rb +34 -24
- data/lib/exceptions.rb +35 -1
- data/lib/lego_ev3.rb +7 -0
- data/lib/sensors/base.rb +19 -12
- data/lib/sensors/color.rb +76 -0
- data/lib/sensors/infrared.rb +142 -0
- data/lib/sensors/touch.rb +1 -2
- data/lib/tacho_motor.rb +49 -28
- data/lib/utilities.rb +38 -2
- metadata +10 -3
- data/lib/connection/remote.rb +0 -39
data/lib/lego_ev3.rb
CHANGED
@@ -6,8 +6,15 @@ end
|
|
6
6
|
|
7
7
|
require_relative 'utilities'
|
8
8
|
require_relative 'exceptions'
|
9
|
+
|
10
|
+
require_relative 'commands/builder'
|
9
11
|
require_all_relative 'commands'
|
12
|
+
|
13
|
+
require_relative 'connection/base'
|
10
14
|
require_all_relative 'connection'
|
15
|
+
|
16
|
+
require_relative 'sensors/base'
|
11
17
|
require_all_relative 'sensors'
|
18
|
+
|
12
19
|
require_relative 'tacho_motor'
|
13
20
|
require_relative 'brick'
|
data/lib/sensors/base.rb
CHANGED
@@ -1,30 +1,37 @@
|
|
1
1
|
module LegoEv3
|
2
2
|
# More info: http://www.ev3dev.org/docs/drivers/lego-sensor-class/
|
3
3
|
class LegoSensor
|
4
|
+
attr_reader :value
|
5
|
+
|
4
6
|
def initialize(connection, id, port, driver_name)
|
5
7
|
@connection = connection
|
6
8
|
@id = id
|
7
9
|
@port = port
|
8
10
|
@driver_name = driver_name
|
9
|
-
|
10
|
-
LegoEv3::Commands::LegoSensor.get_decimals(@connection, @id) do |response|
|
11
|
-
@decimals = response
|
12
|
-
end
|
13
|
-
|
14
|
-
LegoEv3::Commands::LegoSensor.get_num_values(@connection, @id) do |response|
|
15
|
-
@value_parts_count = response
|
16
|
-
end
|
17
|
-
|
18
|
-
@connection.flush
|
19
11
|
end
|
20
12
|
|
21
13
|
def info
|
22
14
|
{
|
23
15
|
id: @id,
|
24
16
|
port: @port,
|
25
|
-
driver_name: @driver_name
|
26
|
-
decimals: @decimals
|
17
|
+
driver_name: @driver_name
|
27
18
|
}
|
28
19
|
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def poll_value(parts_count)
|
24
|
+
raw = []
|
25
|
+
|
26
|
+
parts_count.times do |i|
|
27
|
+
LegoEv3::Commands::LegoSensor.send("get_value#{i}", @connection, @id) do |value|
|
28
|
+
raw << value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
@connection.flush
|
33
|
+
|
34
|
+
raw
|
35
|
+
end
|
29
36
|
end
|
30
37
|
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module LegoEv3
|
2
|
+
class ColorSensor < LegoSensor
|
3
|
+
def initialize(connection, id, port)
|
4
|
+
super(connection, id, port, 'lego-ev3-color')
|
5
|
+
|
6
|
+
@supported_modes = {
|
7
|
+
reflect: 'COL-REFLECT',
|
8
|
+
ambient: 'COL-AMBIENT',
|
9
|
+
color: 'COL-COLOR',
|
10
|
+
reflect_raw: 'REF-RAW',
|
11
|
+
rgb: 'RGB-RAW'
|
12
|
+
}
|
13
|
+
|
14
|
+
@modes_value_parts = {
|
15
|
+
reflect: 1,
|
16
|
+
ambient: 1,
|
17
|
+
color: 1,
|
18
|
+
reflect_raw: 2,
|
19
|
+
rgb: 3
|
20
|
+
}
|
21
|
+
|
22
|
+
@value_to_color = [:none, :black, :blue, :green, :yellow, :red, :white, :brown]
|
23
|
+
|
24
|
+
self.mode = :reflect
|
25
|
+
end
|
26
|
+
|
27
|
+
def mode
|
28
|
+
@mode = parse_mode(LegoEv3::Commands::LegoSensor.get_mode!(@connection, @id))
|
29
|
+
end
|
30
|
+
|
31
|
+
def mode=(new_value)
|
32
|
+
to_set = @supported_modes[new_value.to_sym]
|
33
|
+
throw new InvalidModeException(:sensor, :color, new_value, @supported_modes.keys) unless to_set
|
34
|
+
|
35
|
+
LegoEv3::Commands::LegoSensor.set_mode(@connection, @id, to_set)
|
36
|
+
LegoEv3::Commands::LegoSensor.get_mode(@connection, @id) do |m|
|
37
|
+
@mode = parse_mode(m)
|
38
|
+
end
|
39
|
+
|
40
|
+
@connection.flush
|
41
|
+
@mode
|
42
|
+
end
|
43
|
+
|
44
|
+
def poll
|
45
|
+
@value = parse_value(poll_value(@modes_value_parts[@mode]))
|
46
|
+
end
|
47
|
+
|
48
|
+
def info
|
49
|
+
super.merge({
|
50
|
+
sub_type: :color,
|
51
|
+
mode: @mode,
|
52
|
+
value: @value
|
53
|
+
})
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def parse_mode(raw)
|
59
|
+
@supported_modes.select do |key, value|
|
60
|
+
raw == value
|
61
|
+
end.first[0]
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_value(raw)
|
65
|
+
if @mode == :reflect || @mode == :ambient
|
66
|
+
raw.first.to_f / 100 # percent
|
67
|
+
elsif @mode == :color
|
68
|
+
@value_to_color[raw.first]
|
69
|
+
elsif @mode == :reflect_raw
|
70
|
+
raw[0..1]
|
71
|
+
elsif @mode == :rgb
|
72
|
+
raw[0..2]
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
module LegoEv3
|
2
|
+
# More info: http://www.ev3dev.org/docs/sensors/lego-ev3-infrared-sensor/
|
3
|
+
# TODO: make sense of proximity
|
4
|
+
class InfraredSensor < LegoSensor
|
5
|
+
def initialize(connection, id, port)
|
6
|
+
super(connection, id, port, 'lego-ev3-ir')
|
7
|
+
|
8
|
+
@supported_modes = {
|
9
|
+
proximity: 'IR-PROX',
|
10
|
+
seek: 'IR-SEEK',
|
11
|
+
control: 'IR-REMOTE',
|
12
|
+
control_alt: 'IR-REM-A'
|
13
|
+
}
|
14
|
+
|
15
|
+
@modes_value_parts = {
|
16
|
+
proximity: 1,
|
17
|
+
seek: 8,
|
18
|
+
control: 4,
|
19
|
+
control_alt: 1
|
20
|
+
}
|
21
|
+
|
22
|
+
@control_pressed_buttons = [
|
23
|
+
[],
|
24
|
+
[:red_up],
|
25
|
+
[:red_down],
|
26
|
+
[:blue_up],
|
27
|
+
[:blue_down],
|
28
|
+
[:red_up, :blue_up],
|
29
|
+
[:red_up, :blue_down],
|
30
|
+
[:red_down, :blue_up],
|
31
|
+
[:red_down, :blue_down],
|
32
|
+
[:beacon_on],
|
33
|
+
[:red_up, :red_down],
|
34
|
+
[:blue_up, :blue_down]
|
35
|
+
]
|
36
|
+
|
37
|
+
self.mode = :proximity
|
38
|
+
|
39
|
+
@channel = 1
|
40
|
+
|
41
|
+
init_values
|
42
|
+
end
|
43
|
+
|
44
|
+
def channel
|
45
|
+
@channel
|
46
|
+
end
|
47
|
+
|
48
|
+
def channel=(new_value)
|
49
|
+
@channel = [1, new_value.to_i, 4].sort[1]
|
50
|
+
|
51
|
+
init_values
|
52
|
+
end
|
53
|
+
|
54
|
+
def mode
|
55
|
+
@mode = parse_mode(LegoEv3::Commands::LegoSensor.get_mode!(@connection, @id))
|
56
|
+
end
|
57
|
+
|
58
|
+
def mode=(new_value)
|
59
|
+
to_set = @supported_modes[new_value.to_sym]
|
60
|
+
throw new InvalidModeException(:sensor, :infrared, new_value, @supported_modes.keys) unless to_set
|
61
|
+
|
62
|
+
LegoEv3::Commands::LegoSensor.set_mode(@connection, @id, to_set)
|
63
|
+
LegoEv3::Commands::LegoSensor.get_mode(@connection, @id) do |m|
|
64
|
+
@mode = parse_mode(m)
|
65
|
+
end
|
66
|
+
|
67
|
+
@connection.flush
|
68
|
+
|
69
|
+
init_values
|
70
|
+
|
71
|
+
@mode
|
72
|
+
end
|
73
|
+
|
74
|
+
def poll
|
75
|
+
throw new InvalidChannelException(@channel) if @mode == :control_alt && @channel != 1
|
76
|
+
@last_value_raw = @value_raw
|
77
|
+
@value_raw = poll_value(@modes_value_parts[@mode])
|
78
|
+
@value = parse_value(@value_raw)
|
79
|
+
end
|
80
|
+
|
81
|
+
def info
|
82
|
+
super.merge({
|
83
|
+
sub_type: :infrared,
|
84
|
+
mode: @mode,
|
85
|
+
value: @value,
|
86
|
+
channel: @channel
|
87
|
+
})
|
88
|
+
end
|
89
|
+
|
90
|
+
private
|
91
|
+
|
92
|
+
# TODO Eventually use this to solve the "value 262" problem.
|
93
|
+
def init_values
|
94
|
+
@value = nil
|
95
|
+
@value_raw = nil
|
96
|
+
@last_value_raw = nil
|
97
|
+
end
|
98
|
+
|
99
|
+
def parse_mode(raw)
|
100
|
+
@supported_modes.select do |key, value|
|
101
|
+
raw == value
|
102
|
+
end.first[0]
|
103
|
+
end
|
104
|
+
|
105
|
+
def parse_value(raw)
|
106
|
+
if @mode == :proximity
|
107
|
+
raw.first.to_f / 100 # percent, [0, 1]. 100% is approximately 70cm.
|
108
|
+
elsif @mode == :seek
|
109
|
+
bit_position = (@channel - 1) * 4
|
110
|
+
values_at_position = raw[bit_position, bit_position + 1]
|
111
|
+
|
112
|
+
# When looking in the same direction as the sensor,
|
113
|
+
# -25 is far left and +25 is far right.
|
114
|
+
{
|
115
|
+
heading: values_at_position[0].to_f / 25, # percent, [-1, 1]
|
116
|
+
distance: values_at_position[1].to_f / 100, # percent, [0, 1]
|
117
|
+
activated: values_at_position[1] != -128
|
118
|
+
}
|
119
|
+
elsif @mode == :control
|
120
|
+
bit_position = @channel - 1
|
121
|
+
value_at_position = raw[bit_position]
|
122
|
+
@control_pressed_buttons[value_at_position]
|
123
|
+
elsif @mode == :control_alt
|
124
|
+
value = []
|
125
|
+
|
126
|
+
# TODO: Pressing an up/down button while beacon mode
|
127
|
+
# TODO: is activated with turn off beacon mode.
|
128
|
+
|
129
|
+
# TODO: Also, when the beacon mode is active or for
|
130
|
+
# TODO: about 1 second after any button is released the value is 262.
|
131
|
+
if raw[0] & 0x0F > 0
|
132
|
+
value << :blue_down if raw[0][7] == 1
|
133
|
+
value << :blue_up if raw[0][6] == 1
|
134
|
+
value << :red_down if raw[0][5] == 1
|
135
|
+
value << :red_up if raw[0][4] == 1
|
136
|
+
end
|
137
|
+
|
138
|
+
value
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
data/lib/sensors/touch.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module LegoEv3
|
2
|
-
# More info: http://www.ev3dev.org/docs/drivers/lego-sensor-class/
|
3
2
|
class TouchSensor < LegoSensor
|
4
3
|
def initialize(connection, id, port)
|
5
4
|
super(connection, id, port, 'lego-ev3-touch')
|
@@ -10,7 +9,7 @@ module LegoEv3
|
|
10
9
|
end
|
11
10
|
|
12
11
|
def poll
|
13
|
-
@value =
|
12
|
+
@value = poll_value(1)[0]
|
14
13
|
end
|
15
14
|
|
16
15
|
def info
|
data/lib/tacho_motor.rb
CHANGED
@@ -60,7 +60,6 @@ module LegoEv3
|
|
60
60
|
LegoEv3::Commands::TachoMotor.run_to_abs_pos!(@connection, @id)
|
61
61
|
|
62
62
|
loop do
|
63
|
-
ap info
|
64
63
|
break if operation_completed?(old_position)
|
65
64
|
end
|
66
65
|
|
@@ -98,12 +97,12 @@ module LegoEv3
|
|
98
97
|
sync!
|
99
98
|
end
|
100
99
|
|
101
|
-
def ticks_per_rotation
|
102
|
-
@ticks_per_rotation
|
100
|
+
def ticks_per_rotation(sync = true)
|
101
|
+
get_value('@ticks_per_rotation', 'get_count_per_rot', sync)
|
103
102
|
end
|
104
103
|
|
105
|
-
def speed
|
106
|
-
@speed
|
104
|
+
def speed(sync = true)
|
105
|
+
get_value('@speed', 'get_duty_cycle', sync)
|
107
106
|
end
|
108
107
|
|
109
108
|
# This is actually the desired speed but feels more natural.
|
@@ -118,12 +117,12 @@ module LegoEv3
|
|
118
117
|
desired_speed
|
119
118
|
end
|
120
119
|
|
121
|
-
def desired_speed
|
122
|
-
@desired_speed
|
120
|
+
def desired_speed(sync = true)
|
121
|
+
get_value('@desired_speed', 'get_duty_cycle_sp', sync)
|
123
122
|
end
|
124
123
|
|
125
|
-
def polarity
|
126
|
-
@polarity
|
124
|
+
def polarity(sync = true)
|
125
|
+
get_value('@polarity', 'get_polarity', sync)
|
127
126
|
end
|
128
127
|
|
129
128
|
def polarity=(new_value)
|
@@ -135,8 +134,8 @@ module LegoEv3
|
|
135
134
|
polarity
|
136
135
|
end
|
137
136
|
|
138
|
-
def position
|
139
|
-
@position
|
137
|
+
def position(sync = true)
|
138
|
+
get_value('@position', 'get_position', sync)
|
140
139
|
end
|
141
140
|
|
142
141
|
def position=(new_value)
|
@@ -144,16 +143,16 @@ module LegoEv3
|
|
144
143
|
position
|
145
144
|
end
|
146
145
|
|
147
|
-
def desired_position
|
148
|
-
@desired_position
|
146
|
+
def desired_position(sync = true)
|
147
|
+
get_value('@desired_position', 'get_position_sp', sync)
|
149
148
|
end
|
150
149
|
|
151
|
-
def desired_time
|
152
|
-
@desired_time
|
150
|
+
def desired_time(sync = true)
|
151
|
+
get_value('@desired_time', 'get_time_sp', sync)
|
153
152
|
end
|
154
153
|
|
155
|
-
def stop_mode
|
156
|
-
@stop_mode
|
154
|
+
def stop_mode(sync = true)
|
155
|
+
get_value('@stop_mode', 'get_stop_command', sync)
|
157
156
|
end
|
158
157
|
|
159
158
|
def stop_mode=(new_value)
|
@@ -199,15 +198,17 @@ module LegoEv3
|
|
199
198
|
end
|
200
199
|
|
201
200
|
def sync!
|
202
|
-
ticks_per_rotation
|
203
|
-
speed
|
204
|
-
desired_speed
|
205
|
-
polarity
|
206
|
-
position
|
207
|
-
desired_position
|
208
|
-
desired_time
|
209
|
-
stop_mode
|
210
|
-
update_states
|
201
|
+
ticks_per_rotation(false)
|
202
|
+
speed(false)
|
203
|
+
desired_speed(false)
|
204
|
+
polarity(false)
|
205
|
+
position(false)
|
206
|
+
desired_position(false)
|
207
|
+
desired_time(false)
|
208
|
+
stop_mode(false)
|
209
|
+
update_states(false)
|
210
|
+
|
211
|
+
@connection.flush("Updated motor state")
|
211
212
|
|
212
213
|
info
|
213
214
|
end
|
@@ -237,9 +238,17 @@ module LegoEv3
|
|
237
238
|
throw Exception.new('Speed is set to 0.') if desired_speed == 0
|
238
239
|
end
|
239
240
|
|
240
|
-
def update_states
|
241
|
-
|
241
|
+
def update_states(sync = true)
|
242
|
+
if sync
|
243
|
+
update_states_part_2(LegoEv3::Commands::TachoMotor.get_states!(@connection, @id))
|
244
|
+
else
|
245
|
+
LegoEv3::Commands::TachoMotor.get_states(@connection, @id) do |states|
|
246
|
+
update_states_part_2(states)
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
242
250
|
|
251
|
+
def update_states_part_2(states)
|
243
252
|
@running = states.include?(:running)
|
244
253
|
@ramping = states.include?(:ramping)
|
245
254
|
@holding = states.include?(:holding)
|
@@ -251,5 +260,17 @@ module LegoEv3
|
|
251
260
|
|
252
261
|
!@running || @holding
|
253
262
|
end
|
263
|
+
|
264
|
+
def get_value(variable_name, command, sync = true)
|
265
|
+
if sync
|
266
|
+
instance_variable_set(
|
267
|
+
variable_name,
|
268
|
+
LegoEv3::Commands::TachoMotor.send("#{command}!", @connection, @id))
|
269
|
+
else
|
270
|
+
LegoEv3::Commands::TachoMotor.send(command, @connection, @id) do |value|
|
271
|
+
instance_variable_set(variable_name, value)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
254
275
|
end
|
255
276
|
end
|
data/lib/utilities.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'yaml'
|
2
|
+
require 'optparse'
|
2
3
|
|
3
4
|
module LegoEv3
|
4
5
|
# bar, time = with_timer do
|
@@ -16,13 +17,44 @@ module LegoEv3
|
|
16
17
|
[return_value, (diff * 1000).to_i] # in ms.
|
17
18
|
end
|
18
19
|
|
20
|
+
def self.user_config_overrides
|
21
|
+
@@user_config_overrides
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.user_config_overrides=(new_value)
|
25
|
+
@@user_config_overrides = new_value
|
26
|
+
end
|
27
|
+
|
28
|
+
# 1. Start from a default config + programatic config
|
29
|
+
# 2. If a config is found in the command parameters, apply overrides.
|
30
|
+
# 3. If a config is found in LegoEv3::resolved_config, apply overrides.
|
31
|
+
def self.resolve_user_config(user_config = nil)
|
32
|
+
config = default_user_config.merge(user_config || {})
|
33
|
+
|
34
|
+
OptionParser.new do |opt|
|
35
|
+
opt.on('-c, --config PATH', 'Use the provided configuration at PATH.') do |path|
|
36
|
+
config.merge!(LegoEv3::load_config(path))
|
37
|
+
end
|
38
|
+
end.parse!
|
39
|
+
|
40
|
+
config.merge!(user_config_overrides || {})
|
41
|
+
|
42
|
+
user_config_overrides = config
|
43
|
+
|
44
|
+
config
|
45
|
+
end
|
46
|
+
|
19
47
|
def self.default_user_config
|
20
48
|
{
|
21
|
-
'
|
49
|
+
'entry_point' => 'script.rb',
|
50
|
+
'remote' => {
|
22
51
|
'host' => '192.168.2.3', # I'm working on Mac OS X, eh.
|
23
52
|
'hostname' => 'ev3dev',
|
24
53
|
'username' => 'root',
|
25
|
-
'password' => 'r00tme'
|
54
|
+
'password' => 'r00tme',
|
55
|
+
'ssh' => 22,
|
56
|
+
'tcp' => 13603,
|
57
|
+
'service' => 'ssh'
|
26
58
|
}
|
27
59
|
}
|
28
60
|
end
|
@@ -30,4 +62,8 @@ module LegoEv3
|
|
30
62
|
def self.load_config(path)
|
31
63
|
YAML::load(File.open(path))
|
32
64
|
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
@@user_config_overrides = nil
|
33
69
|
end
|