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.
- checksums.yaml +4 -4
- data/README.md +112 -29
- data/bin/lego-ev3 +66 -13
- data/bin/lego-ev3-tcp-server +19 -0
- data/lib/brick.rb +8 -1
- data/lib/commands/builder.rb +10 -13
- data/lib/commands/lego_sensor.rb +2 -0
- data/lib/commands/tacho_motor.rb +1 -1
- data/lib/connection/base.rb +11 -14
- data/lib/connection/connection.rb +16 -18
- data/lib/connection/local.rb +48 -3
- data/lib/connection/ssh.rb +84 -0
- data/lib/connection/ssh_command.rb +44 -0
- data/lib/connection/tcp_client.rb +64 -0
- data/lib/connection/tcp_server.rb +63 -0
- data/lib/connection/upload.rb +34 -24
- data/lib/exceptions.rb +35 -1
- data/lib/lego_ev3.rb +7 -0
- data/lib/sensors/base.rb +19 -12
- data/lib/sensors/color.rb +76 -0
- data/lib/sensors/infrared.rb +142 -0
- data/lib/sensors/touch.rb +1 -2
- data/lib/tacho_motor.rb +49 -28
- data/lib/utilities.rb +38 -2
- metadata +10 -3
- data/lib/connection/remote.rb +0 -39
data/lib/connection/local.rb
CHANGED
@@ -1,14 +1,59 @@
|
|
1
1
|
module LegoEv3
|
2
2
|
class LocalConnection < BaseConnection
|
3
|
+
def initialize
|
4
|
+
super()
|
5
|
+
@file_handles = {}
|
6
|
+
end
|
3
7
|
|
4
|
-
def close
|
8
|
+
def close
|
9
|
+
@file_handles.values.each do |handle|
|
10
|
+
handle.close
|
11
|
+
end
|
12
|
+
end
|
5
13
|
|
6
14
|
protected
|
7
15
|
|
8
16
|
def create_connection; end
|
9
17
|
|
10
|
-
def call_connection(
|
11
|
-
|
18
|
+
def call_connection(commands)
|
19
|
+
commands.map do |(verb, path, value, handle)|
|
20
|
+
if verb == :list then list(path)
|
21
|
+
elsif verb == :read then read(path, handle)
|
22
|
+
elsif verb == :write then write(path, value, handle)
|
23
|
+
else raise LegoEv3::InvalidCommandException.new(verb)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def list(path)
|
31
|
+
f = get_folder_handler(path)
|
32
|
+
f.map{ |f| f } - ['.', '..']
|
33
|
+
rescue
|
34
|
+
[] # TODO: Bug? The folder is not created if no sensor plugged in once.
|
35
|
+
end
|
36
|
+
|
37
|
+
def read(path, handle)
|
38
|
+
f = get_file_handler(path, handle)
|
39
|
+
f.rewind
|
40
|
+
f.read
|
41
|
+
end
|
42
|
+
|
43
|
+
def write(path, value, handle)
|
44
|
+
f = get_file_handler(path, handle)
|
45
|
+
f.rewind
|
46
|
+
f.write(value)
|
47
|
+
f.flush
|
48
|
+
nil
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_file_handler(path, handle)
|
52
|
+
@file_handles[path] ||= File.open(path, handle)
|
53
|
+
end
|
54
|
+
|
55
|
+
def get_folder_handler(path)
|
56
|
+
@file_handles[path] ||= Dir.open(path)
|
12
57
|
end
|
13
58
|
end
|
14
59
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'net/ssh/simple'
|
2
|
+
|
3
|
+
module LegoEv3
|
4
|
+
class SSHConnection < BaseConnection
|
5
|
+
attr_accessor :timeout
|
6
|
+
|
7
|
+
def initialize(host, port, user, password)
|
8
|
+
super()
|
9
|
+
|
10
|
+
@host = host
|
11
|
+
@port = port
|
12
|
+
@user = user
|
13
|
+
@password = password
|
14
|
+
@timeout = 10
|
15
|
+
end
|
16
|
+
|
17
|
+
def close
|
18
|
+
@connection.close if @connection
|
19
|
+
@connection = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def create_connection
|
25
|
+
Net::SSH::Simple.new(host_name: @host, port: @port, user: @user, password: @password, timeout: @timeout)
|
26
|
+
end
|
27
|
+
|
28
|
+
def call_connection(commands)
|
29
|
+
begin
|
30
|
+
joined_command = join_commands(commands)
|
31
|
+
|
32
|
+
puts "Sending: #{joined_command}"
|
33
|
+
|
34
|
+
responses = @connection
|
35
|
+
.ssh(@host, joined_command)
|
36
|
+
.stdout
|
37
|
+
.split("\n")
|
38
|
+
|
39
|
+
commands.map do |(verb, _, _, _)|
|
40
|
+
if verb == :list
|
41
|
+
# TODO: Bug? The folder is not created if no sensor plugged in once.
|
42
|
+
r = responses.shift || ''
|
43
|
+
r.include?('No such file or directory') ? [] : r.split(' ').map(&:strip)
|
44
|
+
elsif verb == :read
|
45
|
+
responses.shift
|
46
|
+
elsif verb == :write
|
47
|
+
nil
|
48
|
+
else raise new LegoEv3::InvalidCommandException(verb)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
rescue => e
|
52
|
+
if e.wrapped.kind_of?(Timeout::Error)
|
53
|
+
raise SSHConnectionException.new(@host, @user, @password)
|
54
|
+
else
|
55
|
+
raise e
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def join_commands(commands)
|
63
|
+
commands.map do |(verb, path, value, _)|
|
64
|
+
if verb == :list then list(path)
|
65
|
+
elsif verb == :read then read(path)
|
66
|
+
elsif verb == :write then write(path, value)
|
67
|
+
else raise new LegoEv3::InvalidCommandException(verb)
|
68
|
+
end
|
69
|
+
end.join(';')
|
70
|
+
end
|
71
|
+
|
72
|
+
def list(path)
|
73
|
+
"ls -C #{path}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def read(path)
|
77
|
+
"cat #{path}"
|
78
|
+
end
|
79
|
+
|
80
|
+
def write(path, value = nil)
|
81
|
+
"echo #{value} > #{path}"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'net/ssh/simple'
|
2
|
+
|
3
|
+
module LegoEv3
|
4
|
+
class SSHScript
|
5
|
+
def initialize(ssh_config, command)
|
6
|
+
@ssh_config = ssh_config
|
7
|
+
@command = command
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
puts "Connecting to brick..."
|
12
|
+
|
13
|
+
Net::SSH::Simple.sync(@ssh_config) do
|
14
|
+
buffer = ''
|
15
|
+
|
16
|
+
puts "Opening Bash session..."
|
17
|
+
|
18
|
+
ssh('ev3', '/bin/sh') do |event, console, d|
|
19
|
+
case event
|
20
|
+
when :start
|
21
|
+
puts "Executing #{@command} locally..."
|
22
|
+
|
23
|
+
console.send_data(@command)
|
24
|
+
console.eof!
|
25
|
+
when :stdout
|
26
|
+
buffer << d
|
27
|
+
while line = buffer.slice!(/(.*)\r?\n/) do puts line end
|
28
|
+
:no_append
|
29
|
+
when :stderr
|
30
|
+
while line = buffer.slice!(/(.*)\r?\n/) do puts line end
|
31
|
+
:no_append
|
32
|
+
when :exit_code
|
33
|
+
puts d
|
34
|
+
when :exit_signal
|
35
|
+
puts d
|
36
|
+
:no_raise
|
37
|
+
when :finish
|
38
|
+
puts "Done."
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module LegoEv3
|
5
|
+
class TCPConnection < BaseConnection
|
6
|
+
def initialize(host)
|
7
|
+
super()
|
8
|
+
|
9
|
+
@host = host
|
10
|
+
end
|
11
|
+
|
12
|
+
def close
|
13
|
+
@connection.close if @connection
|
14
|
+
@connection = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
protected
|
18
|
+
|
19
|
+
def create_connection
|
20
|
+
socket = TCPSocket.new(@host.split(':').first, @host.split(':').last)
|
21
|
+
socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
22
|
+
socket
|
23
|
+
end
|
24
|
+
|
25
|
+
def call_connection(commands)
|
26
|
+
joined_command = join_commands(commands)
|
27
|
+
|
28
|
+
puts "Sending: #{joined_command}"
|
29
|
+
|
30
|
+
@connection.puts(Base64.strict_encode64(Marshal.dump(joined_command)))
|
31
|
+
|
32
|
+
responses = []
|
33
|
+
|
34
|
+
while responses.count < commands.count
|
35
|
+
raw = @connection.gets
|
36
|
+
response = Marshal.load(Base64.strict_decode64(raw.rstrip))
|
37
|
+
puts "Received: #{response}"
|
38
|
+
responses << response
|
39
|
+
end
|
40
|
+
|
41
|
+
responses
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def join_commands(commands)
|
47
|
+
commands
|
48
|
+
.map { |command| command.join(';') }
|
49
|
+
.join("\n")
|
50
|
+
end
|
51
|
+
|
52
|
+
def list(path)
|
53
|
+
"ls -C #{path}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def read(path)
|
57
|
+
"cat #{path}"
|
58
|
+
end
|
59
|
+
|
60
|
+
def write(path, value = nil)
|
61
|
+
"echo #{value} > #{path}"
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'socket'
|
2
|
+
require 'base64'
|
3
|
+
|
4
|
+
module LegoEv3
|
5
|
+
class TCPServer
|
6
|
+
def initialize(port)
|
7
|
+
@port = port
|
8
|
+
@local_connection = LegoEv3::LocalConnection.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def open
|
12
|
+
@server = ::TCPServer.new(@port)
|
13
|
+
|
14
|
+
hostname = `hostname`.strip
|
15
|
+
|
16
|
+
puts "Started TCP server on #{hostname}:#{@port}."
|
17
|
+
puts "Waiting for clients..."
|
18
|
+
puts "Press CTRL+C to interrupt at any time."
|
19
|
+
|
20
|
+
loop do
|
21
|
+
client = @server.accept
|
22
|
+
client.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
23
|
+
puts "Client connected."
|
24
|
+
|
25
|
+
while raw_request = client.gets
|
26
|
+
request = Marshal.load(Base64.strict_decode64(raw_request.rstrip))
|
27
|
+
puts "Received: #{request}"
|
28
|
+
|
29
|
+
receive_commands(request).each do |(verb, path, value, handle)|
|
30
|
+
@local_connection.send(verb.to_sym, path, value, handle) do |response|
|
31
|
+
puts "Sending: #{response}"
|
32
|
+
client.puts(Base64.strict_encode64(Marshal.dump(response)))
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
@local_connection.flush
|
37
|
+
end
|
38
|
+
|
39
|
+
client.close
|
40
|
+
puts "Client closed."
|
41
|
+
end
|
42
|
+
rescue SystemExit, Interrupt
|
43
|
+
close
|
44
|
+
end
|
45
|
+
|
46
|
+
def close
|
47
|
+
@server.close if @server
|
48
|
+
@server = nil
|
49
|
+
|
50
|
+
@local_connection.close if @local_connection
|
51
|
+
@local_connection = nil
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def receive_commands(raw)
|
57
|
+
raw
|
58
|
+
.strip
|
59
|
+
.split("\n")
|
60
|
+
.map{ |c| c.split(';') }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
data/lib/connection/upload.rb
CHANGED
@@ -2,51 +2,61 @@ require 'net/ssh/simple'
|
|
2
2
|
|
3
3
|
module LegoEv3
|
4
4
|
class Uploader
|
5
|
-
def initialize(
|
6
|
-
@
|
7
|
-
@
|
8
|
-
@password = password
|
9
|
-
@project = project
|
5
|
+
def initialize(ssh_config, project)
|
6
|
+
@ssh_config = ssh_config
|
7
|
+
@local_project_path = Pathname.new(project).realpath.to_s
|
10
8
|
end
|
11
9
|
|
12
10
|
def upload
|
13
|
-
Net::SSH::Simple.sync(
|
14
|
-
puts "
|
15
|
-
ssh('ev3', "rm -rf #{
|
11
|
+
Net::SSH::Simple.sync(@ssh_config) do
|
12
|
+
puts "Removing previous folder #{remote_project_path}..."
|
13
|
+
ssh('ev3', "rm -rf #{remote_project_path}/**/*")
|
14
|
+
|
15
|
+
puts "Creating folder #{remote_project_path}..."
|
16
|
+
ssh('ev3', "mkdir -p #{remote_project_path}")
|
16
17
|
|
17
18
|
puts "Upload project..."
|
18
|
-
upload_folder(@
|
19
|
+
upload_folder(@local_project_path)
|
20
|
+
|
21
|
+
# Takes too much time, better to do it once on the brick.
|
22
|
+
#puts "Installing lib..."
|
23
|
+
#ssh('ev3', "cd #{remote_project_path} && gem install lego_ev3")
|
19
24
|
|
20
|
-
|
21
|
-
|
25
|
+
# Takes too much time, better to do it once on the brick.
|
26
|
+
#puts "Downloading dependencies from Gemfile..."
|
27
|
+
#ssh('ev3', "cd #{remote_project_path} && gem install bundler && bundle install")
|
22
28
|
end
|
23
29
|
end
|
24
30
|
|
25
31
|
private
|
26
32
|
|
27
|
-
def upload_file(
|
28
|
-
file_remote = "#{
|
29
|
-
puts "Sending #{
|
30
|
-
scp_put('ev3',
|
33
|
+
def upload_file(src, dst_relative)
|
34
|
+
file_remote = "#{remote_project_path}/#{dst_relative}"
|
35
|
+
puts "Sending #{src} to #{file_remote}..."
|
36
|
+
scp_put('ev3', src, file_remote)
|
31
37
|
end
|
32
38
|
|
33
|
-
def upload_folder(
|
34
|
-
folders = Dir.glob("#{
|
35
|
-
files = Dir.glob("#{
|
39
|
+
def upload_folder(src)
|
40
|
+
folders = Dir.glob("#{src}/**/*/")
|
41
|
+
files = Dir.glob("#{src}/**/*").select { |f| File.file?(f) }
|
36
42
|
|
37
|
-
folders.each do |
|
38
|
-
folder_remote = "#{
|
43
|
+
folders.each do |path|
|
44
|
+
folder_remote = "#{remote_project_path}/#{local_abs_to_rel_path(path)}"
|
39
45
|
puts "Creating folder #{folder_remote}..."
|
40
46
|
ssh('ev3', "mkdir -p #{folder_remote}")
|
41
47
|
end
|
42
48
|
|
43
|
-
files.each do |
|
44
|
-
upload_file(
|
49
|
+
files.each do |path|
|
50
|
+
upload_file(path, local_abs_to_rel_path(path))
|
45
51
|
end
|
46
52
|
end
|
47
53
|
|
48
|
-
def
|
49
|
-
|
54
|
+
def local_abs_to_rel_path(path)
|
55
|
+
path.gsub(/^#{@local_project_path}\//, '')
|
56
|
+
end
|
57
|
+
|
58
|
+
def remote_project_path
|
59
|
+
"/home/#{@local_project_path.split('/').last}"
|
50
60
|
end
|
51
61
|
end
|
52
62
|
end
|
data/lib/exceptions.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
module LegoEv3
|
2
|
-
class
|
2
|
+
class SSHConnectionException < Exception
|
3
3
|
def initialize(host, user, password)
|
4
4
|
super(
|
5
5
|
"Could not connect to the brick. " +
|
@@ -8,4 +8,38 @@ module LegoEv3
|
|
8
8
|
"[ssh #{user}@#{host} + enter your password]")
|
9
9
|
end
|
10
10
|
end
|
11
|
+
|
12
|
+
class InvalidCommandException < Exception
|
13
|
+
def initialize(verb)
|
14
|
+
super(
|
15
|
+
"The command with verb #{verb} is not supported." +
|
16
|
+
"You must implement it.")
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class InvalidModeException < Exception
|
21
|
+
def initialize(type, sub_type, mode, valid_modes)
|
22
|
+
super(
|
23
|
+
"The mode #{mode} is not valid on the #{type} of type #{sub_type}. " +
|
24
|
+
"Supported modes are: #{valid_modes}")
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
class InvalidChannelException < Exception
|
29
|
+
def initialize(channel)
|
30
|
+
super(
|
31
|
+
"The channel #{channel} is not supported in the mode :control_alt of " +
|
32
|
+
"the infrared sensor. The only supported channel is 1."
|
33
|
+
)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class InvalidRemoteServiceException < Exception
|
38
|
+
def initialize(service)
|
39
|
+
super(
|
40
|
+
"The 'remote.service' configuration is not valid. " +
|
41
|
+
"Supported services are: 'tcp', 'ssh'."
|
42
|
+
)
|
43
|
+
end
|
44
|
+
end
|
11
45
|
end
|