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