lego_ev3 0.9.0
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 +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: []
|