Class: Tinkerforge::Device

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

Overview

internal

Direct Known Subclasses

BrickDC, BrickDaemon, BrickESP32, BrickESP32Ethernet, BrickHAT, BrickHATZero, BrickIMU, BrickIMUV2, BrickMaster, BrickRED, BrickServo, BrickSilentStepper, BrickStepper, BrickletAccelerometer, BrickletAccelerometerV2, BrickletAirQuality, BrickletAmbientLight, BrickletAmbientLightV2, BrickletAmbientLightV3, BrickletAnalogIn, BrickletAnalogInV2, BrickletAnalogInV3, BrickletAnalogOut, BrickletAnalogOutV2, BrickletAnalogOutV3, BrickletBarometer, BrickletBarometerV2, BrickletCAN, BrickletCANV2, BrickletCO2, BrickletCO2V2, BrickletColor, BrickletColorV2, BrickletCompass, BrickletCurrent12, BrickletCurrent25, BrickletDCV2, BrickletDMX, BrickletDistanceIR, BrickletDistanceIRV2, BrickletDistanceUS, BrickletDistanceUSV2, BrickletDualButton, BrickletDualButtonV2, BrickletDualRelay, BrickletDustDetector, BrickletEPaper296x128, BrickletEnergyMonitor, BrickletGPS, BrickletGPSV2, BrickletGPSV3, BrickletHallEffect, BrickletHallEffectV2, BrickletHumidity, BrickletHumidityV2, BrickletIMUV3, BrickletIO16, BrickletIO16V2, BrickletIO4, BrickletIO4V2, BrickletIndustrialAnalogOut, BrickletIndustrialAnalogOutV2, BrickletIndustrialCounter, BrickletIndustrialDigitalIn4, BrickletIndustrialDigitalIn4V2, BrickletIndustrialDigitalOut4, BrickletIndustrialDigitalOut4V2, BrickletIndustrialDual020mA, BrickletIndustrialDual020mAV2, BrickletIndustrialDualACIn, BrickletIndustrialDualACRelay, BrickletIndustrialDualAnalogIn, BrickletIndustrialDualAnalogInV2, BrickletIndustrialDualRelay, BrickletIndustrialPTC, BrickletIndustrialQuadRelay, BrickletIndustrialQuadRelayV2, BrickletIsolator, BrickletJoystick, BrickletJoystickV2, BrickletLCD128x64, BrickletLCD16x2, BrickletLCD20x4, BrickletLEDStrip, BrickletLEDStripV2, BrickletLaserRangeFinder, BrickletLaserRangeFinderV2, BrickletLine, BrickletLinearPoti, BrickletLinearPotiV2, BrickletLoadCell, BrickletLoadCellV2, BrickletMoisture, BrickletMotionDetector, BrickletMotionDetectorV2, BrickletMotorizedLinearPoti, BrickletMultiTouch, BrickletMultiTouchV2, BrickletNFC, BrickletNFCRFID, BrickletOLED128x64, BrickletOLED128x64V2, BrickletOLED64x48, BrickletOneWire, BrickletOutdoorWeather, BrickletPTC, BrickletPTCV2, BrickletParticulateMatter, BrickletPerformanceDC, BrickletPiezoBuzzer, BrickletPiezoSpeaker, BrickletPiezoSpeakerV2, BrickletRGBLED, BrickletRGBLEDButton, BrickletRGBLEDMatrix, BrickletRGBLEDV2, BrickletRS232, BrickletRS232V2, BrickletRS485, BrickletRealTimeClock, BrickletRealTimeClockV2, BrickletRemoteSwitch, BrickletRemoteSwitchV2, BrickletRotaryEncoder, BrickletRotaryEncoderV2, BrickletRotaryPoti, BrickletRotaryPotiV2, BrickletSegmentDisplay4x7, BrickletSegmentDisplay4x7V2, BrickletServoV2, BrickletSilentStepperV2, BrickletSolidStateRelay, BrickletSolidStateRelayV2, BrickletSoundIntensity, BrickletSoundPressureLevel, BrickletTemperature, BrickletTemperatureIR, BrickletTemperatureIRV2, BrickletTemperatureV2, BrickletThermalImaging, BrickletThermocouple, BrickletThermocoupleV2, BrickletTilt, BrickletUVLight, BrickletUVLightV2, BrickletVoltage, BrickletVoltageCurrent, BrickletVoltageCurrentV2, BrickletXMC1400Breakout

Constant Summary collapse

DEVICE_IDENTIFIER_CHECK_PENDING =
0
DEVICE_IDENTIFIER_CHECK_MATCH =
1
DEVICE_IDENTIFIER_CHECK_MISMATCH =
2
RESPONSE_EXPECTED_INVALID_FUNCTION_ID =
0
RESPONSE_EXPECTED_ALWAYS_TRUE =

getter

1
RESPONSE_EXPECTED_TRUE =

setter

2
RESPONSE_EXPECTED_FALSE =

setter, default

3

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uid, ipcon, device_identifier, device_display_name) ⇒ Device

internal



243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/tinkerforge/ip_connection.rb', line 243

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

Instance Attribute Details

#callback_formatsObject

Returns the value of attribute callback_formats.



238
239
240
# File 'lib/tinkerforge/ip_connection.rb', line 238

def callback_formats
  @callback_formats
end

#expected_response_function_idObject

Returns the value of attribute expected_response_function_id.



236
237
238
# File 'lib/tinkerforge/ip_connection.rb', line 236

def expected_response_function_id
  @expected_response_function_id
end

#expected_response_sequence_numberObject

Returns the value of attribute expected_response_sequence_number.



237
238
239
# File 'lib/tinkerforge/ip_connection.rb', line 237

def expected_response_sequence_number
  @expected_response_sequence_number
end

#high_level_callbacksObject

Returns the value of attribute high_level_callbacks.



239
240
241
# File 'lib/tinkerforge/ip_connection.rb', line 239

def high_level_callbacks
  @high_level_callbacks
end

#registered_callbacksObject

Returns the value of attribute registered_callbacks.



240
241
242
# File 'lib/tinkerforge/ip_connection.rb', line 240

def registered_callbacks
  @registered_callbacks
end

#replacedObject

Returns the value of attribute replaced.



234
235
236
# File 'lib/tinkerforge/ip_connection.rb', line 234

def replaced
  @replaced
end

#uidObject

Returns the value of attribute uid.



235
236
237
# File 'lib/tinkerforge/ip_connection.rb', line 235

def uid
  @uid
end

Instance Method Details

#check_validityObject

internal



484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/tinkerforge/ip_connection.rb', line 484

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) ⇒ Object

internal



467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
# File 'lib/tinkerforge/ip_connection.rb', line 467

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) ⇒ Object

internal



459
460
461
462
463
464
# File 'lib/tinkerforge/ip_connection.rb', line 459

def enqueue_response(response)
  @response_mutex.synchronize {
    @response_queue.push response
    @response_condition.signal
  }
end

#get_api_versionObject

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



298
299
300
# File 'lib/tinkerforge/ip_connection.rb', line 298

def get_api_version
  @api_version
end

#get_response_expected(function_id) ⇒ Object

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.



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
# File 'lib/tinkerforge/ip_connection.rb', line 317

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) ⇒ Object

internal



386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/tinkerforge/ip_connection.rb', line 386

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) ⇒ Object

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.



346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
# File 'lib/tinkerforge/ip_connection.rb', line 346

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) ⇒ Object

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



370
371
372
373
374
375
376
377
378
379
380
381
382
383
# File 'lib/tinkerforge/ip_connection.rb', line 370

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