class Tinkerforge::Device

internal

Constants

DEVICE_IDENTIFIER_CHECK_MATCH
DEVICE_IDENTIFIER_CHECK_MISMATCH
DEVICE_IDENTIFIER_CHECK_PENDING
RESPONSE_EXPECTED_ALWAYS_TRUE
RESPONSE_EXPECTED_FALSE
RESPONSE_EXPECTED_INVALID_FUNCTION_ID
RESPONSE_EXPECTED_TRUE

Attributes

callback_formats[RW]
expected_response_function_id[RW]
expected_response_sequence_number[RW]
high_level_callbacks[RW]
registered_callbacks[RW]
replaced[RW]
uid[RW]

Public Class Methods

new(uid, ipcon, device_identifier, device_display_name) click to toggle source

internal

# File lib/tinkerforge/ip_connection.rb, line 242
def initialize(uid, ipcon, device_identifier, device_display_name)
  @replaced = false
  @uid = Base58.decode uid
  @uid_string = uid

  if @uid > (1 << 64) - 1
    raise ArgumentError, "UID '#{uid}' is too big"
  end

  if @uid > (1 << 32) - 1
    # convert from 64bit to 32bit
    value1 = @uid & 0xFFFFFFFF
    value2 = (@uid >> 32) & 0xFFFFFFFF

    @uid  = (value1 & 0x00000FFF)
    @uid |= (value1 & 0x0F000000) >> 12
    @uid |= (value2 & 0x0000003F) << 16
    @uid |= (value2 & 0x000F0000) << 6
    @uid |= (value2 & 0x3F000000) << 2
  end

  if @uid == 0
    raise ArgumentError, "UID '#{uid}' is empty or maps to zero"
  end

  @api_version = [0, 0, 0]

  @ipcon = ipcon

  @device_identifier = device_identifier
  @device_display_name = device_display_name
  @device_identifier_lock = Mutex.new
  @device_identifier_check = DEVICE_IDENTIFIER_CHECK_PENDING # protected by device_identifier_lock
  @wrong_device_display_name = '?' # protected by device_identifier_lock

  @request_mutex = Mutex.new

  @response_expected = Array.new(256, RESPONSE_EXPECTED_INVALID_FUNCTION_ID)

  @expected_response_function_id = 0
  @expected_response_sequence_number = 0

  @response_mutex = Mutex.new
  @response_condition = ConditionVariable.new
  @response_queue = Queue.new

  @stream_mutex = Mutex.new

  @callback_formats = {}
  @high_level_callbacks = {}
  @registered_callbacks = {}
end

Public Instance Methods

check_validity() click to toggle source

internal

# File lib/tinkerforge/ip_connection.rb, line 483
def check_validity()
  if @replaced
    raise DeviceReplacedException, 'Device has been replaced'
  end

  if @device_identifier_check == DEVICE_IDENTIFIER_CHECK_MATCH
    return
  end

  @device_identifier_lock.synchronize {
    if @device_identifier_check == DEVICE_IDENTIFIER_CHECK_PENDING
      device_identifier = send_request(255, [], '', 33, 'Z8 Z8 k C3 C3 S')[5] # <device>.get_identity

      if device_identifier == @device_identifier
        @device_identifier_check = DEVICE_IDENTIFIER_CHECK_MATCH
      else
        @device_identifier_check = DEVICE_IDENTIFIER_CHECK_MISMATCH
        @wrong_device_display_name = get_device_display_name device_identifier
      end
    end

    if @device_identifier_check == DEVICE_IDENTIFIER_CHECK_MISMATCH
      raise WrongDeviceTypeException, "UID #{@uid_string} belongs to a #{@wrong_device_display_name} instead of the expected #{@device_display_name}"
    end
  }
end
dequeue_response(message) click to toggle source

internal

# File lib/tinkerforge/ip_connection.rb, line 466
def dequeue_response(message)
  response = nil

  @response_mutex.synchronize {
    @response_condition.wait @response_mutex, @ipcon.timeout

    if @response_queue.empty?
      raise TimeoutException, message
    end

    response = @response_queue.pop
  }

  response
end
enqueue_response(response) click to toggle source

internal

# File lib/tinkerforge/ip_connection.rb, line 458
def enqueue_response(response)
  @response_mutex.synchronize {
    @response_queue.push response
    @response_condition.signal
  }
end
get_api_version() click to toggle source

Returns the API version (major, minor, revision) of the bindings for this device.

# File lib/tinkerforge/ip_connection.rb, line 297
def get_api_version
  @api_version
end
get_response_expected(function_id) click to toggle source

Returns the response expected flag for the function specified by the function_id parameter. It is true if the function is expected to send a response, false otherwise.

For getter functions this is enabled by default and cannot be disabled, because those functions will always send a response. For callback configuration functions it is enabled by default too, but can be disabled via the set_response_expected function. For setter functions it is disabled by default and can be enabled.

Enabling the response expected flag for a setter function allows to detect timeouts and other error conditions calls of this setter as well. The device will then send a response for this purpose. If this flag is disabled for a setter function then no response is sent and errors are silently ignored, because they cannot be detected.

# File lib/tinkerforge/ip_connection.rb, line 316
def get_response_expected(function_id)
  if function_id < 0 or function_id > 255
    raise ArgumentError, "Function ID #{function_id} out of range"
  end

  flag = @response_expected[function_id]

  if flag == RESPONSE_EXPECTED_INVALID_FUNCTION_ID
    raise ArgumentError, "Invalid function ID #{function_id}"
  end

  if flag == RESPONSE_EXPECTED_ALWAYS_TRUE or \
     flag == RESPONSE_EXPECTED_TRUE
    true
  else
    false
  end
end
send_request(function_id, request_data, request_format, expected_response_length, response_format) click to toggle source

internal

# File lib/tinkerforge/ip_connection.rb, line 385
def send_request(function_id, request_data, request_format,
                 expected_response_length, response_format)
  response = nil

  if request_data.length > 0
    payload = Packer.pack request_data, request_format
  else
    payload = ''
  end

  header, response_expected, sequence_number = \
    @ipcon.create_packet_header self, 8 + payload.length, function_id
  request = header + payload

  if response_expected
    packet = nil

    @request_mutex.synchronize {
      @expected_response_function_id = function_id
      @expected_response_sequence_number = sequence_number

      begin
        @ipcon.send_request request

        while true
          packet = dequeue_response "Did not receive response in time for function ID #{function_id}"

          if function_id == Packer.get_function_id_from_data(packet) and \
             sequence_number == Packer.get_sequence_number_from_data(packet)
            # ignore old responses that arrived after the timeout expired, but before setting
            # expected_response_function_id and expected_response_sequence_number back to None
            break
          end
        end
      ensure
        @expected_response_function_id = 0
        @expected_response_sequence_number = 0
      end
    }

    error_code = Packer.get_error_code_from_data packet

    if error_code == 0
      if expected_response_length == 0
        expected_response_length = 8 # setter with response-expected enabled
      end

      if packet.length != expected_response_length
        raise WrongResponseLengthException, "Expected response of #{expected_response_length} byte for function ID #{function_id}, got #{packet.length} byte instead"
      end
    elsif error_code == 1
      raise InvalidParameterException, "Got invalid parameter for function ID #{function_id}"
    elsif error_code == 2
      raise NotSupportedException, "Function ID #{function_id} is not supported"
    else
      raise UnknownErrorCodeException, "Function ID #{function_id} returned an unknown error"
    end

    if response_format.length > 0
      response = Packer.unpack packet[8..-1], response_format

      if response.length == 1
        response = response[0]
      end
    end
  else
    @ipcon.send_request request
  end

  response
end
set_response_expected(function_id, response_expected) click to toggle source

Changes the response expected flag of the function specified by the function_id parameter. This flag can only be changed for setter (default value: false) and callback configuration functions (default value: true). For getter functions it is always enabled.

Enabling the response expected flag for a setter function allows to detect timeouts and other error conditions calls of this setter as well. The device will then send a response for this purpose. If this flag is disabled for a setter function then no response is sent and errors are silently ignored, because they cannot be detected.

# File lib/tinkerforge/ip_connection.rb, line 345
def set_response_expected(function_id, response_expected)
  if function_id < 0 or function_id > 255
    raise ArgumentError, "Function ID #{function_id} out of range"
  end

  flag = @response_expected[function_id]

  if flag == RESPONSE_EXPECTED_INVALID_FUNCTION_ID
    raise ArgumentError, "Invalid function ID #{function_id}"
  end

  if flag == RESPONSE_EXPECTED_ALWAYS_TRUE
    raise ArgumentError, "Response Expected flag cannot be changed for function ID #{function_id}"
  end

  if response_expected
    @response_expected[function_id] = RESPONSE_EXPECTED_TRUE
  else
    @response_expected[function_id] = RESPONSE_EXPECTED_FALSE
  end
end
set_response_expected_all(response_expected) click to toggle source

Changes the response expected flag for all setter and callback configuration functions of this device at once.

# File lib/tinkerforge/ip_connection.rb, line 369
def set_response_expected_all(response_expected)
  if response_expected
    flag = RESPONSE_EXPECTED_TRUE
  else
    flag = RESPONSE_EXPECTED_FALSE
  end

  for function_id in 0..255
    if @response_expected[function_id] == RESPONSE_EXPECTED_TRUE or \
       @response_expected[function_id] == RESPONSE_EXPECTED_FALSE
      @response_expected[function_id] = flag
    end
  end
end