lego_ev3 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/Gemfile +8 -0
- data/README.md +46 -0
- data/bin/lego-ev3 +33 -0
- data/lib/brick.rb +96 -0
- data/lib/commands/builder.rb +106 -0
- data/lib/commands/lego_port.rb +8 -0
- data/lib/commands/lego_sensor.rb +18 -0
- data/lib/commands/tacho_motor.rb +25 -0
- data/lib/connection/base.rb +37 -0
- data/lib/connection/connection.rb +50 -0
- data/lib/connection/local.rb +14 -0
- data/lib/connection/remote.rb +39 -0
- data/lib/connection/upload.rb +52 -0
- data/lib/exceptions.rb +11 -0
- data/lib/lego_ev3.rb +13 -0
- data/lib/sensors/base.rb +30 -0
- data/lib/sensors/touch.rb +23 -0
- data/lib/tacho_motor.rb +255 -0
- data/lib/utilities.rb +33 -0
- metadata +78 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 5b2fb90c9305d336e5d81ec1a7d7ac242542c1cc
|
4
|
+
data.tar.gz: fcc6149efda2798a9e3c1cfff391812511cf9e14
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2eb2ba5f4c07fa9e387845ff5d1d27d89114294f94a4f2482456ba3142f4f25f23c5bb302e3dfa39b21ff0bf4a2e42c6447be2c32ab8e4a6689728e693d91f39
|
7
|
+
data.tar.gz: 43e8cab0d3c77082c7ba1eab43c6f5d3299f9c939df5434f2aff32ba7976ee012101b018b49e6261350b9a4b825f37c048ca32c50873c61038762da40348769b
|
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# Lego EV3
|
2
|
+
|
3
|
+
## Getting started
|
4
|
+
|
5
|
+
### Make sure you have the necessary Ruby dependencies
|
6
|
+
|
7
|
+
The [ev3dev.org](http://www.ev3dev.org) distribution contains Ruby 2 but some libraries are missing to build native extensions. *This lib requires native extensions to work*.
|
8
|
+
|
9
|
+
Solution #1: Use [RVM](https://rvm.io).
|
10
|
+
Solution #2: Install those dependencies:
|
11
|
+
|
12
|
+
```
|
13
|
+
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
|
14
|
+
```
|
15
|
+
|
16
|
+
### Install the gem
|
17
|
+
|
18
|
+
```
|
19
|
+
gem install lego-ev3
|
20
|
+
```
|
21
|
+
|
22
|
+
### A simple script
|
23
|
+
|
24
|
+
```
|
25
|
+
require 'lego_ev3'
|
26
|
+
|
27
|
+
# The connection class look at the hostname of the machine to determine
|
28
|
+
# if the connection to establish must be local or remote (ssh).
|
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
|
+
})
|
36
|
+
|
37
|
+
brick = LegoEv3::Brick.new(connection)
|
38
|
+
|
39
|
+
# Plug the touch sensor in any input and run this script by pressing
|
40
|
+
# or not the sensor. The 'pressed' value should change accordingly.
|
41
|
+
s = brick.sensors.first
|
42
|
+
s.poll
|
43
|
+
puts s.info.inspect
|
44
|
+
|
45
|
+
connection.close
|
46
|
+
```
|
data/bin/lego-ev3
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'lego_ev3'
|
4
|
+
require 'optparse'
|
5
|
+
|
6
|
+
options = {
|
7
|
+
user_config: LegoEv3::default_user_config
|
8
|
+
}
|
9
|
+
|
10
|
+
OptionParser.new do |opt|
|
11
|
+
opt.on('-u', '--upload PROJECT', 'Upload everything under ./.../PROJECT to /home/PROJECT on the brick.') do |project|
|
12
|
+
options[:mode] = :upload
|
13
|
+
options[:project] = project
|
14
|
+
end
|
15
|
+
|
16
|
+
opt.on('-c, --config PATH', 'Use the provided configuration at PATH.') do |path|
|
17
|
+
options[:user_config].merge!(LegoEv3::load_config(path))
|
18
|
+
end
|
19
|
+
end.parse!
|
20
|
+
|
21
|
+
puts
|
22
|
+
puts "Config used:"
|
23
|
+
puts
|
24
|
+
ap options
|
25
|
+
puts
|
26
|
+
|
27
|
+
LegoEv3::Uploader
|
28
|
+
.new(
|
29
|
+
options[:user_config]['ssh']['host'],
|
30
|
+
options[:user_config]['ssh']['username'],
|
31
|
+
options[:user_config]['ssh']['password'],
|
32
|
+
options[:project])
|
33
|
+
.upload
|
data/lib/brick.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
module LegoEv3
|
2
|
+
class Brick
|
3
|
+
def initialize(connection)
|
4
|
+
@connection = connection
|
5
|
+
@ports = []
|
6
|
+
@devices = []
|
7
|
+
@motors = []
|
8
|
+
@sensors = []
|
9
|
+
|
10
|
+
refresh!
|
11
|
+
motors.each{ |m| m.reset }
|
12
|
+
end
|
13
|
+
|
14
|
+
def motors
|
15
|
+
@motors
|
16
|
+
end
|
17
|
+
|
18
|
+
def sensors
|
19
|
+
@sensors
|
20
|
+
end
|
21
|
+
|
22
|
+
def refresh!
|
23
|
+
@ports = LegoEv3::Commands::LegoPort.list!(@connection).map do |port|
|
24
|
+
{ id: port }
|
25
|
+
end
|
26
|
+
|
27
|
+
# Retrieve name and status in batch.
|
28
|
+
@ports.each do |port|
|
29
|
+
LegoEv3::Commands::LegoPort.get_port_name(@connection, port[:id]) do |name|
|
30
|
+
port[:name] = name
|
31
|
+
end
|
32
|
+
|
33
|
+
LegoEv3::Commands::LegoPort.get_status(@connection, port[:id]) do |status|
|
34
|
+
port[:status] = status
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Retrieve Port name -> Tacho motor in batch.
|
39
|
+
tacho_motors = {}
|
40
|
+
LegoEv3::Commands::TachoMotor.list!(@connection).each do |motor|
|
41
|
+
LegoEv3::Commands::TachoMotor.get_port_name(@connection, motor) do |port_name|
|
42
|
+
tacho_motors[port_name] = motor
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
# Retrieve Port name -> Lego sensor in batch.
|
47
|
+
lego_sensors = {}
|
48
|
+
LegoEv3::Commands::LegoSensor.list!(@connection).each do |sensor|
|
49
|
+
LegoEv3::Commands::LegoSensor.get_port_name(@connection, sensor) do |port_name|
|
50
|
+
lego_sensors[port_name] = sensor
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
@connection.flush
|
55
|
+
|
56
|
+
# Assemble port info.
|
57
|
+
@ports.each do |port|
|
58
|
+
status = port.delete(:status)
|
59
|
+
connected =
|
60
|
+
status != 'no-sensor' &&
|
61
|
+
status != 'no-motor' &&
|
62
|
+
status != 'error'
|
63
|
+
|
64
|
+
port[:type] = port[:name].start_with?('in') ? :sensor : :motor
|
65
|
+
port[:connected] = connected
|
66
|
+
port[:error] = status == 'error'
|
67
|
+
port[:driver] = connected ? status : nil
|
68
|
+
end
|
69
|
+
|
70
|
+
@motors = @ports
|
71
|
+
.select{ |p| p[:type] == :motor && p[:connected] }
|
72
|
+
.map{ |p| LegoEv3::TachoMotor.new(@connection, tacho_motors[p[:name]], p) }
|
73
|
+
|
74
|
+
@sensors = @ports
|
75
|
+
.select{ |p| p[:type] == :sensor && p[:connected] }
|
76
|
+
.map do |p|
|
77
|
+
id = lego_sensors[p[:name]]
|
78
|
+
driver_name = LegoEv3::Commands::LegoSensor.get_driver_name!(@connection, id)
|
79
|
+
|
80
|
+
if driver_name == 'lego-ev3-touch'
|
81
|
+
LegoEv3::TouchSensor.new(@connection, id, p)
|
82
|
+
else
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
end.compact
|
86
|
+
end
|
87
|
+
|
88
|
+
def info
|
89
|
+
{
|
90
|
+
ports: @ports,
|
91
|
+
motors: @motors.map(&:info),
|
92
|
+
sensors: @sensors.map(&:info)
|
93
|
+
}
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module LegoEv3
|
2
|
+
module Commands
|
3
|
+
module CommandBuilder
|
4
|
+
private
|
5
|
+
|
6
|
+
def base_path(path)
|
7
|
+
self.define_singleton_method "get_base_path" do
|
8
|
+
path
|
9
|
+
end
|
10
|
+
|
11
|
+
self.define_singleton_method "get_path" do |id|
|
12
|
+
get_base_path + "/#{id}"
|
13
|
+
end
|
14
|
+
|
15
|
+
self.define_singleton_method "get_command_path" do |id|
|
16
|
+
get_base_path + "/#{id}/command"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def get(alias_name, type = String, command_name = nil, processor = nil)
|
21
|
+
command_name ||= alias_name
|
22
|
+
|
23
|
+
self.define_singleton_method "get_#{alias_name}" do |connection, id, &callback|
|
24
|
+
connection.send("cat #{get_path(id)}/#{command_name}") do |response|
|
25
|
+
sanitized = (response || '').strip
|
26
|
+
|
27
|
+
if type == Integer
|
28
|
+
sanitized = sanitized.to_i
|
29
|
+
elsif type == Symbol
|
30
|
+
sanitized = sanitized.to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
sanitized = processor.call(sanitized) if processor
|
34
|
+
callback.call(sanitized) if callback
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
self.define_singleton_method "get_#{alias_name}!" do |connection, id|
|
39
|
+
return_value = nil
|
40
|
+
|
41
|
+
self.send("get_#{alias_name}", connection, id) do |response|
|
42
|
+
return_value = response
|
43
|
+
end
|
44
|
+
|
45
|
+
connection.flush
|
46
|
+
|
47
|
+
return_value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def set(alias_name, command_name = nil)
|
52
|
+
command_name ||= alias_name
|
53
|
+
|
54
|
+
self.define_singleton_method "set_#{alias_name}" do |connection, id, value|
|
55
|
+
connection.send("echo #{value} > #{get_path(id)}/#{command_name}")
|
56
|
+
end
|
57
|
+
|
58
|
+
self.define_singleton_method "set_#{alias_name}!" do |connection, id, value|
|
59
|
+
self.send("set_#{alias_name}", connection, id, value)
|
60
|
+
connection.flush
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
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)
|
67
|
+
end
|
68
|
+
|
69
|
+
def command(alias_name, command_name = nil)
|
70
|
+
command_name ||= alias_name
|
71
|
+
|
72
|
+
self.define_singleton_method alias_name do |connection, id|
|
73
|
+
connection.send("echo #{command_name} > #{get_command_path(id)}")
|
74
|
+
end
|
75
|
+
|
76
|
+
self.define_singleton_method "#{alias_name}!" do |connection, id|
|
77
|
+
self.send(alias_name, connection, id)
|
78
|
+
connection.flush
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def has_list
|
83
|
+
self.define_singleton_method :list do |connection, &callback|
|
84
|
+
connection.send("ls -C #{get_base_path}") do |response|
|
85
|
+
# TODO: Bug? The folder is not created if no sensor plugged in once.
|
86
|
+
entries_raw = response || ''
|
87
|
+
entries = entries_raw.include?('No such file or directory') ? [] : entries_raw.split(' ').map(&:strip)
|
88
|
+
callback.call(entries)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
self.define_singleton_method :list! do |connection|
|
93
|
+
entries = nil
|
94
|
+
|
95
|
+
list(connection) do |response|
|
96
|
+
entries = response
|
97
|
+
end
|
98
|
+
|
99
|
+
connection.flush
|
100
|
+
|
101
|
+
entries
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module LegoEv3::Commands::LegoSensor
|
2
|
+
extend LegoEv3::Commands::CommandBuilder
|
3
|
+
|
4
|
+
base_path '/sys/class/lego-sensor'
|
5
|
+
has_list
|
6
|
+
get :port_name
|
7
|
+
get :driver_name
|
8
|
+
get :decimals, Integer
|
9
|
+
get :num_values, Integer
|
10
|
+
get :value0, Integer
|
11
|
+
get :value1, Integer
|
12
|
+
get :value2, Integer
|
13
|
+
get :value3, Integer
|
14
|
+
get :value4, Integer
|
15
|
+
get :value5, Integer
|
16
|
+
get :value6, Integer
|
17
|
+
get :value7, Integer
|
18
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module LegoEv3::Commands::TachoMotor
|
2
|
+
extend LegoEv3::Commands::CommandBuilder
|
3
|
+
|
4
|
+
base_path '/sys/class/tacho-motor'
|
5
|
+
has_list
|
6
|
+
get :port_name
|
7
|
+
get :count_per_rot, Integer
|
8
|
+
get :duty_cycle, Integer
|
9
|
+
get :states, String, 'state', -> response { response.split(' ').map{ |s| s.strip.to_sym } }
|
10
|
+
get_set :duty_cycle_sp, Integer
|
11
|
+
get_set :position, Integer
|
12
|
+
get_set :position_sp, Integer
|
13
|
+
get_set :speed_sp, Integer
|
14
|
+
get_set :polarity, Symbol
|
15
|
+
get_set :time_sp, Integer
|
16
|
+
get_set :stop_command, Symbol
|
17
|
+
get_set :speed_regulation, Symbol, nil, -> (response) { response == :on }
|
18
|
+
command :run_forever, 'run-forever'
|
19
|
+
command :run_to_abs_pos, 'run-to-abs-pos'
|
20
|
+
#command :run_to_rel_pos, 'run-to-rel-pos' # not used.
|
21
|
+
command :run_timed, 'run-timed'
|
22
|
+
command :run_direct, 'run-direct'
|
23
|
+
command :stop
|
24
|
+
command :reset
|
25
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module LegoEv3
|
2
|
+
class BaseConnection
|
3
|
+
def initialize
|
4
|
+
@to_send = []
|
5
|
+
end
|
6
|
+
|
7
|
+
def send(command, &callback)
|
8
|
+
@to_send << [command, callback]
|
9
|
+
end
|
10
|
+
|
11
|
+
def flush
|
12
|
+
@connection ||= create_connection
|
13
|
+
|
14
|
+
joined_command = @to_send
|
15
|
+
.map{ |(c, _)| c }
|
16
|
+
.join(';')
|
17
|
+
|
18
|
+
callbacks = @to_send
|
19
|
+
.map{ |(_, c)| c }
|
20
|
+
|
21
|
+
joined_response, time = LegoEv3::with_timer do
|
22
|
+
call_connection(joined_command)
|
23
|
+
end
|
24
|
+
|
25
|
+
puts "#{joined_command}. #{time} ms."
|
26
|
+
|
27
|
+
# We assume that one command output one line of result.
|
28
|
+
responses = joined_response.split("\n")
|
29
|
+
|
30
|
+
callbacks.each_with_index.each do |c, i|
|
31
|
+
c.call(responses[i]) if c
|
32
|
+
end
|
33
|
+
|
34
|
+
@to_send.clear
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
require 'optparse'
|
2
|
+
|
3
|
+
module LegoEv3
|
4
|
+
class Connection
|
5
|
+
def initialize(user_config = {})
|
6
|
+
options = {
|
7
|
+
user_config: LegoEv3::default_user_config.merge(user_config)
|
8
|
+
}
|
9
|
+
|
10
|
+
OptionParser.new do |opt|
|
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']
|
16
|
+
|
17
|
+
@inner_connection = if is_local
|
18
|
+
LegoEv3::LocalConnection.new
|
19
|
+
else
|
20
|
+
LegoEv3::RemoteConnection.new(
|
21
|
+
options[:user_config]['ssh']['host'],
|
22
|
+
options[:user_config]['ssh']['username'],
|
23
|
+
options[:user_config]['ssh']['password']
|
24
|
+
)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
def send(command, &callback)
|
29
|
+
@inner_connection.send(command, &callback)
|
30
|
+
end
|
31
|
+
|
32
|
+
def flush
|
33
|
+
@inner_connection.flush
|
34
|
+
end
|
35
|
+
|
36
|
+
def close
|
37
|
+
@inner_connection.close
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def create_connection
|
43
|
+
@inner_connection.create_connection
|
44
|
+
end
|
45
|
+
|
46
|
+
def call_connection(command)
|
47
|
+
@inner_connection.call_connection(command)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'net/ssh/simple'
|
2
|
+
|
3
|
+
module LegoEv3
|
4
|
+
class RemoteConnection < BaseConnection
|
5
|
+
attr_accessor :timeout
|
6
|
+
|
7
|
+
def initialize(host, user, password)
|
8
|
+
super()
|
9
|
+
|
10
|
+
@host = host
|
11
|
+
@user = user
|
12
|
+
@password = password
|
13
|
+
@timeout = 10
|
14
|
+
end
|
15
|
+
|
16
|
+
def close
|
17
|
+
@connection.close if @connection
|
18
|
+
@connection = nil
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def create_connection
|
24
|
+
Net::SSH::Simple.new(host_name: @host, user: @user, password: @password, timeout: @timeout)
|
25
|
+
end
|
26
|
+
|
27
|
+
def call_connection(command)
|
28
|
+
begin
|
29
|
+
@connection.ssh(@host, command).stdout
|
30
|
+
rescue => e
|
31
|
+
if e.wrapped.kind_of?(Timeout::Error)
|
32
|
+
raise RemoteConnectionException.new(@host, @user, @password)
|
33
|
+
else
|
34
|
+
raise e
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'net/ssh/simple'
|
2
|
+
|
3
|
+
module LegoEv3
|
4
|
+
class Uploader
|
5
|
+
def initialize(host, user, password, project)
|
6
|
+
@host = host
|
7
|
+
@user = user
|
8
|
+
@password = password
|
9
|
+
@project = project
|
10
|
+
end
|
11
|
+
|
12
|
+
def upload
|
13
|
+
Net::SSH::Simple.sync({ host_name: @host, user: @user, password: @password, timeout: 600 }) do
|
14
|
+
puts "Creating folder #{project_folder}..."
|
15
|
+
ssh('ev3', "rm -rf #{project_folder}")
|
16
|
+
|
17
|
+
puts "Upload project..."
|
18
|
+
upload_folder(@project)
|
19
|
+
|
20
|
+
puts "Downloading dependencies from Gemfile..."
|
21
|
+
ssh('ev3', "cd #{project_folder} && gem install bundler && bundle install")
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def upload_file(src_relative, dst_relative)
|
28
|
+
file_remote = "#{project_folder}/#{dst_relative}"
|
29
|
+
puts "Sending #{src_relative} to #{file_remote}..."
|
30
|
+
scp_put('ev3', src_relative, file_remote)
|
31
|
+
end
|
32
|
+
|
33
|
+
def upload_folder(src_relative)
|
34
|
+
folders = Dir.glob("#{src_relative}/**/*/")
|
35
|
+
files = Dir.glob("#{src_relative}/**/*").select { |f| File.file?(f) }
|
36
|
+
|
37
|
+
folders.each do |path_relative|
|
38
|
+
folder_remote = "#{project_folder}/#{path_relative}"
|
39
|
+
puts "Creating folder #{folder_remote}..."
|
40
|
+
ssh('ev3', "mkdir -p #{folder_remote}")
|
41
|
+
end
|
42
|
+
|
43
|
+
files.each do |path_relative|
|
44
|
+
upload_file(path_relative, path_relative)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def project_folder
|
49
|
+
"/home/#{@project.split('/').last}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/exceptions.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
module LegoEv3
|
2
|
+
class RemoteConnectionException < Exception
|
3
|
+
def initialize(host, user, password)
|
4
|
+
super(
|
5
|
+
"Could not connect to the brick. " +
|
6
|
+
"Make sure these command works: " +
|
7
|
+
"[ping #{host}]" +
|
8
|
+
"[ssh #{user}@#{host} + enter your password]")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
data/lib/lego_ev3.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
def require_all_relative(directory)
|
2
|
+
Dir[File.join(File.dirname(__FILE__), directory, '**', '*.rb')].each do |file|
|
3
|
+
require file
|
4
|
+
end
|
5
|
+
end
|
6
|
+
|
7
|
+
require_relative 'utilities'
|
8
|
+
require_relative 'exceptions'
|
9
|
+
require_all_relative 'commands'
|
10
|
+
require_all_relative 'connection'
|
11
|
+
require_all_relative 'sensors'
|
12
|
+
require_relative 'tacho_motor'
|
13
|
+
require_relative 'brick'
|
data/lib/sensors/base.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
module LegoEv3
|
2
|
+
# More info: http://www.ev3dev.org/docs/drivers/lego-sensor-class/
|
3
|
+
class LegoSensor
|
4
|
+
def initialize(connection, id, port, driver_name)
|
5
|
+
@connection = connection
|
6
|
+
@id = id
|
7
|
+
@port = port
|
8
|
+
@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
|
+
end
|
20
|
+
|
21
|
+
def info
|
22
|
+
{
|
23
|
+
id: @id,
|
24
|
+
port: @port,
|
25
|
+
driver_name: @driver_name,
|
26
|
+
decimals: @decimals
|
27
|
+
}
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module LegoEv3
|
2
|
+
# More info: http://www.ev3dev.org/docs/drivers/lego-sensor-class/
|
3
|
+
class TouchSensor < LegoSensor
|
4
|
+
def initialize(connection, id, port)
|
5
|
+
super(connection, id, port, 'lego-ev3-touch')
|
6
|
+
end
|
7
|
+
|
8
|
+
def pressed?
|
9
|
+
@value == 1
|
10
|
+
end
|
11
|
+
|
12
|
+
def poll
|
13
|
+
@value = LegoEv3::Commands::LegoSensor.get_value0!(@connection, @id)
|
14
|
+
end
|
15
|
+
|
16
|
+
def info
|
17
|
+
super.merge({
|
18
|
+
sub_type: :touch,
|
19
|
+
pressed: pressed?
|
20
|
+
})
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/tacho_motor.rb
ADDED
@@ -0,0 +1,255 @@
|
|
1
|
+
module LegoEv3
|
2
|
+
# More info: http://www.ev3dev.org/docs/drivers/tacho-motor-class/
|
3
|
+
# TODO: ramp_up_sp, ramp_down_sp
|
4
|
+
# TODO: handle 'run_direct' and 'run_forever' states
|
5
|
+
# TODO: speed_regulation by default?
|
6
|
+
class TachoMotor
|
7
|
+
def initialize(connection, id, port)
|
8
|
+
@connection = connection
|
9
|
+
@id = id
|
10
|
+
@port = port
|
11
|
+
|
12
|
+
@speed = 0
|
13
|
+
@desired_speed = 0
|
14
|
+
@ticks_per_rotation = 0
|
15
|
+
@polarity = :normal
|
16
|
+
@position = 0
|
17
|
+
@desired_position = 0
|
18
|
+
@desired_time_ms = 0
|
19
|
+
@stop_mode = :coast
|
20
|
+
@running = false
|
21
|
+
@ramping = false
|
22
|
+
@holding = false
|
23
|
+
@stalled = false
|
24
|
+
@regulated_speed = false
|
25
|
+
|
26
|
+
sync!
|
27
|
+
end
|
28
|
+
|
29
|
+
# Run the motor indefinitely, until another command is sent.
|
30
|
+
def run_forever
|
31
|
+
ensure_valid_speed
|
32
|
+
|
33
|
+
LegoEv3::Commands::TachoMotor.run_forever!(@connection, @id)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Run to an absolute position specified by *desired_position*.
|
37
|
+
# Then stop using the current stop behavior.
|
38
|
+
def run_to_absolute_position(desired_position)
|
39
|
+
ensure_valid_speed
|
40
|
+
old_position = position
|
41
|
+
|
42
|
+
LegoEv3::Commands::TachoMotor.set_position_sp!(@connection, @id, desired_position)
|
43
|
+
LegoEv3::Commands::TachoMotor.run_to_abs_pos!(@connection, @id)
|
44
|
+
|
45
|
+
loop do
|
46
|
+
break if operation_completed?(old_position)
|
47
|
+
end
|
48
|
+
|
49
|
+
position
|
50
|
+
end
|
51
|
+
|
52
|
+
# Run to a position relative to the current position.
|
53
|
+
# The new position will be *position* + *desired_position*.
|
54
|
+
# Then stop using the current stop behavior.
|
55
|
+
def run_to_relative_position(desired_position)
|
56
|
+
ensure_valid_speed
|
57
|
+
old_position = position
|
58
|
+
|
59
|
+
LegoEv3::Commands::TachoMotor.set_position_sp!(@connection, @id, old_position + desired_position)
|
60
|
+
LegoEv3::Commands::TachoMotor.run_to_abs_pos!(@connection, @id)
|
61
|
+
|
62
|
+
loop do
|
63
|
+
ap info
|
64
|
+
break if operation_completed?(old_position)
|
65
|
+
end
|
66
|
+
|
67
|
+
position
|
68
|
+
end
|
69
|
+
|
70
|
+
# Run the motor for the amount of time specified in *desired_time_ms*.
|
71
|
+
# Then stop using the current stop behavior.
|
72
|
+
def run_timed(desired_time_ms)
|
73
|
+
ensure_valid_speed
|
74
|
+
old_position = position
|
75
|
+
|
76
|
+
LegoEv3::Commands::TachoMotor.set_time_sp!(@connection, @id, desired_time_ms.to_i)
|
77
|
+
LegoEv3::Commands::TachoMotor.run_timed!(@connection, @id)
|
78
|
+
|
79
|
+
loop do
|
80
|
+
break if operation_completed?(old_position)
|
81
|
+
end
|
82
|
+
|
83
|
+
position
|
84
|
+
end
|
85
|
+
|
86
|
+
# Run the motor at the *desired_speed*.
|
87
|
+
# Unlike other run commands, changing *desired_speed* take immediate effect.
|
88
|
+
def run_direct
|
89
|
+
LegoEv3::Commands::TachoMotor.run_direct!(@connection, @id)
|
90
|
+
end
|
91
|
+
|
92
|
+
def stop
|
93
|
+
LegoEv3::Commands::TachoMotor.stop!(@connection, @id)
|
94
|
+
end
|
95
|
+
|
96
|
+
def reset
|
97
|
+
LegoEv3::Commands::TachoMotor.reset!(@connection, @id)
|
98
|
+
sync!
|
99
|
+
end
|
100
|
+
|
101
|
+
def ticks_per_rotation
|
102
|
+
@ticks_per_rotation = LegoEv3::Commands::TachoMotor.get_count_per_rot!(@connection, @id)
|
103
|
+
end
|
104
|
+
|
105
|
+
def speed
|
106
|
+
@speed = LegoEv3::Commands::TachoMotor.get_duty_cycle!(@connection, @id)
|
107
|
+
end
|
108
|
+
|
109
|
+
# This is actually the desired speed but feels more natural.
|
110
|
+
def speed=(new_value)
|
111
|
+
sanitized = [[new_value.to_i, -100].max, 100].min
|
112
|
+
|
113
|
+
# I will probably learn why this is not right but for now,
|
114
|
+
# I sync those 2 values to keep it simple.
|
115
|
+
LegoEv3::Commands::TachoMotor.set_speed_sp!(@connection, @id, sanitized)
|
116
|
+
LegoEv3::Commands::TachoMotor.set_duty_cycle_sp!(@connection, @id, sanitized)
|
117
|
+
|
118
|
+
desired_speed
|
119
|
+
end
|
120
|
+
|
121
|
+
def desired_speed
|
122
|
+
@desired_speed = LegoEv3::Commands::TachoMotor.get_duty_cycle_sp!(@connection, @id)
|
123
|
+
end
|
124
|
+
|
125
|
+
def polarity
|
126
|
+
@polarity = LegoEv3::Commands::TachoMotor.get_polarity!(@connection, @id)
|
127
|
+
end
|
128
|
+
|
129
|
+
def polarity=(new_value)
|
130
|
+
unless [:normal, :inversed].include?(new_value.to_sym)
|
131
|
+
raise Exception.new('Invalid polarity. Possible values: :normal, :inversed.')
|
132
|
+
end
|
133
|
+
|
134
|
+
LegoEv3::Commands::TachoMotor.set_polarity!(@connection, @id, new_value)
|
135
|
+
polarity
|
136
|
+
end
|
137
|
+
|
138
|
+
def position
|
139
|
+
@position = LegoEv3::Commands::TachoMotor.get_position!(@connection, @id)
|
140
|
+
end
|
141
|
+
|
142
|
+
def position=(new_value)
|
143
|
+
LegoEv3::Commands::TachoMotor.set_position!(@connection, @id, new_value.to_i)
|
144
|
+
position
|
145
|
+
end
|
146
|
+
|
147
|
+
def desired_position
|
148
|
+
@desired_position = LegoEv3::Commands::TachoMotor.get_position_sp!(@connection, @id)
|
149
|
+
end
|
150
|
+
|
151
|
+
def desired_time
|
152
|
+
@desired_time = LegoEv3::Commands::TachoMotor.get_time_sp!(@connection, @id)
|
153
|
+
end
|
154
|
+
|
155
|
+
def stop_mode
|
156
|
+
@stop_mode = LegoEv3::Commands::TachoMotor.get_stop_command!(@connection, @id)
|
157
|
+
end
|
158
|
+
|
159
|
+
def stop_mode=(new_value)
|
160
|
+
unless [:coast, :brake, :hold].include?(new_value.to_sym)
|
161
|
+
raise Exception.new('Invalid stop behavior. Possible values: :coast, :brake, :hold.')
|
162
|
+
end
|
163
|
+
|
164
|
+
LegoEv3::Commands::TachoMotor.set_stop_command!(@connection, @id, new_value)
|
165
|
+
stop_mode
|
166
|
+
end
|
167
|
+
|
168
|
+
def states
|
169
|
+
@states = LegoEv3::Commands::TachoMotor.get_states!(@connection, @id)
|
170
|
+
end
|
171
|
+
|
172
|
+
def running
|
173
|
+
update_states
|
174
|
+
@running
|
175
|
+
end
|
176
|
+
|
177
|
+
def ramping
|
178
|
+
update_states
|
179
|
+
@ramping
|
180
|
+
end
|
181
|
+
|
182
|
+
def holding
|
183
|
+
update_states
|
184
|
+
@holding
|
185
|
+
end
|
186
|
+
|
187
|
+
def stalled
|
188
|
+
update_states
|
189
|
+
@stalled
|
190
|
+
end
|
191
|
+
|
192
|
+
def regulated_speed
|
193
|
+
@regulated_speed = LegoEv3::Commands::TachoMotor.get_speed_regulation!(@connection, @id)
|
194
|
+
end
|
195
|
+
|
196
|
+
def regulated_speed=(new_value)
|
197
|
+
LegoEv3::Commands::TachoMotor.set_speed_regulation!(@connection, @id, new_value.kind_of?(TrueClass) ? 'on' : 'off')
|
198
|
+
regulated_speed
|
199
|
+
end
|
200
|
+
|
201
|
+
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
|
211
|
+
|
212
|
+
info
|
213
|
+
end
|
214
|
+
|
215
|
+
def info
|
216
|
+
{
|
217
|
+
id: @id,
|
218
|
+
port: @port,
|
219
|
+
ticks_per_rotation: @ticks_per_rotation,
|
220
|
+
speed: @speed,
|
221
|
+
desired_speed: @desired_speed,
|
222
|
+
position: @position,
|
223
|
+
desired_position: @desired_position,
|
224
|
+
polarity: @polarity,
|
225
|
+
desired_time_ms: @desired_time_ms,
|
226
|
+
stop_mode: @stop_mode,
|
227
|
+
running: @running,
|
228
|
+
ramping: @ramping,
|
229
|
+
holding: @holding,
|
230
|
+
stalled: @stalled
|
231
|
+
}
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
236
|
+
def ensure_valid_speed
|
237
|
+
throw Exception.new('Speed is set to 0.') if desired_speed == 0
|
238
|
+
end
|
239
|
+
|
240
|
+
def update_states
|
241
|
+
states = LegoEv3::Commands::TachoMotor.get_states!(@connection, @id)
|
242
|
+
|
243
|
+
@running = states.include?(:running)
|
244
|
+
@ramping = states.include?(:ramping)
|
245
|
+
@holding = states.include?(:holding)
|
246
|
+
@stalled = states.include?(:stalled)
|
247
|
+
end
|
248
|
+
|
249
|
+
def operation_completed?(old_position)
|
250
|
+
update_states
|
251
|
+
|
252
|
+
!@running || @holding
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
data/lib/utilities.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module LegoEv3
|
4
|
+
# bar, time = with_timer do
|
5
|
+
# foo()
|
6
|
+
# end
|
7
|
+
#
|
8
|
+
def self.with_timer(&block)
|
9
|
+
start = Time.now
|
10
|
+
|
11
|
+
return_value = block.call
|
12
|
+
|
13
|
+
finish = Time.now
|
14
|
+
diff = finish - start
|
15
|
+
|
16
|
+
[return_value, (diff * 1000).to_i] # in ms.
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.default_user_config
|
20
|
+
{
|
21
|
+
'ssh' => {
|
22
|
+
'host' => '192.168.2.3', # I'm working on Mac OS X, eh.
|
23
|
+
'hostname' => 'ev3dev',
|
24
|
+
'username' => 'root',
|
25
|
+
'password' => 'r00tme'
|
26
|
+
}
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.load_config(path)
|
31
|
+
YAML::load(File.open(path))
|
32
|
+
end
|
33
|
+
end
|
metadata
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: lego_ev3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.9.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jodi Giordano
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: net-ssh-simple
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.6'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.6'
|
27
|
+
description: Uses the amazing ev3dev.org stuff to interface with the Lego EV3 starter
|
28
|
+
kit
|
29
|
+
email: giordano.jodi@gmail.com
|
30
|
+
executables:
|
31
|
+
- lego-ev3
|
32
|
+
extensions: []
|
33
|
+
extra_rdoc_files: []
|
34
|
+
files:
|
35
|
+
- Gemfile
|
36
|
+
- README.md
|
37
|
+
- bin/lego-ev3
|
38
|
+
- lib/brick.rb
|
39
|
+
- lib/commands/builder.rb
|
40
|
+
- lib/commands/lego_port.rb
|
41
|
+
- lib/commands/lego_sensor.rb
|
42
|
+
- lib/commands/tacho_motor.rb
|
43
|
+
- lib/connection/base.rb
|
44
|
+
- lib/connection/connection.rb
|
45
|
+
- lib/connection/local.rb
|
46
|
+
- lib/connection/remote.rb
|
47
|
+
- lib/connection/upload.rb
|
48
|
+
- lib/exceptions.rb
|
49
|
+
- lib/lego_ev3.rb
|
50
|
+
- lib/sensors/base.rb
|
51
|
+
- lib/sensors/touch.rb
|
52
|
+
- lib/tacho_motor.rb
|
53
|
+
- lib/utilities.rb
|
54
|
+
homepage: https://github.com/jodigiordano/lego_ev3
|
55
|
+
licenses:
|
56
|
+
- MIT
|
57
|
+
metadata: {}
|
58
|
+
post_install_message:
|
59
|
+
rdoc_options: []
|
60
|
+
require_paths:
|
61
|
+
- lib
|
62
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '0'
|
72
|
+
requirements: []
|
73
|
+
rubyforge_project:
|
74
|
+
rubygems_version: 2.4.6
|
75
|
+
signing_key:
|
76
|
+
specification_version: 4
|
77
|
+
summary: Library to interface with Lego EV3 starter kit
|
78
|
+
test_files: []
|