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
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
|