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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b418f6caad54717971ee644696289198bd6a1c62
|
4
|
+
data.tar.gz: cb47b39657e7913366bd821f76a35d6eabcd1d28
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1b476defd2bde6d39c2d7bf843cdbba04f3dd4612a211deaa56537b6ee1458b059eda153ade4efc5b1b3151312ed2177bf7b5c42d3e067581e26c961c7446037
|
7
|
+
data.tar.gz: 5027148c8a6d338752fc3682ef0c8435dd3192da8a60c716ac305f93f276c1e939a343807b81f658d81a4a04bd24287b534ac8e54aa8a42febf1e8b38cb6e62b
|
data/README.md
CHANGED
@@ -1,46 +1,129 @@
|
|
1
1
|
# Lego EV3
|
2
2
|
|
3
|
+
This library leverages the [ev3dev.org](http://www.ev3dev.org) project to provide a dead simple way to program the Lego Mindstorms EV3 starter kit in Ruby.
|
4
|
+
|
3
5
|
## Getting started
|
4
6
|
|
5
|
-
|
7
|
+
gem install lego-ev3
|
8
|
+
|
9
|
+
Then:
|
10
|
+
|
11
|
+
mkdir example
|
12
|
+
touch example/script.rb
|
13
|
+
touch example/config.yml
|
14
|
+
|
15
|
+
Put in `script.rb`:
|
16
|
+
|
17
|
+
require 'lego_ev3'
|
18
|
+
|
19
|
+
connection = LegoEv3::Connection.new
|
20
|
+
|
21
|
+
brick = LegoEv3::Brick.new(connection)
|
22
|
+
|
23
|
+
# Plug any sensor in any input.
|
24
|
+
s = brick.sensors.first
|
25
|
+
|
26
|
+
100.times do
|
27
|
+
puts s.poll
|
28
|
+
end
|
29
|
+
|
30
|
+
connection.close
|
31
|
+
|
32
|
+
Put in `config.yml`:
|
33
|
+
|
34
|
+
remote:
|
35
|
+
host: '192.168.2.3'
|
36
|
+
hostname: 'ev3dev'
|
37
|
+
username: 'root'
|
38
|
+
password: 'r00tme'
|
39
|
+
ssh: 22
|
40
|
+
tcp: 13603
|
41
|
+
service: 'ssh'
|
42
|
+
|
43
|
+
Execute the script with:
|
44
|
+
|
45
|
+
lego-ev3 example -R
|
46
|
+
|
47
|
+
This will open an SSH connection to the brick to send commands. This is one way of doing things, read below for more options.
|
48
|
+
|
49
|
+
## Setup the brick to use Ruby
|
6
50
|
|
7
|
-
|
51
|
+
### Make sure you have the necessary dependencies
|
52
|
+
|
53
|
+
The [ev3dev.org](http://www.ev3dev.org) distribution contains Ruby 2 but some libraries are missing to build native extensions. **Running local scripts with this lib requires native extensions to work**.
|
8
54
|
|
9
55
|
Solution #1: Use [RVM](https://rvm.io).
|
56
|
+
|
10
57
|
Solution #2: Install those dependencies:
|
11
58
|
|
12
|
-
|
13
|
-
|
14
|
-
|
59
|
+
apt-get install build-essential bison openssl libreadline6 libreadline6-dev curl git-core zlib1g zlib1g-dev libssl-dev libyaml-dev libxml2-dev autoconf libc6-dev ncurses-dev automake libtool ruby-dev
|
60
|
+
|
61
|
+
### (Optional) Faster `gem install` operations
|
62
|
+
|
63
|
+
To install gems faster, make sure your `~/.gemrc` on the brick looks like:
|
64
|
+
|
65
|
+
---
|
66
|
+
:verbose: true
|
67
|
+
:sources:
|
68
|
+
- http://rubygems.org/
|
69
|
+
:update_sources: true
|
70
|
+
:backtrace: false
|
71
|
+
:bulk_threshold: 1000
|
72
|
+
:benchmark: false
|
73
|
+
gem: --no-ri --no-rdoc --verbose
|
74
|
+
|
75
|
+
## Running a script remotely on the brick for debugging
|
76
|
+
|
77
|
+
This is the mode used in _Getting started_ above.
|
78
|
+
This mode requires `remote.service: 'ssh'` in the config.
|
79
|
+
|
80
|
+
* [+] No need to setup the brick to use Ruby
|
81
|
+
* [+] No need to send files to the brick
|
82
|
+
* [+] Can use breakpoints in script using [Pry](https://github.com/pry/pry)
|
83
|
+
* [-] The slowest approach (~200 ms per command)
|
84
|
+
|
85
|
+
## Running a script locally on the brick
|
86
|
+
|
87
|
+
The same script in _Getting started_ above can be ran locally on the brick without modifying the code.
|
88
|
+
|
89
|
+
* [-] Need to setup the brick to use Ruby
|
90
|
+
* [-] Need to send files to the brick
|
91
|
+
* [-] Cannot use breakpoints in script
|
92
|
+
* [+] Fastest approach (~0.7 ms per command)
|
93
|
+
|
94
|
+
Sending a script to the brick:
|
95
|
+
|
96
|
+
lego-ev3 example -u -r
|
97
|
+
|
98
|
+
This will:
|
99
|
+
|
100
|
+
* Upload the script to the brick (`-u`) at `/home/example`.
|
101
|
+
* Execute `ruby /home/example/script.rb` through an SSH connection and capture the output.
|
102
|
+
|
103
|
+
**How can the same code be used remotely and locally?**
|
15
104
|
|
16
|
-
|
105
|
+
The `LegoEv3::Connection` class is smart enough to determine if the script being executed is on your machine or directly on the brick. To accomplish this, this class uses the `hostname` parameter in the config.
|
17
106
|
|
18
|
-
|
19
|
-
gem install lego-ev3
|
20
|
-
```
|
107
|
+
## Running a script remotely on the brick for a test run
|
21
108
|
|
22
|
-
|
109
|
+
* [-] Need to setup the brick to use Ruby
|
110
|
+
* [+] No need to send files to the brick
|
111
|
+
* [+] Can use breakpoints in script using [Pry](https://github.com/pry/pry)
|
112
|
+
* [+] Slow approach (~40 ms per command)
|
23
113
|
|
24
|
-
|
25
|
-
require 'lego_ev3'
|
114
|
+
This mode requires `remote.service: 'tcp'` in the config.
|
26
115
|
|
27
|
-
|
28
|
-
|
29
|
-
# machine.hostname != config.hostname => remote.
|
30
|
-
connection = LegoEv3::Connection.new('ssh' => {
|
31
|
-
'host' => '192.168.2.3',
|
32
|
-
'hostname' => 'ev3dev',
|
33
|
-
'username' => 'root',
|
34
|
-
'password' => 'r00tme'
|
35
|
-
})
|
116
|
+
lego-ev3 example -s # Spawn the TCP server on the brick
|
117
|
+
lego-ev3 example -R # Execute the script with a TCP client
|
36
118
|
|
37
|
-
|
119
|
+
Note that the TCP server is spawned in background on the brick.
|
120
|
+
To spawn it in foreground, you need to connect to the brick.
|
38
121
|
|
39
|
-
|
40
|
-
# or not the sensor. The 'pressed' value should change accordingly.
|
41
|
-
s = brick.sensors.first
|
42
|
-
s.poll
|
43
|
-
puts s.info.inspect
|
122
|
+
## TODO
|
44
123
|
|
45
|
-
|
46
|
-
|
124
|
+
* Add a DSL on top of current lib
|
125
|
+
* Add a state machine on top of current lib
|
126
|
+
* Use the state machine to produce graphs of robot logic
|
127
|
+
* Add logging and easy way to produce sensor graphs
|
128
|
+
* Polling sensors should probably be multi-threaded
|
129
|
+
* Add examples
|
data/bin/lego-ev3
CHANGED
@@ -2,32 +2,85 @@
|
|
2
2
|
|
3
3
|
require 'lego_ev3'
|
4
4
|
require 'optparse'
|
5
|
+
require 'pp'
|
5
6
|
|
6
7
|
options = {
|
7
|
-
|
8
|
+
upload: false,
|
9
|
+
mode: :none,
|
10
|
+
user_config: nil
|
8
11
|
}
|
9
12
|
|
10
13
|
OptionParser.new do |opt|
|
11
|
-
opt.
|
12
|
-
|
13
|
-
|
14
|
+
opt.banner = 'Usage: lego-ev3 [project] [options]'
|
15
|
+
|
16
|
+
opt.on('-u', '--upload', 'Upload the project to /home/PROJECT on the brick.') do
|
17
|
+
options[:upload] = true
|
18
|
+
end
|
19
|
+
|
20
|
+
opt.on('-r', '--run', 'Run the project locally.') do
|
21
|
+
options[:mode] = :run_local
|
22
|
+
end
|
23
|
+
|
24
|
+
opt.on('-R', '--run-remotely', 'Run the project remotely.') do
|
25
|
+
options[:mode] = :run_remote
|
14
26
|
end
|
15
27
|
|
16
|
-
opt.on('-c, --config PATH', 'Use the provided configuration at PATH.') do |path|
|
28
|
+
opt.on('-c', '--config PATH', 'Use the provided configuration at PATH.') do |path|
|
17
29
|
options[:user_config].merge!(LegoEv3::load_config(path))
|
18
30
|
end
|
31
|
+
|
32
|
+
opt.on('-s', '--server', 'Start a TCP server on the brick.') do |port|
|
33
|
+
options[:mode] = :server
|
34
|
+
end
|
19
35
|
end.parse!
|
20
36
|
|
37
|
+
options[:project] = ARGV.pop
|
38
|
+
options[:user_config] ||= options[:project].nil? || !File.exists?(File.join(options[:project], 'config.yml')) ?
|
39
|
+
LegoEv3::default_user_config :
|
40
|
+
LegoEv3::load_config(File.join(options[:project], 'config.yml'))
|
41
|
+
|
21
42
|
puts
|
22
43
|
puts "Config used:"
|
23
44
|
puts
|
24
|
-
|
45
|
+
pp options
|
25
46
|
puts
|
26
47
|
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
48
|
+
if options[:project].nil? && options[:mode] != :server
|
49
|
+
puts 'This mode requires a project!'
|
50
|
+
puts 'Usage: lego-ev3 [project] [options]'
|
51
|
+
exit
|
52
|
+
end
|
53
|
+
|
54
|
+
ssh_config = {
|
55
|
+
host_name: options[:user_config]['remote']['host'],
|
56
|
+
port: options[:user_config]['remote']['ssh'],
|
57
|
+
user: options[:user_config]['remote']['username'],
|
58
|
+
password: options[:user_config]['remote']['password'],
|
59
|
+
timeout: 20
|
60
|
+
}
|
61
|
+
|
62
|
+
if options[:upload]
|
63
|
+
LegoEv3::Uploader
|
64
|
+
.new(ssh_config, options[:project])
|
65
|
+
.upload
|
66
|
+
end
|
67
|
+
|
68
|
+
if options[:mode] == :run_local
|
69
|
+
command =
|
70
|
+
"cd /home/#{options[:project].split('/').last} && " +
|
71
|
+
"ruby #{options[:user_config]['entry_point']}"
|
72
|
+
|
73
|
+
LegoEv3::SSHScript
|
74
|
+
.new(ssh_config, command)
|
75
|
+
.run
|
76
|
+
|
77
|
+
elsif options[:mode] == :run_remote
|
78
|
+
LegoEv3::user_config_overrides = options[:user_config]
|
79
|
+
load File.join(options[:project], options[:user_config]['entry_point'])
|
80
|
+
elsif options[:mode] == :server
|
81
|
+
command = "nohup lego-ev3-tcp-server -p #{options[:user_config]['remote']['tcp']} > /dev/null 2>&1 &"
|
82
|
+
|
83
|
+
LegoEv3::SSHScript
|
84
|
+
.new(ssh_config, command)
|
85
|
+
.run
|
86
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'lego_ev3'
|
4
|
+
require 'optparse'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
options = {
|
8
|
+
port: LegoEv3::default_user_config['remote']['tcp']
|
9
|
+
}
|
10
|
+
|
11
|
+
OptionParser.new do |opt|
|
12
|
+
opt.banner = 'Usage: lego-ev3-tcp-server [options]'
|
13
|
+
|
14
|
+
opt.on('-p', '--port PORT', 'The port to use') do |port|
|
15
|
+
options[:port] = port
|
16
|
+
end
|
17
|
+
end.parse!
|
18
|
+
|
19
|
+
LegoEv3::TCPServer.new(options[:port]).open
|
data/lib/brick.rb
CHANGED
@@ -35,6 +35,8 @@ module LegoEv3
|
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
+
@connection.flush("Refreshed ports and their status")
|
39
|
+
|
38
40
|
# Retrieve Port name -> Tacho motor in batch.
|
39
41
|
tacho_motors = {}
|
40
42
|
LegoEv3::Commands::TachoMotor.list!(@connection).each do |motor|
|
@@ -51,7 +53,7 @@ module LegoEv3
|
|
51
53
|
end
|
52
54
|
end
|
53
55
|
|
54
|
-
@connection.flush
|
56
|
+
@connection.flush("Refreshed motors and sensors")
|
55
57
|
|
56
58
|
# Assemble port info.
|
57
59
|
@ports.each do |port|
|
@@ -79,7 +81,12 @@ module LegoEv3
|
|
79
81
|
|
80
82
|
if driver_name == 'lego-ev3-touch'
|
81
83
|
LegoEv3::TouchSensor.new(@connection, id, p)
|
84
|
+
elsif driver_name == 'lego-ev3-color'
|
85
|
+
LegoEv3::ColorSensor.new(@connection, id, p)
|
86
|
+
elsif driver_name == 'lego-ev3-ir'
|
87
|
+
LegoEv3::InfraredSensor.new(@connection, id, p)
|
82
88
|
else
|
89
|
+
puts "The sensor #{driver_name} is not supported :( but I accept pull requests!"
|
83
90
|
nil
|
84
91
|
end
|
85
92
|
end.compact
|
data/lib/commands/builder.rb
CHANGED
@@ -17,11 +17,11 @@ module LegoEv3
|
|
17
17
|
end
|
18
18
|
end
|
19
19
|
|
20
|
-
def get(alias_name, type = String, command_name = nil, processor = nil)
|
20
|
+
def get(alias_name, type = String, command_name = nil, handle = 'r', processor = nil)
|
21
21
|
command_name ||= alias_name
|
22
22
|
|
23
23
|
self.define_singleton_method "get_#{alias_name}" do |connection, id, &callback|
|
24
|
-
connection.send("
|
24
|
+
connection.send(:read, "#{get_path(id)}/#{command_name}", nil, handle) do |response|
|
25
25
|
sanitized = (response || '').strip
|
26
26
|
|
27
27
|
if type == Integer
|
@@ -48,11 +48,11 @@ module LegoEv3
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
def set(alias_name, command_name = nil)
|
51
|
+
def set(alias_name, command_name = nil, handle = 'r')
|
52
52
|
command_name ||= alias_name
|
53
53
|
|
54
54
|
self.define_singleton_method "set_#{alias_name}" do |connection, id, value|
|
55
|
-
connection.send("
|
55
|
+
connection.send(:write, "#{get_path(id)}/#{command_name}", value.to_s, handle)
|
56
56
|
end
|
57
57
|
|
58
58
|
self.define_singleton_method "set_#{alias_name}!" do |connection, id, value|
|
@@ -62,17 +62,17 @@ module LegoEv3
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def get_set(alias_name, type = String, command_name = nil, processor = nil)
|
65
|
-
get(alias_name, type, command_name, processor)
|
66
|
-
set(alias_name, command_name)
|
65
|
+
get(alias_name, type, command_name, 'r+', processor)
|
66
|
+
set(alias_name, command_name, 'r+')
|
67
67
|
end
|
68
68
|
|
69
69
|
def command(alias_name, command_name = nil)
|
70
70
|
command_name ||= alias_name
|
71
71
|
|
72
72
|
self.define_singleton_method alias_name do |connection, id|
|
73
|
-
connection.send(
|
73
|
+
connection.send(:write, get_command_path(id), command_name, 'w')
|
74
74
|
end
|
75
|
-
|
75
|
+
|
76
76
|
self.define_singleton_method "#{alias_name}!" do |connection, id|
|
77
77
|
self.send(alias_name, connection, id)
|
78
78
|
connection.flush
|
@@ -81,11 +81,8 @@ module LegoEv3
|
|
81
81
|
|
82
82
|
def has_list
|
83
83
|
self.define_singleton_method :list do |connection, &callback|
|
84
|
-
connection.send(
|
85
|
-
|
86
|
-
entries_raw = response || ''
|
87
|
-
entries = entries_raw.include?('No such file or directory') ? [] : entries_raw.split(' ').map(&:strip)
|
88
|
-
callback.call(entries)
|
84
|
+
connection.send(:list, get_base_path, nil, 'r') do |response|
|
85
|
+
callback.call(response)
|
89
86
|
end
|
90
87
|
end
|
91
88
|
|
data/lib/commands/lego_sensor.rb
CHANGED
@@ -7,6 +7,8 @@ module LegoEv3::Commands::LegoSensor
|
|
7
7
|
get :driver_name
|
8
8
|
get :decimals, Integer
|
9
9
|
get :num_values, Integer
|
10
|
+
get_set :mode
|
11
|
+
get :modes, String, 'modes', 'r', -> response { response.split(' ').map(&:strip) }
|
10
12
|
get :value0, Integer
|
11
13
|
get :value1, Integer
|
12
14
|
get :value2, Integer
|
data/lib/commands/tacho_motor.rb
CHANGED
@@ -6,7 +6,7 @@ module LegoEv3::Commands::TachoMotor
|
|
6
6
|
get :port_name
|
7
7
|
get :count_per_rot, Integer
|
8
8
|
get :duty_cycle, Integer
|
9
|
-
get :states, String, 'state', -> response { response.split(' ').map{ |s| s.strip.to_sym } }
|
9
|
+
get :states, String, 'state', 'r', -> response { response.split(' ').map{ |s| s.strip.to_sym } }
|
10
10
|
get_set :duty_cycle_sp, Integer
|
11
11
|
get_set :position, Integer
|
12
12
|
get_set :position_sp, Integer
|
data/lib/connection/base.rb
CHANGED
@@ -4,28 +4,25 @@ module LegoEv3
|
|
4
4
|
@to_send = []
|
5
5
|
end
|
6
6
|
|
7
|
-
def send(
|
8
|
-
@to_send << [
|
7
|
+
def send(verb, path, value = nil, handle = 'r+', &callback)
|
8
|
+
@to_send << [[verb, path, value, handle], callback]
|
9
9
|
end
|
10
10
|
|
11
|
-
def flush
|
11
|
+
def flush(summary = nil)
|
12
12
|
@connection ||= create_connection
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
.join(';')
|
14
|
+
commands = @to_send.map{ |(c, _)| c }
|
15
|
+
callbacks = @to_send.map{ |(_, c)| c }
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
joined_response, time = LegoEv3::with_timer do
|
22
|
-
call_connection(joined_command)
|
17
|
+
responses, time = LegoEv3::with_timer do
|
18
|
+
call_connection(commands)
|
23
19
|
end
|
24
20
|
|
25
|
-
|
21
|
+
summary ||= commands
|
22
|
+
.map{ |c| "[#{c.join(' ')}]" }
|
23
|
+
.join(' ')
|
26
24
|
|
27
|
-
#
|
28
|
-
responses = joined_response.split("\n")
|
25
|
+
puts "#{summary}. #{time} ms."
|
29
26
|
|
30
27
|
callbacks.each_with_index.each do |c, i|
|
31
28
|
c.call(responses[i]) if c
|
@@ -1,36 +1,34 @@
|
|
1
|
-
require 'optparse'
|
2
|
-
|
3
1
|
module LegoEv3
|
4
2
|
class Connection
|
5
3
|
def initialize(user_config = {})
|
6
4
|
options = {
|
7
|
-
user_config: LegoEv3::
|
5
|
+
user_config: LegoEv3::resolve_user_config
|
8
6
|
}
|
9
7
|
|
10
|
-
|
11
|
-
opt.on('-c, --config PATH', 'Use the provided configuration at PATH.') do |path|
|
12
|
-
options[:user_config].merge!(LegoEv3::load_config(path))
|
13
|
-
end
|
14
|
-
end.parse!
|
15
|
-
is_local = `hostname`.strip == options[:user_config]['ssh']['hostname']
|
8
|
+
is_local = `hostname`.strip == options[:user_config]['remote']['hostname']
|
16
9
|
|
17
10
|
@inner_connection = if is_local
|
18
11
|
LegoEv3::LocalConnection.new
|
19
|
-
|
20
|
-
LegoEv3::
|
21
|
-
|
22
|
-
|
23
|
-
options[:user_config]['
|
12
|
+
elsif options[:user_config]['remote']['service'].to_sym == :tcp
|
13
|
+
LegoEv3::TCPConnection.new(options[:user_config]['remote']['host'] + ':' + options[:user_config]['remote']['tcp'].to_s)
|
14
|
+
elsif options[:user_config]['remote']['service'].to_sym == :ssh
|
15
|
+
LegoEv3::SSHConnection.new(
|
16
|
+
options[:user_config]['remote']['host'],
|
17
|
+
options[:user_config]['remote']['ssh'],
|
18
|
+
options[:user_config]['remote']['username'],
|
19
|
+
options[:user_config]['remote']['password']
|
24
20
|
)
|
21
|
+
else
|
22
|
+
raise LegoEv3::InvalidRemoteServiceException.new(options[:user_config]['remote']['service'])
|
25
23
|
end
|
26
24
|
end
|
27
25
|
|
28
|
-
def send(
|
29
|
-
@inner_connection.send(
|
26
|
+
def send(verb, path, value = nil, handle = 'r+', &callback)
|
27
|
+
@inner_connection.send(verb, path, value, handle, &callback)
|
30
28
|
end
|
31
29
|
|
32
|
-
def flush
|
33
|
-
@inner_connection.flush
|
30
|
+
def flush(summary = nil)
|
31
|
+
@inner_connection.flush(summary)
|
34
32
|
end
|
35
33
|
|
36
34
|
def close
|