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.
@@ -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; end
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(command)
11
- `command`
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
@@ -2,51 +2,61 @@ require 'net/ssh/simple'
2
2
 
3
3
  module LegoEv3
4
4
  class Uploader
5
- def initialize(host, user, password, project)
6
- @host = host
7
- @user = user
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({ host_name: @host, user: @user, password: @password, timeout: 600 }) do
14
- puts "Creating folder #{project_folder}..."
15
- ssh('ev3', "rm -rf #{project_folder}")
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(@project)
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
- puts "Downloading dependencies from Gemfile..."
21
- ssh('ev3', "cd #{project_folder} && gem install bundler && bundle install")
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(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)
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(src_relative)
34
- folders = Dir.glob("#{src_relative}/**/*/")
35
- files = Dir.glob("#{src_relative}/**/*").select { |f| File.file?(f) }
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 |path_relative|
38
- folder_remote = "#{project_folder}/#{path_relative}"
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 |path_relative|
44
- upload_file(path_relative, path_relative)
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 project_folder
49
- "/home/#{@project.split('/').last}"
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
@@ -1,5 +1,5 @@
1
1
  module LegoEv3
2
- class RemoteConnectionException < Exception
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