diff --git a/README.md b/README.md index 5af6bd0..30fb67d 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,8 @@ Or install it yourself as: ```ruby require 'ruby-itach-ip2ir' device = RubyItachIp2ir::Device.new("192.168.0.108") -device.connect +timeout = 10 #seconds +device.connect(timeout) device.set_learning_mode(true) device.listen_for_learning_responses{|resp| puts "send_ir_string = #{resp.inspect}" } @@ -32,6 +33,8 @@ device.send_ir_raw(send_ir_string) device.send_ir( device_id, request_id, freq, repeat, offset, ir_string ) device.send_ir( "1:3", :auto, 37878, 1, 1, "125,61,16,15,16,15..." ) + +device.close ``` ## Contributing diff --git a/lib/ruby-itach-ip2ir/device.rb b/lib/ruby-itach-ip2ir/device.rb index 897a01d..44539f9 100644 --- a/lib/ruby-itach-ip2ir/device.rb +++ b/lib/ruby-itach-ip2ir/device.rb @@ -1,42 +1,64 @@ -=begin -require 'ruby-itach-ip2ir' -device = RubyItachIp2ir::Device.new("192.168.0.108") -device.connect - -device.set_learning_mode(true) -device.listen_for_learning_responses{|resp| puts "send_ir_string = #{resp.inspect}" } -# send_ir_string = "sendir,1:3,6,37878,1,1,125,61,16,15,16,15... -device.set_learning_mode(false) - -device.send_ir_raw(send_ir_string) - -device.send_ir( device_id, request_id, freq, repeat, offset, ir_string ) -device.send_ir( "1:3", :auto, 37878, 1, 1, "125,61,16,15,16,15..." ) - -=end - require "socket" class RubyItachIp2ir::Device - attr_accessor :ip - attr_accessor :socket - attr_accessor :requests_count + attr_accessor :ip, :port, :socket, :requests_count - def initialize(ip) + def initialize(ip, port = 4998) self.ip = ip + self.port = port self.requests_count = 0 end + def connect(timeout = 5) + # Uses non-blocking connections to make the timeout handling more efficient. + # Taken from http://spin.atomicobject.com/2013/09/30/socket-connection-timeout-ruby/ + + host = self.ip + port = self.port + + addr = Socket.getaddrinfo(host, nil) + sockaddr = Socket.pack_sockaddr_in(port, addr[0][3]) + + begin + + self.socket = Socket.new(Socket.const_get(addr[0][0]), Socket::SOCK_STREAM, 0) + self.socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) + + + # Initiate the socket connection in the background. If it doesn't fail + # immediatelyit will raise an IO::WaitWritable (Errno::EINPROGRESS) + # indicating the connection is in progress. + self.socket.connect_nonblock(sockaddr) + + rescue IO::WaitWritable + # IO.select will block until the socket is writable or the timeout + # is exceeded - whichever comes first. + if IO.select(nil, [socket], nil, timeout) + begin + # Verify there is now a good connection + self.socket.connect_nonblock(sockaddr) + rescue Errno::EISCONN + # Good news everybody, the socket is connected! + + rescue + # An unexpected exception was raised - the connection is no good. + self.socket.close + raise + end + else + # IO.select returns nil when the socket is not ready before timeout + # seconds have elapsed + self.socket.close + raise "Connection timeout" + end + end - def connect - self.socket = TCPSocket.new(self.ip,4998) end def connected? !!self.socket end - def set_learning_mode(state) if state write("get_IRL\r") @@ -57,7 +79,6 @@ def listen_for_learning_responses(&block) end end - # TODO: this will receive string starting with "sendir,...", handle it def send_ir_raw(send_ir_string) send_ir( *send_ir_string.split(",",7) ) @@ -70,17 +91,17 @@ def send_ir(device_id,request_id,freq,repeat,offset,ir_string) send_ir_string = [device_id,request_id,freq,repeat,offset,ir_string].join(",") write("sendir,#{send_ir_string}\r") - expect_response("completeir,#{device_id},#{request_id}\r" => true) + expect_response("completeir,#{device_id},#{request_id}\r" => true) end - def generate_request_id self.requests_count = 0 if self.requests_count >= 65535 self.requests_count += 1 - self.requests_count end - + def close + self.socket.close if connected? + end protected @@ -115,8 +136,6 @@ def expect_response(expected_hash) end end - - class UnexpectedResponse < RuntimeError; end class BadDeviceIdFormat < RuntimeError; end