lego_ev3 0.9.0 → 0.9.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|