Class: Tinkerforge::IPConnection

Inherits:
Object
  • Object
show all
Defined in:
lib/tinkerforge/ip_connection.rb

Constant Summary collapse

CALLBACK_ENUMERATE =
253
CALLBACK_CONNECTED =
0
CALLBACK_DISCONNECTED =
1
ENUMERATION_TYPE_AVAILABLE =

enumeration_type parameter for CALLBACK_ENUMERATE

0
ENUMERATION_TYPE_CONNECTED =
1
ENUMERATION_TYPE_DISCONNECTED =
2
CONNECT_REASON_REQUEST =

connect_reason parameter for CALLBACK_CONNECTED

0
CONNECT_REASON_AUTO_RECONNECT =
1
DISCONNECT_REASON_REQUEST =

disconnect_reason parameter for CALLBACK_DISCONNECTED

0
DISCONNECT_REASON_ERROR =
1
DISCONNECT_REASON_SHUTDOWN =
2
CONNECTION_STATE_DISCONNECTED =

returned by get_connection_state

0
CONNECTION_STATE_CONNECTED =
1
CONNECTION_STATE_PENDING =

auto-reconnect in progress

2

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeIPConnection

Creates an IP Connection object that can be used to enumerate the available devices. It is also required for the constructor of Bricks and Bricklets.



582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
# File 'lib/tinkerforge/ip_connection.rb', line 582

def initialize
  @host = nil
  @port = 0

  @timeout = 2.5

  @auto_reconnect = true
  @auto_reconnect_allowed = false
  @auto_reconnect_pending = false

  @next_sequence_number = 0 # protected by sequence_number_mutex
  @sequence_number_mutex = Mutex.new

  @next_authentication_nonce = 0 # protected by authentication_mutex
  @authentication_mutex = Mutex.new # protects authentication handshake

  @devices = {}
  @replace_mutex = Mutex.new # used to synchronize replacements in the devices dict

  @registered_callbacks = {}

  @socket_mutex = Mutex.new
  @socket_send_mutex = Mutex.new
  @socket = nil # protected by socket_mutex
  @socket_id = 0 # protected by socket_mutex

  @receive_flag = false
  @receive_thread = nil

  @callback = nil

  @disconnect_probe_flag = false
  @disconnect_probe_queue = nil
  @disconnect_probe_thread = nil # protected by socket_mutex

  @waiter_queue = Queue.new

  @brickd = BrickDaemon.new '2', self
end

Instance Attribute Details

#timeoutObject

Returns the value of attribute timeout.



554
555
556
# File 'lib/tinkerforge/ip_connection.rb', line 554

def timeout
  @timeout
end

Instance Method Details

#add_device(device) ⇒ Object

internal



794
795
796
797
798
799
800
801
802
803
804
# File 'lib/tinkerforge/ip_connection.rb', line 794

def add_device(device)
  @replace_mutex.synchronize {
    replaced_device = @devices.fetch device.uid, nil

    if replaced_device != nil
      replaced_device.replaced = true
    end

    @devices[device.uid] = device # FIXME: use a weakref here
  }
end

#authenticate(secret) ⇒ Object

Performs an authentication handshake with the connected Brick Daemon or WIFI/Ethernet Extension. If the handshake succeeds the connection switches from non-authenticated to authenticated state and communication can continue as normal. If the handshake fails then the connection gets closed. Authentication can fail if the wrong secret was used or if authentication is not enabled at all on the Brick Daemon or the WIFI/Ethernet Extension.

For more information about authentication see www.tinkerforge.com/en/doc/Tutorials/Tutorial_Authentication/Tutorial.html



689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
# File 'lib/tinkerforge/ip_connection.rb', line 689

def authenticate(secret)
  if not secret.ascii_only?
    raise ArgumentError, "Authentication secret contains non-ASCII characters"
  end
  @authentication_mutex.synchronize {
    if @next_authentication_nonce == 0
      @next_authentication_nonce = SecureRandom.random_number(1 << 32)
    end

    server_nonce = @brickd.get_authentication_nonce
    client_nonce = Packer.unpack(Packer.pack([@next_authentication_nonce], 'L'), 'C4')[0]
    @next_authentication_nonce += 1
    nonce_bytes = Packer.pack [server_nonce, client_nonce], 'C4 C4'
    digest_bytes = OpenSSL::HMAC.digest 'sha1', secret, nonce_bytes
    digest = Packer.unpack(digest_bytes, 'C20')[0]

    @brickd.authenticate client_nonce, digest
  }
end

#connect(host, port) ⇒ Object

Creates a TCP/IP connection to the given host and port. The host and port can point to a Brick Daemon or to a WIFI/Ethernet Extension.

Devices can only be controlled when the connection was established successfully.

Blocks until the connection is established and throws an exception if there is no Brick Daemon or WIFI/Ethernet Extension listening at the given host and port.



632
633
634
635
636
637
638
639
640
641
642
643
# File 'lib/tinkerforge/ip_connection.rb', line 632

def connect(host, port)
  @socket_mutex.synchronize {
    if @socket != nil
      raise AlreadyConnectedException, "Already connected to #{@host}:#{@port}"
    end

    @host = host
    @port = port

    connect_unlocked false
  }
end

#create_packet_header(device, length, function_id) ⇒ Object

internal



816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
# File 'lib/tinkerforge/ip_connection.rb', line 816

def create_packet_header(device, length, function_id)
  uid = 0
  sequence_number = get_next_sequence_number
  response_expected = false
  r_bit = 0

  if device != nil
    uid = device.uid
    response_expected = device.get_response_expected function_id
  end

  if response_expected
    r_bit = 1
  end

  sequence_number_and_options = (sequence_number << 4) | (r_bit << 3)
  header = Packer.pack [uid, length, function_id, sequence_number_and_options, 0], 'L C C C C'

  [header, response_expected, sequence_number]
end

#disconnectObject

Disconnects the TCP/IP connection from the Brick Daemon or the WIFI/Ethernet Extension.



647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
# File 'lib/tinkerforge/ip_connection.rb', line 647

def disconnect
  callback = nil

  @socket_mutex.synchronize {
    @auto_reconnect_allowed = false

    if @auto_reconnect_pending
      # Abort pending auto reconnect
      @auto_reconnect_pending = false
    else
      if @socket == nil
        raise NotConnectedException, 'Not connected'
      end

      disconnect_unlocked
    end

    # Destroy callback thread
    callback = @callback
    @callback = nil
  }

  # Do this outside of socket_mutex to allow calling (dis-)connect from
  # the callbacks while blocking on the join call here
  callback.queue.push [QUEUE_KIND_META, [CALLBACK_DISCONNECTED,
                                         DISCONNECT_REASON_REQUEST, nil]]
  callback.queue.push [QUEUE_KIND_EXIT, nil]

  if Thread.current != callback.thread
    callback.thread.join
  end
end

#enumerateObject

Broadcasts an enumerate request. All devices will respond with an enumerate callback.



761
762
763
764
765
# File 'lib/tinkerforge/ip_connection.rb', line 761

def enumerate
  request, _, _ = create_packet_header nil, 8, FUNCTION_ENUMERATE

  send_request request
end

#get_auto_reconnectObject

Returns true if auto-reconnect is enabled, false otherwise.



742
743
744
# File 'lib/tinkerforge/ip_connection.rb', line 742

def get_auto_reconnect
  @auto_reconnect
end

#get_connection_stateObject

Can return the following states:

  • CONNECTION_STATE_DISCONNECTED: No connection is established.

  • CONNECTION_STATE_CONNECTED: A connection to the Brick Daemon or the WIFI/Ethernet Extension is established.

  • CONNECTION_STATE_PENDING: IP Connection is currently trying to connect.



716
717
718
719
720
721
722
723
724
# File 'lib/tinkerforge/ip_connection.rb', line 716

def get_connection_state
  if @socket != nil
    CONNECTION_STATE_CONNECTED
  elsif @auto_reconnect_pending
    CONNECTION_STATE_PENDING
  else
    CONNECTION_STATE_DISCONNECTED
  end
end

#get_next_sequence_numberObject

internal



807
808
809
810
811
812
813
# File 'lib/tinkerforge/ip_connection.rb', line 807

def get_next_sequence_number
  @sequence_number_mutex.synchronize {
    sequence_number = @next_sequence_number + 1
    @next_sequence_number = sequence_number % 15
    sequence_number
  }
end

#get_timeoutObject

Returns the timeout as set by set_timeout.



755
756
757
# File 'lib/tinkerforge/ip_connection.rb', line 755

def get_timeout
  @timeout
end

#register_callback(id, &block) ⇒ Object

Registers a callback with ID id to the block block.



788
789
790
791
# File 'lib/tinkerforge/ip_connection.rb', line 788

def register_callback(id, &block)
  callback = block
  @registered_callbacks[id] = callback
end

#send_request(request) ⇒ Object

internal



838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
# File 'lib/tinkerforge/ip_connection.rb', line 838

def send_request(request)
  @socket_mutex.synchronize {
    if @socket == nil
      raise NotConnectedException, 'Not connected'
    end

    begin
      @socket_send_mutex.synchronize {
        @socket.send request, 0
      }
    rescue IOError
      handle_disconnect_by_peer DISCONNECT_REASON_ERROR, @socket_id, true
      raise NotConnectedException, 'Not connected'
    rescue Errno::ECONNRESET
      handle_disconnect_by_peer DISCONNECT_REASON_SHUTDOWN, @socket_id, true
      raise NotConnectedException, 'Not connected'
    end

    @disconnect_probe_flag = false
  }
end

#set_auto_reconnect(auto_reconnect) ⇒ Object

Enables or disables auto-reconnect. If auto-reconnect is enabled, the IP Connection will try to reconnect to the previously given host and port, if the connection is lost.

Default value is true.



731
732
733
734
735
736
737
738
# File 'lib/tinkerforge/ip_connection.rb', line 731

def set_auto_reconnect(auto_reconnect)
  @auto_reconnect = auto_reconnect

  if not @auto_reconnect
    # Abort potentially pending auto reconnect
    @auto_reconnect_allowed = false
  end
end

#set_timeout(timeout) ⇒ Object

Sets the timeout in seconds for getters and for setters for which the response expected flag is activated.

Default timeout is 2.5.



750
751
752
# File 'lib/tinkerforge/ip_connection.rb', line 750

def set_timeout(timeout)
  @timeout = timeout
end

#unwaitObject

Unwaits the thread previously stopped by wait.

Wait and unwait act in the same way as “acquire” and “release” of a semaphore.



783
784
785
# File 'lib/tinkerforge/ip_connection.rb', line 783

def unwait
  @waiter_queue.push nil
end

#waitObject

Stops the current thread until unwait is called.

This is useful if you rely solely on callbacks for events, if you want to wait for a specific callback or if the IP Connection was created in a thread.

Wait and unwait act in the same way as “acquire” and “release” of a semaphore.



775
776
777
# File 'lib/tinkerforge/ip_connection.rb', line 775

def wait
  @waiter_queue.pop
end