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.
@@ -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'
@@ -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
@@ -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 = LegoEv3::Commands::LegoSensor.get_value0!(@connection, @id)
12
+ @value = poll_value(1)[0]
14
13
  end
15
14
 
16
15
  def info
@@ -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 = LegoEv3::Commands::TachoMotor.get_count_per_rot!(@connection, @id)
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 = LegoEv3::Commands::TachoMotor.get_duty_cycle!(@connection, @id)
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 = LegoEv3::Commands::TachoMotor.get_duty_cycle_sp!(@connection, @id)
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 = LegoEv3::Commands::TachoMotor.get_polarity!(@connection, @id)
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 = LegoEv3::Commands::TachoMotor.get_position!(@connection, @id)
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 = LegoEv3::Commands::TachoMotor.get_position_sp!(@connection, @id)
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 = LegoEv3::Commands::TachoMotor.get_time_sp!(@connection, @id)
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 = LegoEv3::Commands::TachoMotor.get_stop_command!(@connection, @id)
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
- states = LegoEv3::Commands::TachoMotor.get_states!(@connection, @id)
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
@@ -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
- 'ssh' => {
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