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.



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
621
# File 'lib/tinkerforge/ip_connection.rb', line 583

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.



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

def timeout
  @timeout
end

Instance Method Details

#add_device(device) ⇒ Object

internal



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

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



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

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.



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

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



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

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.



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
679
# File 'lib/tinkerforge/ip_connection.rb', line 648

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.



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

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.



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

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.



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

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



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

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.



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

def get_timeout
  @timeout
end

#register_callback(id, &block) ⇒ Object

Registers a callback with ID id to the block block.



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

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

#send_request(request) ⇒ Object

internal



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

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.



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

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.



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

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.



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

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.



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

def wait
  @waiter_queue.pop
end