# $Id: 51_I2C_TSL2561.pm 15723 2017-12-29 21:08:17Z jensb $
=head1
  51_I2C_TSL2561.pm

=head1 SYNOPSIS
  Modul for FHEM for reading a TSL2561 ambient light sensor via I2C
  connected to the Raspberry Pi.

  contributed by Kai Stuke 2014

=head1 DESCRIPTION
  51_I2C_TSL2561.pm reads the illumination of the the ambient light sensor TSL2561
  via i2c bus connected to the Raspberry Pi.

  This module needs IODev FHEM modules or the HiPi perl modules
  IODev see: <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a> or <a href="#NetzerI2C">NetzerI2C</a>
  HiPi see: http://raspberrypi.znix.com/hipidocs/
  
  For a simple automated installation of the HiPi perl modules:<br>
  wget http://raspberry.znix.com/hipifiles/hipi-install
  perl hipi-install

  HiPi Example:
  define Luminosity I2C_TSL2561 /dev/i2c-0 0x39
  attr Luminosity poll_interval 5
  
  IODev Example:
  define Luminosity I2C_TSL2561 0x39
  attr Luminosity IODev I2CModule
  attr Luminosity poll_interval 5
  
=head1 HiPi CAVEATS
  Make sure that the user fhem.pl is running as has read/write access to the i2c device file
  (e.g. /dev/i2c-0).
  This can be achieved by adding the user to the group i2c (sudo usermod -G i2c -a fhem).
  
  The pinout of the i2c-bus pins differs between revision 1 and revision 2 Raspberry Pi Model B boards.
  On revision 1, only bus 0 is accessible, on revision 2 bus 1 is connected to the standard pin header instead.
  
  There is a problem with newer kernel versions (>3.9?) when both i2c and 1-wire are used.
  If i2cdetect shows devices on all addresses you are affected by this bug.
  To avoid this the kernel modules must be loaded in a specific order.
  
  Try these settings in /etc/modules
    w1_therm
    w1-gpio
    i2c-dev
    i2c-bcm2708
    snd-bcm2835

  and in /etc/modprobe.d/raspi-blacklist.conf
    blacklist spi-bcm2708
    blacklist i2c-bcm2708
  
=head1 CHANGES
  18.03.2015 jensb
    IODev support added as alternative to HiPi
    I2C error detection for IODev mode added 
    hotplug support (reinit TLS2561 afer each I2C error)
    luminosity calculation alternative with float arithmetic for improved precision, especially with ir ratio below 50% and below 10 lux (new default, can be disabled)
    scale readings 'broadband' and 'ir' with actual gain and integration time ratios to get values that can be directly used (e.g. for plots) 
    'luminosity', 'broadband' and 'ir' readings are not updated if state is 'Saturated' or 'I2C Error' so that timestamp of readings show last valid time
    attribute and reading restoration added in I2C_TSL2561_Define
    autoGain attribute inversion fix
    'Saturation' state freeze fix
  22.03.2015 jensb
    round luminosity to 3 significant digits or max. 1 fractional digit when float arithmetics are enabled
  11.04.2015 jensb
    unblock FHEM while waiting for end of integration time - this makes using long integration times preferable
    changing attribute 'gain' or 'integrationTime' no longer powers up the TSL2561
    attribute 'disable' added
    attribute 'autoIntegrationTime' added (decrease when measurement gets saturated, increase when value gets low)
    I2C auto address mode added for IODev to compensate floating address selection
    improved I2C read error handling for RPII2C IODev
  16.04.2015 jensb
    make scaling of readings 'broadband' and 'ir' depended on new attribute 'normalizeRawValues'
  18.04.2015 jensb
    new readings 'gain' and 'integrationTime'
  20.04.2015 jensb
    update reading 'state' in bulk along with luminosity when toggling between 'Initialized' and 'Saturated'
  17.11.2015 jensb
    register InitFn for IODev post initialization and do not init IOdev in Define if FHEM is not initialized
  19.12.2015 jensb
    constants renamed with module specific prefix
    state machines modified to become I2C read driven for Firmata compatibility (non-blocking I2C I/O)
    changing gain/integrationTime attributes will no longer write to device but will be used at next poll
  26.12.2015 kaihs
    CalculateLux float arithmetics formula fix
  02.01.2017 jensb
    inverted check of I2C IO result (not "Ok" instead of "error")
    
    
=head1 TODO
  manual integration time (optional)
  
=head1 CREDITS
  Based on the module 51_I2C_BMP180.pm by Dirk Hoffmann and Klaus Wittstock
  TSL2651 specific code based on the python module by schwabbel as posted on 
  http://forums.adafruit.com/viewtopic.php?f=8&t=34922&start=75
  which in turn is based on the code by Adafruit
  https://github.com/adafruit/Adafruit_TSL2561
  Lux calculation algorithm is based on the code in the TSL2561 datasheet
  http://www.adafruit.com/datasheets/TSL2561.pdf
  newer version
  http://www.ams.com/eng/content/download/250094/975485/142937
  
=head1 AUTHOR - Kai Stuke
  kaihs@FHEM_Forum (forum.fhem.de)
  modified by Jens Beyer jensb@FHEM_Forum (forum.fhem.de)

=cut

package main;

use strict;
use warnings;

use Time::HiRes qw(tv_interval);
use Scalar::Util qw(looks_like_number);

use constant {
  # I2C address options
  TSL2561_ADDR_LOW          => '0x29',
  TSL2561_ADDR_FLOAT        => '0x39',    # Default address (pin left floating)
  TSL2561_ADDR_HIGH         => '0x49',
  TSL2561_ADDR_AUTO         => 'AUTO',

  # I2C registers
  TSL2561_REGISTER_CONTROL          => 0x00,
  TSL2561_REGISTER_TIMING           => 0x01,
  TSL2561_REGISTER_THRESHHOLDL_LOW  => 0x02,
  TSL2561_REGISTER_THRESHHOLDL_HIGH => 0x03,
  TSL2561_REGISTER_THRESHHOLDH_LOW  => 0x04,
  TSL2561_REGISTER_THRESHHOLDH_HIGH => 0x05,
  TSL2561_REGISTER_INTERRUPT        => 0x06,
  TSL2561_REGISTER_CRC              => 0x08,
  TSL2561_REGISTER_ID               => 0x0A,
  TSL2561_REGISTER_CHAN0_LOW        => 0x0C,
  TSL2561_REGISTER_CHAN0_HIGH       => 0x0D,
  TSL2561_REGISTER_CHAN1_LOW        => 0x0E,
  TSL2561_REGISTER_CHAN1_HIGH       => 0x0F,
   
  # I2C register values
  TSL2561_COMMAND_BIT                  => 0x80,   # Must be 1,
  TSL2561_CLEAR_BIT                    => 0x40,   # Clears any pending interrupt (write 1 to clear)
  TSL2561_WORD_BIT                     => 0x20,   # 1 = read/write word (rather than byte)
  TSL2561_BLOCK_BIT                    => 0x10,   # 1 = using block read/write
  TSL2561_CONTROL_POWERON              => 0x03,
  TSL2561_CONTROL_POWEROFF             => 0x00,
  TSL2561_PACKAGE_CS                   => 0b0001,
  TSL2561_PACKAGE_T_FN_CL              => 0b0101,
  TSL2561_GAIN_1X                      => 0x00,   # No gain
  TSL2561_GAIN_16X                     => 0x10,   # 16x gain
  TSL2561_INTEGRATIONTIME_13MS         => 0x00,   # 13.7ms
  TSL2561_INTEGRATIONTIME_101MS        => 0x01,   # 101ms
  TSL2561_INTEGRATIONTIME_402MS        => 0x02,   # 402ms
  TSL2561_INTEGRATIONTIME_MANUAL_STOP  => 0x03,   # stop manual integration cycle (not implemented)
  TSL2561_INTEGRATIONTIME_MANUAL_START => 0x0b,   # start manual integration cycle (not implemented)

  # Auto-gain thresholds
  TSL2561_AGC_THI_13MS              => 4850,    # Max value at Ti 13.7ms = 5047,
  TSL2561_AGC_TLO_13MS              => 100,
  TSL2561_AGC_THI_101MS             => 36000,   # Max value at Ti 101ms = 37177,
  TSL2561_AGC_TLO_101MS             => 200,
  TSL2561_AGC_THI_402MS             => 63000,   # Max value at Ti 402ms = 65535,
  TSL2561_AGC_TLO_402MS             => 500,

  # Saturation clipping thresholds
  TSL2561_CLIPPING_13MS             => 4946,    # 2% below 13.7 ms max. of 5047
  TSL2561_CLIPPING_101MS            => 36433,   # 2% below 101 ms max. of 37177
  TSL2561_CLIPPING_402MS            => 64224,   # 2% below 402 ms max. of 65535
  
  # Lux calculations differ slightly for CS package
  TSL2561_LUX_LUXSCALE      =>14,      # Scale by 2^14,
  TSL2561_LUX_RATIOSCALE    =>9,       # Scale ratio by 2^9,
  TSL2561_LUX_CHSCALE       =>10,      # Scale channel values by 2^10,
  TSL2561_LUX_CHSCALE_TINT0 =>0x7517,  # 322/11 * 2^TSL2561_LUX_CHSCALE
  TSL2561_LUX_CHSCALE_TINT1 =>0x0FE7,  # 322/81 * 2^TSL2561_LUX_CHSCALE

  # T, FN and CL package values
  TSL2561_LUX_K1T           =>0x0040,  # 0.125 * 2^RATIO_SCALE
  TSL2561_LUX_B1T           =>0x01f2,  # 0.0304 * 2^LUX_SCALE
  TSL2561_LUX_M1T           =>0x01be,  # 0.0272, * 2^LUX_SCALE
  TSL2561_LUX_K2T           =>0x0080,  # 0.250 * 2^RATIO_SCALE
  TSL2561_LUX_B2T           =>0x0214,  # 0.0325 * 2^LUX_SCALE
  TSL2561_LUX_M2T           =>0x02d1,  # 0.0440 * 2^LUX_SCALE
  TSL2561_LUX_K3T           =>0x00c0,  # 0.375 * 2^RATIO_SCALE
  TSL2561_LUX_B3T           =>0x023f,  # 0.0351, * 2^LUX_SCALE
  TSL2561_LUX_M3T           =>0x037b,  # 0.0544, * 2^LUX_SCALE
  TSL2561_LUX_K4T           =>0x0100,  # 0.50 * 2^RATIO_SCALE
  TSL2561_LUX_B4T           =>0x0270,  # 0.0381 * 2^LUX_SCALE
  TSL2561_LUX_M4T           =>0x03fe,  # 0.0624, * 2^LUX_SCALE
  TSL2561_LUX_K5T           =>0x0138,  # 0.61 * 2^RATIO_SCALE
  TSL2561_LUX_B5T           =>0x016f,  # 0.0224, * 2^LUX_SCALE
  TSL2561_LUX_M5T           =>0x01fc,  # 0.0310, * 2^LUX_SCALE
  TSL2561_LUX_K6T           =>0x019a,  # 0.80, * 2^RATIO_SCALE
  TSL2561_LUX_B6T           =>0x00d2,  # 0.0128 * 2^LUX_SCALE
  TSL2561_LUX_M6T           =>0x00fb,  # 0.0153, * 2^LUX_SCALE
  TSL2561_LUX_K7T           =>0x029a,  # 1.3, * 2^RATIO_SCALE
  TSL2561_LUX_B7T           =>0x0018,  # 0.00146 * 2^LUX_SCALE
  TSL2561_LUX_M7T           =>0x0012,  # 0.00112 * 2^LUX_SCALE
  TSL2561_LUX_K8T           =>0x029a,  # 1.3, * 2^RATIO_SCALE
  TSL2561_LUX_B8T           =>0x0000,  # 0.000 * 2^LUX_SCALE
  TSL2561_LUX_M8T           =>0x0000,  # 0.000 * 2^LUX_SCALE
   
  # CS package values
  TSL2561_LUX_K1C           =>0x0043,  # 0.130 * 2^RATIO_SCALE
  TSL2561_LUX_B1C           =>0x0204,  # 0.0315 * 2^LUX_SCALE
  TSL2561_LUX_M1C           =>0x01ad,  # 0.0262, * 2^LUX_SCALE
  TSL2561_LUX_K2C           =>0x0085,  # 0.260 * 2^RATIO_SCALE
  TSL2561_LUX_B2C           =>0x0228,  # 0.0337 * 2^LUX_SCALE
  TSL2561_LUX_M2C           =>0x02c1,  # 0.0430 * 2^LUX_SCALE
  TSL2561_LUX_K3C           =>0x00c8,  # 0.390 * 2^RATIO_SCALE
  TSL2561_LUX_B3C           =>0x0253,  # 0.0363 * 2^LUX_SCALE
  TSL2561_LUX_M3C           =>0x0363,  # 0.0529 * 2^LUX_SCALE
  TSL2561_LUX_K4C           =>0x010a,  # 0.520, * 2^RATIO_SCALE
  TSL2561_LUX_B4C           =>0x0282,  # 0.0392 * 2^LUX_SCALE
  TSL2561_LUX_M4C           =>0x03df,  # 0.0605, * 2^LUX_SCALE
  TSL2561_LUX_K5C           =>0x014d,  # 0.65, * 2^RATIO_SCALE
  TSL2561_LUX_B5C           =>0x0177,  # 0.0229 * 2^LUX_SCALE
  TSL2561_LUX_M5C           =>0x01dd,  # 0.0291, * 2^LUX_SCALE
  TSL2561_LUX_K6C           =>0x019a,  # 0.80, * 2^RATIO_SCALE
  TSL2561_LUX_B6C           =>0x0101,  # 0.0157 * 2^LUX_SCALE
  TSL2561_LUX_M6C           =>0x0127,  # 0.0180 * 2^LUX_SCALE
  TSL2561_LUX_K7C           =>0x029a,  # 1.3, * 2^RATIO_SCALE
  TSL2561_LUX_B7C           =>0x0037,  # 0.00338 * 2^LUX_SCALE
  TSL2561_LUX_M7C           =>0x002b,  # 0.00260, * 2^LUX_SCALE
  TSL2561_LUX_K8C           =>0x029a,  # 1.3, * 2^RATIO_SCALE
  TSL2561_LUX_B8C           =>0x0000,  # 0.000 * 2^LUX_SCALE
  TSL2561_LUX_M8C           =>0x0000,  # 0.000 * 2^LUX_SCALE

  TSL2561_STATE_UNDEFINED   => 'Undefined',
  TSL2561_STATE_DEFINED     => 'Defined',
  TSL2561_STATE_INITIALIZED => 'Initialized',
  TSL2561_STATE_SATURATED   => 'Saturated',
  TSL2561_STATE_I2C_ERROR   => 'I2C Error',
  TSL2561_STATE_DISABLED    => 'Disabled',
  
  TSL2561_ACQUI_STATE_IDLE              => 0,
  TSL2561_ACQUI_STATE_SETUP             => 1,
  TSL2561_ACQUI_STATE_ENABLE_REQUESTED  => 2,
  TSL2561_ACQUI_STATE_ENABLED           => 3,
  TSL2561_ACQUI_STATE_DATA_AVAILABLE    => 4,
  TSL2561_ACQUI_STATE_DATA_REQUESTED    => 5,
  TSL2561_ACQUI_STATE_DATA_CH0_RECEIVED => 6,
  TSL2561_ACQUI_STATE_DATA_CH1_RECEIVED => 7,
  TSL2561_ACQUI_STATE_ERROR             => 8,

  TSL2561_CALC_STATE_IDLE           => 0,
  TSL2561_CALC_STATE_DATA_REQUESTED => 1,
  TSL2561_CALC_STATE_DATA_RECEIVED  => 2,
  TSL2561_CALC_STATE_ERROR          => 3,
  TSL2561_CALC_STATE_COMPLETED      => 4,
  
  TSL2561_MAX_CONSECUTIVE_OPERATIONS => 20,
};

##################################################
# Forward declarations
#
sub I2C_TSL2561_Initialize($);
sub I2C_TSL2561_Define($$);
sub I2C_TSL2561_Attr(@);
sub I2C_TSL2561_Poll($);
sub I2C_TSL2561_Set($@);
sub I2C_TSL2561_Get($);
sub I2C_TSL2561_Undef($$);
sub I2C_TSL2561_Enable($);
sub I2C_TSL2561_Disable($);
sub I2C_TSL2561_GetData($);
sub I2C_TSL2561_SetTimingRegister($);
sub I2C_TSL2561_SetIntegrationTime($$);
sub I2C_TSL2561_SetGain($$);
sub I2C_TSL2561_GetLuminosity($);
sub I2C_TSL2561_CalculateLux($);

my $libcheck_hasHiPi = 1;

my %sets = (
  "update" => "",
);

my %validAdresses = (
  "0x29" => TSL2561_ADDR_LOW,
  "0x39" => TSL2561_ADDR_FLOAT, 
  "0x49" => TSL2561_ADDR_HIGH,
  "auto" => TSL2561_ADDR_AUTO,
);

my %validPackages = (
  "CS" => TSL2561_PACKAGE_CS,
  "T"  => TSL2561_PACKAGE_T_FN_CL,
  "FN" => TSL2561_PACKAGE_T_FN_CL,
  "CL" => TSL2561_PACKAGE_T_FN_CL,
);

my @fsmSubs = (\&I2C_TSL2561_StartMeasurement, \&I2C_TSL2561_GetMeasurement);

=head2 I2C_TSL2561_Initialize
  Title:    I2C_TSL2561_Initialize
  Function:  Implements the initialize function.
  Returns:  -
  Args:    named arguments:
        -argument1 => hash

=cut

sub I2C_TSL2561_Initialize($) {
  my ($hash) = @_;
    
  eval "use HiPi::Device::I2C;";
  $libcheck_hasHiPi = 0 if($@);    

  $hash->{DefFn}    = 'I2C_TSL2561_Define';
  $hash->{InitFn}   = 'I2C_TSL2561_Init';
  $hash->{AttrFn}   = 'I2C_TSL2561_Attr';
  $hash->{SetFn}    = 'I2C_TSL2561_Set';
  $hash->{UndefFn}  = 'I2C_TSL2561_Undef';
  $hash->{I2CRecFn} = 'I2C_TSL2561_I2CRec';

  $hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 ' .
                      'loglevel:0,1,2,3,4,5,6 poll_interval:1,2,5,10,20,30 ' .
                      'gain:1,16 integrationTime:13,101,402 ' . 
                      'autoGain:0,1 autoIntegrationTime:0,1 normalizeRawValues:0,1 ' .
                      'floatArithmetics:0,1 disable:0,1 ' . $readingFnAttributes;
  $hash->{AttrList} .= " useHiPiLib:0,1 " if ($libcheck_hasHiPi);
}

sub I2C_TSL2561_Define($$) {
  my ($hash, $def) = @_;
  my @a = split('[ \t][ \t]*', $def);
  my $name = $a[0];
  my $device;
  
  readingsSingleUpdate($hash, 'state', TSL2561_STATE_UNDEFINED, 1);

  Log3 $name, 1, "I2C_TSL2561_Define start: " . @a . "/" . join(' ', @a);
  
  $hash->{HiPi_exists} = $libcheck_hasHiPi if ($libcheck_hasHiPi);    
  $hash->{HiPi_used} = 0;
  
  my $address = undef;
  my $msg = '';
  if (@a < 3) {
    $msg = 'wrong syntax: define <name> I2C_TSL2561 [devicename] address';
  } elsif (@a == 3) {
    $address = lc($a[2]);
  } else {
    $device = $a[2];
    $address = lc($a[3]);
    if ($libcheck_hasHiPi) {
      $hash->{HiPi_used} = 1;
      delete $validAdresses{'auto'};
    } else {
      $msg = '$name error: HiPi library not installed';
    }                
  }
  if ($msg) {
    Log3 ($hash, 1, $msg);
    return $msg;
  }    
  
  $address = $validAdresses{$address};
  if (!defined($address)) {
    $msg = "Wrong address, must be one of " . join(' ', keys %validAdresses);
    Log3 ($hash, 1, $msg);
    return $msg;
  }
  
  if ($address eq TSL2561_ADDR_AUTO) {
    # start with lowest address in auto mode
    $hash->{autoAddress} = 1;
    $address = TSL2561_ADDR_LOW;
  } else {
    $hash->{autoAddress} = 0;
  }
  if ($hash->{HiPi_used}) {
    $hash->{autoAddress} = 0;
  }
  $hash->{I2C_Address} = hex($address);
  
  # create default attributes
  if (AttrVal($name, 'poll_interval', '?') eq '?') {  
    $msg = CommandAttr(undef, $name . ' poll_interval 5');
    if ($msg) {
      Log (1, $msg);
      return $msg;
    }
  }
  if (AttrVal($name, 'floatArithmetics', '?') eq '?') {  
    $msg = CommandAttr(undef, $name . ' floatArithmetics 1');
    if ($msg) {
      Log (1, $msg);
      return $msg;
    }
  }
  
  # preset some internal readings
  if (!defined($hash->{tsl2561IntegrationTime})) {
    my $attrVal = AttrVal($name, 'integrationTime', 13);  
    $hash->{tsl2561IntegrationTime} = $attrVal == 402? TSL2561_INTEGRATIONTIME_402MS : $attrVal == 101? TSL2561_INTEGRATIONTIME_101MS : TSL2561_INTEGRATIONTIME_13MS;
  }
  if (!defined($hash->{tsl2561Gain})) {
    my $attrVal = AttrVal($name, 'gain', 1);
    $hash->{tsl2561Gain} = $attrVal == 16? TSL2561_GAIN_16X : TSL2561_GAIN_1X;
  }
  if (!defined($hash->{acquiState})) {
    $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  }
  if (!defined($hash->{calcState})) {
    $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
  }
  if (!defined($hash->{operationCounter})) {
    $hash->{operationCounter} = 0;
  }
  if (!defined($hash->{blockingIO})) {
    $hash->{blockingIO} = 0;
  }

  readingsSingleUpdate($hash, 'state', TSL2561_STATE_DEFINED, 1);
  
  if ($main::init_done || $hash->{HiPi_used}) {
    eval { 
      I2C_TSL2561_Init($hash, [ $device ]); 
    };
    Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_TSL2561_Catch($@)) if $@;;
  }

  Log3 $name, 5, "I2C_TSL2561_Define end";
  return undef;
}
  
sub I2C_TSL2561_Init($$) {
  my ($hash, $dev) = @_;
  my $name = $hash->{NAME};
  
  if ($hash->{HiPi_used}) {
    # check for existing i2c device  
    my $i2cModulesLoaded = 0;
    $i2cModulesLoaded = 1 if -e $dev;
    if ($i2cModulesLoaded) {
      if (-r $dev && -w $dev) {
        $hash->{devTSL2561} = HiPi::Device::I2C->new( 
            devicename  => $dev,
            address    => $hash->{I2C_Address},
            busmode    => 'i2c',
        );
        Log3 $name, 3, "I2C_TSL2561_Define device created";
      } else {
        my @groups = split '\s', $(;
        return "$name :Error! $dev isn't readable/writable by user " . getpwuid( $< ) . " or group(s) " . 
          getgrgid($_) . " " foreach(@groups); 
      }
    } else {
      return $name . ': Error! I2C device not found: ' . $dev . '. Please check that these kernelmodules are loaded: i2c_bcm2708, i2c_dev';
    }
  } else {
    AssignIoPort($hash);
  }

  # clear package identification to force device reinitialization (device may have been powered off)
  $hash->{tsl2561Package} = undef;
  
  # start new measurement cycle
  RemoveInternalTimer($hash);
  InternalTimer(gettimeofday() + 10, 'I2C_TSL2561_Poll', $hash, 0);
  
  return undef;
}

sub I2C_TSL2561_Catch($) {
  my $exception = shift;
  if ($exception) {
    $exception =~ /^(.*)( at.*FHEM.*)$/;
    return $1;
  }
  return undef;
}

=head2 I2C_TSL2561_Attr
  Title:    I2C_TSL2561_Attr
  Function:  Implements AttrFn function.
  Returns:  string|undef
  Args:    named arguments:
        -argument1 => array

=cut

sub I2C_TSL2561_Attr (@) {
  my ($cmd, $name, $attr, $val) =  @_;
  my $hash = $defs{$name};
  my $msg = '';

  Log3 $name, 5, "I2C_TSL2561_Attr: start cmd=$cmd attr=$attr"; 
  if ($attr eq 'poll_interval') {
    my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;    
    if ($val > 0) {
      # start new measurement cycle
      RemoveInternalTimer($hash);
      InternalTimer(gettimeofday() + 1, 'I2C_TSL2561_Poll', $hash, 0);
    } elsif (defined($val)) {
      $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0';
    }
  } elsif ($attr eq 'gain') {
    my $gain = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
    
    Log3 $name, 5, "I2C_TSL2561_Attr: attr gain is " . $gain;
    if ($gain == 1) {
      I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X);
    } elsif ($gain == 16) {
      I2C_TSL2561_SetGain($hash, TSL2561_GAIN_16X);
    } elsif (defined($val)) {
      $msg = 'Wrong gain defined. must be 1 or 16';
    }
  } elsif ($attr eq 'integrationTime') {
    my $time = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
    
    if ($time == 13) {
      I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_13MS);
    } elsif ($time == 101) {
      I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_101MS);
    } elsif ($time == 402) {
      I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_402MS);
    } elsif (defined($val)) {
      $msg = 'Wrong integrationTime defined. must be 13 or 101 or 402';
    }
  } elsif ($attr eq 'autoGain') {
    my $autoGain = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
    $hash->{timingModified} = 1;    
  } elsif ($attr eq 'autoIntegrationTime') {
    my $autoIntegrationTime = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
    $hash->{timingModified} = 1;    
  } elsif ($attr eq 'normalizeRawValues') {
    my $normalizeRawValues = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  } elsif ($attr eq 'floatArithmetics') {
    my $floatArithmetics = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
    } elsif ($attr eq "disable") {
      my $disable = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
  }  

  return ($msg) ? $msg : undef;
}

=head2 I2C_TSL2561_Poll
  Title:    I2C_TSL2561_Poll
  Function: Start polling the sensor at interval defined in attribute
  Returns:  -
  Args:     named arguments:
            - argument1 => hash
=cut

sub I2C_TSL2561_Poll($) {
  my ($hash) =  @_;
  my $name = $hash->{NAME};
  RemoveInternalTimer($hash);
  
  Log3 $name, 5, "I2C_TSL2561_Poll: start";
  
  my $pollDelay = 60*AttrVal($hash->{NAME}, 'poll_interval', 0); # seconds polling
  if (!AttrVal($hash->{NAME}, "disable", 0)) {
    # Request new samples from TSL2561 and calculate luminosity
    my $lux = I2C_TSL2561_GetLuminosity($hash);
    if ($hash->{calcState} == TSL2561_CALC_STATE_DATA_REQUESTED) {
      # Measurement in progress
      if ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLED) {
        $pollDelay = I2C_TSL2561_GetIntegrationTime($hash) + 0.003; # seconds measurement time
        if (!$hash->{blockingIO}) {
          $pollDelay += 0.200; # extra time for async transport jitter compensation
        }
      } else {
        $pollDelay = 0.400; # seconds async I2C read reply timeout
      }
    } else {
      # Measurement completed
      if ($hash->{calcState} == TSL2561_CALC_STATE_COMPLETED) {
        # success, update readings based on new data
        my $chScale = 1;
        if (AttrVal($hash->{NAME}, "normalizeRawValues", 0)) {
          $chScale = I2C_TSL2561_GetChannelScale($hash);
        }
        readingsBeginUpdate($hash);
        readingsBulkUpdate($hash, "gain",            I2C_TSL2561_GetGain($hash));
        readingsBulkUpdate($hash, "integrationTime", I2C_TSL2561_GetIntegrationTime($hash));
        readingsBulkUpdate($hash, "broadband",       ceil($chScale*$hash->{broadband}));
        readingsBulkUpdate($hash, "ir",              ceil($chScale*$hash->{ir}));
        if (defined($lux)) {
          readingsBulkUpdate($hash, "luminosity", $lux);
        }
        my $state = ReadingsVal($name, 'state', '');
        if ($state ne TSL2561_STATE_SATURATED && $hash->{saturated}) {
          readingsBulkUpdate($hash, 'state', TSL2561_STATE_SATURATED, 1);
        } elsif ($state ne TSL2561_STATE_INITIALIZED && !$hash->{saturated}) {
          readingsBulkUpdate($hash, 'state', TSL2561_STATE_INITIALIZED, 1);
        }
        readingsEndUpdate($hash, 1);
      }

      # backup required operations (for diagnostics)
      $hash->{requiredOperations} = $hash->{operationCounter};
      
      # Reset state
      $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
      $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
      $hash->{operationCounter} = 0;
    }
  } else {
    readingsSingleUpdate($hash, 'state', TSL2561_STATE_DISABLED, 1);
  }
  
  # Schedule next polling
  Log3 $name, 5, "I2C_TSL2561_Poll: $pollDelay s";
  if ($pollDelay > 0) {
    InternalTimer(gettimeofday() + $pollDelay, 'I2C_TSL2561_Poll', $hash, 0);
  }
  
  return undef;
}

sub I2C_TSL2561_Set($@) {
  my ( $hash, @args ) = @_;
  my $name = $hash->{NAME};

  my $cmd = $args[1];

  if(!defined($sets{$cmd})) {
    return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets)
  }
  
  I2C_TSL2561_Poll($hash);
  return undef;
}

sub I2C_TSL2561_Undef($$) {
  my ($hash, $arg) = @_;
  
  RemoveInternalTimer($hash);
  if ($hash->{HiPi_used}) {
    $hash->{devTSL2561}->close()
  }
  
  return undef;
}

#
# process received control register
#
sub I2C_TSL2561_I2CRcvControl($$) {
  my ($hash, $control) = @_;
  my $name = $hash->{NAME};
  
  my $enabled = $control & 0x3;
  if ($enabled == TSL2561_CONTROL_POWERON) {
    Log3 $name, 5, "I2C_TSL2561_I2CRcvControl: is enabled";
    $hash->{acquiState} = TSL2561_ACQUI_STATE_ENABLED;
    $hash->{acquiStarted} = [gettimeofday];
  } else {
    Log3 $name, 5, "I2C_TSL2561_I2CRcvControl: is disabled";
    $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  }
  
  if (!$hash->{blockingIO}) {
    I2C_TSL2561_Poll($hash);
  }
  return undef;
}

#
# process received ID register 
#
sub I2C_TSL2561_I2CRcvID($$) {
  my ($hash, $sensorId) = @_;
  my $name = $hash->{NAME};
  
  if ( !($sensorId & 0b00010000) ) {
    return $name . ': Error! I2C failure: Please check your i2c bus and the connected device address: ' . $hash->{I2C_Address};
  }
  
  my $package = '';
  $hash->{tsl2561Package} = $sensorId >> 4;
  if ($hash->{tsl2561Package} == TSL2561_PACKAGE_CS) {
    $package = 'CS';
  } else {
    $package = 'T/FN/CL';
  }
  $hash->{sensorType} = 'TSL2561 Package ' . $package . ' Rev. ' . ( $sensorId & 0x0f );
  Log3 $name, 5, 'I2C_TSL2561_I2CRcvID: sensorId ' . $hash->{sensorType}; 
  
  # init state
  $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  readingsSingleUpdate($hash, 'state', TSL2561_STATE_INITIALIZED, 1);
  
  # force preset of integration time and gain (device may have been powered off)
  $hash->{timingModified} = 1;

  # I2C-API blocking/non-blocking detection
  $hash->{blockingIO} = $hash->{operationInProgress};
  
  if (!$hash->{blockingIO}) {
    I2C_TSL2561_Poll($hash);
  }
  return undef;
}

#
# process received timing register 
#
sub I2C_TSL2561_I2CRcvTiming ($$) {
  my ($hash, $timing) = @_;
  my $name = $hash->{NAME};
  
  $hash->{tsl2561IntegrationTime} = $timing & 0x03;
  $hash->{tsl2561Gain}            = $timing & 0x10;          
  Log3 $name, 4, "I2C_TSL2561_I2CRcvTiming: time $hash->{tsl2561IntegrationTime}, gain $hash->{tsl2561Gain}";
  
  $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
  if (!$hash->{blockingIO}) {
    I2C_TSL2561_Poll($hash);
  }
  return undef;
}

#
# process received ADC channel 0 register 
#
sub I2C_TSL2561_I2CRcvChan0 ($$) {
  my ($hash, $broadband) = @_;
  my $name = $hash->{NAME};
    
  $hash->{broadband} = $broadband;
  Log3 $name, 4, 'I2C_TSL2561_I2CRcvChan0 ' . $broadband; 
  
  $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_CH0_RECEIVED;
  return undef;
}

#
# process received ADC channel 1 register 
#
sub I2C_TSL2561_I2CRcvChan1 ($$) {
  my ($hash, $ir) = @_;
  my $name = $hash->{NAME};
  
  $hash->{ir} = $ir;
  Log3 $name, 4, 'I2C_TSL2561_I2CRcvChan1 ' . $ir;   

  $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_CH1_RECEIVED;
  if (!$hash->{blockingIO}) {
    I2C_TSL2561_Poll($hash);
  }
  return undef;
}

#
# preprocess received data from I2C bus
#
sub I2C_TSL2561_I2CRec ($$) {
  my ($hash, $clientmsg) = @_;
  my $name = $hash->{NAME};
  
  my $pname = undef;
  unless ($hash->{HiPi_used}) { #nicht nutzen wenn HiPi Bibliothek in Benutzung
    my $phash = $hash->{IODev};
    $pname = $phash->{NAME};
    while (my ( $k, $v ) = each %$clientmsg) { #erzeugen von Internals für alle Keys in $clientmsg die mit dem physical Namen beginnen
      $hash->{$k} = $v if $k =~ /^$pname/;
    }
  }
  
  if ($clientmsg->{direction} && $clientmsg->{reg} && 
    (($pname && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok") 
     || $hash->{HiPi_used})) {
    if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received})) {
      my $register = $clientmsg->{reg} & 0xF;
      Log3 $hash, 5, "$name RX register $register, $clientmsg->{nbyte} byte: $clientmsg->{received}";
      my $byte = undef;
      my $word = undef;
      my @raw = split(" ", $clientmsg->{received});
      if ($clientmsg->{nbyte} == 1) {
        $byte = $raw[0];
      } elsif ($clientmsg->{nbyte} == 2) {
        $word = $raw[1] << 8 | $raw[0];
      }
      if ($register == TSL2561_REGISTER_CONTROL) {
        I2C_TSL2561_I2CRcvControl($hash, $byte); 
      } elsif ($register == TSL2561_REGISTER_ID) {
        I2C_TSL2561_I2CRcvID($hash, $byte); 
      } elsif ($register == TSL2561_REGISTER_TIMING) {
        I2C_TSL2561_I2CRcvTiming($hash, $byte); 
      } elsif ($register == TSL2561_REGISTER_CHAN0_LOW) {
        I2C_TSL2561_I2CRcvChan0($hash, $word); 
      } elsif ($register == TSL2561_REGISTER_CHAN1_LOW) {
        I2C_TSL2561_I2CRcvChan1($hash, $word); 
      } else {
        Log3 $name, 3, "I2C_TSL2561_I2CRec unsupported register $register";
      }
    }
  }
  return undef;
}

=head2 I2C_TSL2561_Enable
  Title:    I2C_TSL2561_Enable
  Function: Enables the device
  Returns:  1 if enabling sensor was initiated, 0 if enabling sensor failed
  Args:     named arguments:
            - argument1 => hash:  $hash      hash of device
=cut

sub I2C_TSL2561_Enable($) {
  my ($hash) = @_;
  my $name = $hash->{NAME};
  
  Log3 $name, 5, 'I2C_TSL2561_Enable: start ';  
  my $success = 0;
  if (I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON)) {
    $success = I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, 1);
  }
  Log3 $name, 5, 'I2C_TSL2561_Enable: end '; 
  
  return $success;
}

=head2 I2C_TSL2561_Disable
  Title:    I2C_TSL2561_Disable
  Function: Disables the device
  Returns:  1 if disabling sensor was initiated, 0 if disabling sensor failed
  Args:     named arguments:
            - argument1 => hash:  $hash      hash of device
=cut

sub I2C_TSL2561_Disable($) {
  my ($hash) = @_;
  my $name = $hash->{NAME};

  Log3 $name, 5, 'I2C_TSL2561_Disable: start ';
  my $success = I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWEROFF);
  Log3 $name, 5, 'I2C_TSL2561_Disable: end ';
  
  return $success;
}

=head2 I2C_TSL2561_GetData
  Title:    I2C_TSL2561_GetData
  Function: Private function to read luminosity on both channels
  Returns:  -
  Args:     named arguments:
            - argument1 => hash:  $hash      hash of device
=cut

sub I2C_TSL2561_GetData($) {
  my ($hash) = @_;
  my $name = $hash->{NAME};
  
  # Data acquisition state machine with asynchronous wait
  my $success = 1;
  my $operations = 0;
  while (1) {
    $operations++;
    if ($hash->{acquiState} == TSL2561_ACQUI_STATE_ERROR) {
      $success = 0;
      last; # Abort, Start again at next slow poll
    } elsif ($operations > 10) {
      # Too many consecutive operations, abort
      $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
      Log3 $name, 5, "I2C_TSL2561_GetData: state machine stuck, aborting";
    } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_IDLE) {
      if (!defined($hash->{tsl2561Package})) {
        # Choose an address to scan the I2C bus for device in auto address mode
        if ($hash->{autoAddress}) {
          if ($hash->{I2C_Address} == hex(TSL2561_ADDR_LOW)) {
            $hash->{I2C_Address} = hex(TSL2561_ADDR_FLOAT);
          } elsif ($hash->{I2C_Address} == hex(TSL2561_ADDR_FLOAT)) {
            $hash->{I2C_Address} = hex(TSL2561_ADDR_HIGH);
          } else {
            $hash->{I2C_Address} = hex(TSL2561_ADDR_LOW);
          }      
        }
        # Detect TLS2561 package type and init integration time and gain
        Log3 $name, 5, "I2C_TSL2561_GetData: request device id";
        $hash->{acquiState} = TSL2561_ACQUI_STATE_SETUP;
        if (I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_ID, 1)) {
          last; # Wait for id confirmation, check again after next fast poll
        } else {
          $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
        }
      } elsif ($hash->{timingModified}) {
        $hash->{acquiState} = TSL2561_ACQUI_STATE_SETUP;
        if (I2C_TSL2561_SetTimingRegister($hash)) {
          last; # Wait new timing to be confirmed, check again after next fast poll
        } else {
          $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
        }
      } else {
        # Enable the device
        $hash->{acquiState} = TSL2561_ACQUI_STATE_ENABLE_REQUESTED;
        if (I2C_TSL2561_Enable($hash)) {
          last; # Wait for enable confirmation, check again after next fast poll
        } else {
          $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
        }
      }
    } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_SETUP) {
      last; # Wait for setup confirmation, check again after next fast poll
    } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLE_REQUESTED) {
      last; # Wait for enable confirmation, check again after next fast poll
    } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLED) {
      # Wait x ms for ADC to complete
      my $now = [gettimeofday];
      if (tv_interval($hash->{acquiStarted}, $now) >= I2C_TSL2561_GetIntegrationTime($hash)) {
        $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_AVAILABLE;
      } else {
        last; # Wait for measurement to complete, check again after next fast poll
      }
    } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_AVAILABLE) {
      # Read a two byte value from channel 0 and channel 1 (visible + infrared) 
      $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_REQUESTED;
      if (I2C_TSL2561_i2cread($hash,  TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN0_LOW, 2)) {
        if (!I2C_TSL2561_i2cread($hash,  TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN1_LOW, 2)) {
          $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
        }
      } else {
        $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
      }
    } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_REQUESTED) {
      last; # Wait for channel 0 or channel 1 data to be read
    } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_CH0_RECEIVED) {
      # Read a two byte value from channel 1 (infrared) 
      $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_REQUESTED;
      last; # Wait for channel 1 data to be read
    } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_CH1_RECEIVED) {
      $hash->{calcState} = TSL2561_CALC_STATE_DATA_RECEIVED;
      # Try to turn the device off to save power 
      I2C_TSL2561_Disable($hash);
      $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
      last; # Done, start again at next slow poll
    } else {
      # Undefined state
      $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
    }
  }
  
  return $success;
}

#
# write integration time and gain to device
#
sub I2C_TSL2561_SetTimingRegister($) {
  my ($hash) = @_;
  my $name = $hash->{NAME};

  my $success = 0;
  if (!AttrVal($hash->{NAME}, "disable", 0) && defined($hash->{tsl2561Package})) {
    # Update the timing register 
    my $autoGain = AttrVal($name, 'autoGain', 1);      
    if (!$autoGain) {
      my $attrVal = AttrVal($name, 'gain', 1);
      $hash->{tsl2561Gain} = $attrVal == 16? TSL2561_GAIN_16X : TSL2561_GAIN_1X;
    }
    my $autoIntegrationTime = AttrVal($name, 'autoIntegrationTime', 0);
    if (!$autoIntegrationTime) {
      my $attrVal = AttrVal($name, 'integrationTime', 13);  
      $hash->{tsl2561IntegrationTime} = $attrVal == 402? TSL2561_INTEGRATIONTIME_402MS : $attrVal == 101? TSL2561_INTEGRATIONTIME_101MS : TSL2561_INTEGRATIONTIME_13MS;
    }
    Log3 $name, 4, "I2C_TSL2561_SetTimingRegister: time $hash->{tsl2561IntegrationTime}, gain $hash->{tsl2561Gain}";
    if (I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, $hash->{tsl2561IntegrationTime} | $hash->{tsl2561Gain})) {
      if (I2C_TSL2561_i2cread($hash,  TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, 1)) {
        $success = 1;
      }
    }
  }
  $hash->{timingModified} = 0;  
 
  return $success;
}

=head2 I2C_TSL2561_SetIntegrationTime
  Title:    I2C_TSL2561_SetIntegrationTime
  Function:  Sets the integration time for the TSL2561
  Returns:  -
  Args:    named arguments:
        -argument1 => hash:  $hash    hash of device
        -argument1 => number:  $time    constant for integration time setting

=cut

sub I2C_TSL2561_SetIntegrationTime($$) {
  my ($hash, $time) = @_;
  my $name = $hash->{NAME};
 
  # store the value even if $hash->{tsl2561Package} is not set (yet). That happens
  # during fhem startup.
  if (defined($hash->{tsl2561IntegrationTime}) && $hash->{tsl2561IntegrationTime} != $time) {
    Log3 $name, 4, "I2C_TSL2561_SetIntegrationTime: $hash->{tsl2561IntegrationTime} -> $time";
  }
  $hash->{tsl2561IntegrationTime} = $time;  
  $hash->{timingModified} = 1;  
  
  return undef;
}

#
# decode TSL2561 integration time into decimal value
# @param device hash
# @return integration time in seconds that was last reported by the TSL2561
#
sub I2C_TSL2561_GetIntegrationTime($) {
  my ($hash) = @_;
  my $tsl2561IntegrationTime = $hash->{tsl2561IntegrationTime};

  my $integrationTime = 0.402;   # 402 ms
  if ($tsl2561IntegrationTime == TSL2561_INTEGRATIONTIME_13MS) {
    $integrationTime = 0.0137; # 13.7 ms
  } elsif ($tsl2561IntegrationTime == TSL2561_INTEGRATIONTIME_101MS) {
    $integrationTime = 0.101;  # 101 ms
  }
  
  return $integrationTime;
}

=head2 I2C_TSL2561_SetGain
  Title:    I2C_TSL2561_SetGain
  Function: Adjusts the gain on the TSL2561 (adjusts the sensitivity to light)
  Returns:  -
  Args:     named arguments:
            - argument1 => hash:   $hash    hash of device
            - argument1 => number: $gain    constant for gain
=cut

sub I2C_TSL2561_SetGain($$) {
  my ($hash, $gain) = @_;
  my $name = $hash->{NAME};

  # store the value even if $hash->{tsl2561Package} is not set (yet). That happens
  # during fhem startup.
  if (defined($hash->{tsl2561Gain}) && $hash->{tsl2561Gain} != $gain) {
    Log3 $name, 4, "I2C_TSL2561_SetGain: $hash->{tsl2561Gain} -> $gain";
  }
  $hash->{tsl2561Gain} = $gain;
  $hash->{timingModified} = 1;  

  return undef;
}

#
# decode TSL2561 gain into decimal value
# @param device hash
# @return decimal gain factor that was last reported by the TSL2561
#
sub I2C_TSL2561_GetGain($) {
  my ($hash) = @_;
  my $tsl2561Gain = $hash->{tsl2561Gain};
  
  my $gain = 1;
  if (defined($tsl2561Gain) && $tsl2561Gain) {
    $gain = 16;
  }
  
  return $gain;
}

=head2 I2C_TSL2561_GetLuminosity
  Title:    I2C_TSL2561_GetLuminosity
  Function:  Gets the broadband (mixed lighting) and IR only values from the TSL2561, adjusting gain if auto-gain is enabled and calculate luminosity
  Returns:  luminosity
  Args:    named arguments:
        -argument1 => hash:  $hash    hash of device

=cut

sub I2C_TSL2561_GetLuminosity($) {
  my ($hash) = @_;
  my $name = $hash->{NAME};

#  Log3 $name, 5, "I2C_TSL2561_GetLuminosity: start";

  $hash->{operationInProgress} = 1;

  # Luminosity calculation state machine
  my $lux = undef;
  while(1) {
    $hash->{operationCounter}++;
    Log3 $name, 5, "I2C_TSL2561_GetLuminosity: calc state $hash->{calcState} acqui state $hash->{acquiState}";
    if ($hash->{calcState} == TSL2561_CALC_STATE_ERROR) {
      Log3 $name, 5, "I2C_TSL2561_GetLuminosity: error, aborting";
      # Try to turn the device off to save power 
      I2C_TSL2561_Disable($hash);
      # Reset package to force device reinitialization
      $hash->{tsl2561Package} = undef;
      # Claim I2C error
      readingsSingleUpdate($hash, 'state', TSL2561_STATE_I2C_ERROR, 1);      
      last; # Abort, start again at next slow poll
    } elsif ($hash->{operationCounter} > TSL2561_MAX_CONSECUTIVE_OPERATIONS) {
      # Too many consecutive operations, abort
      $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
      Log3 $name, 5, "I2C_TSL2561_GetLuminosity: state machine stuck, aborting";
    } elsif ($hash->{calcState} == TSL2561_CALC_STATE_IDLE) {
      # Enable device and request data
      Log3 $name, 5, "I2C_TSL2561_GetLuminosity: starting new measurement";
      if (I2C_TSL2561_GetData($hash)) {
        $hash->{calcState} = TSL2561_CALC_STATE_DATA_REQUESTED;
      } else {      
        $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
      }      
    } elsif ($hash->{calcState} == TSL2561_CALC_STATE_DATA_REQUESTED) {
      # Wait for device
      if (I2C_TSL2561_GetData($hash)) {
        if ($hash->{acquiState} == TSL2561_ACQUI_STATE_SETUP) {
          last; # Wait for setup confirmation, check again after next fast poll
        } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLE_REQUESTED) {
          last; # Wait for enable to be confirmed, check again at next fast poll
        } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLED) {
          last; # Wait for measurement to complete, check again at next fast poll
        } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_REQUESTED) {
          last; # Wait for data to be read, check again at next fast poll
        }
      } else {
        $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
      }
    } elsif ($hash->{calcState} == TSL2561_CALC_STATE_DATA_RECEIVED) {
      # Data was received, optimize gain
      my $autoGain = AttrVal($name, 'autoGain', 1);
      if ($autoGain) {
        # Get the hi/low threshold for the current integration time
        my $it = $hash->{tsl2561IntegrationTime};
        my $hi = TSL2561_AGC_THI_402MS;
        my $lo = TSL2561_AGC_TLO_402MS;
        if ($it == TSL2561_INTEGRATIONTIME_13MS) {
          $hi = TSL2561_AGC_THI_13MS;
          $lo = TSL2561_AGC_TLO_13MS;
        } elsif ($it == TSL2561_INTEGRATIONTIME_101MS) {
          $hi = TSL2561_AGC_THI_101MS;
          $lo = TSL2561_AGC_TLO_101MS;
        } 
        if (($hash->{broadband} < $lo) && ($hash->{tsl2561Gain} == TSL2561_GAIN_1X)) {
          # Increase gain and try again 
          I2C_TSL2561_SetGain($hash, TSL2561_GAIN_16X);
          $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
          $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
          next;
        } elsif (($hash->{broadband} > $hi) && ($hash->{tsl2561Gain} == TSL2561_GAIN_16X)) {
          # Drop gain and try again 
          I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X);
          $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
          $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
          next;
        } else {
          # Reading is either valid, or we're already at the chips limits 
        }
      } else {
        # Auto gain disabled, always valid
      }

      # Optimize integration time (make sure the sensor isn't saturated at 402 ms)
      my $clipThreshold = 0;
      if ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS) {
        $clipThreshold = TSL2561_CLIPPING_13MS;
      } elsif ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS) {
        $clipThreshold = TSL2561_CLIPPING_101MS;
      } else {
        $clipThreshold = TSL2561_CLIPPING_402MS;
      }
      my $autoIntegrationTime = AttrVal($name, 'autoIntegrationTime', 0);
      if (($hash->{broadband} > $clipThreshold) || ($hash->{ir} > $clipThreshold)) {
        # ADC saturated, try to decrease integration time
        if ($autoIntegrationTime && $hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_402MS) {
          # Drop integration time and try again
          I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_101MS);
          $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
          $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
          next;
        } else {
          # Integration time fixed or already below 402 ms, give up
          $hash->{saturated} = 1; 
        }
      } elsif ($autoIntegrationTime
           && ($hash->{broadband} < ($clipThreshold >> 2) && $hash->{ir} < ($clipThreshold >> 2)) 
           && ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS || $hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS)) {
        # Integration time below 178 ms, maximize and try again
        I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_402MS);
        $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
        $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
        next;
      } else {
        # Readings are not saturated or auto integration time is disabled
        $hash->{saturated} = 0; 
      }

      # Received data is valid, calculate luminosity
      $lux = I2C_TSL2561_CalculateLux($hash);
      $hash->{calcState} = TSL2561_CALC_STATE_COMPLETED;
      last; # Done, start again at next slow poll
    } else {
      # Undefined state
      $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
    }
  }
  
  $hash->{operationInProgress} = 0;
  
#  Log3 $name, 5, "I2C_TSL2561_GetLuminosity: end";
  
  return $lux;
}

#
# get channel scale
#
sub I2C_TSL2561_GetChannelScale($) {
  my ($hash) = @_;
  my $name = $hash->{NAME};

  my $chScale = 0;
  
  if (AttrVal($name, 'floatArithmetics', 0)) {
    # Get the correct scale depending on the integration time 
    if (!defined($hash->{tsl2561IntegrationTime})) {
      $chScale = 1.0;
    } elsif ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS) {
      $chScale = 322.0/11; 
    } elsif ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS) {
      $chScale = 322.0/81;
    } else {
      $chScale = 1.0;
    }

    # Scale for gain (1x or 16x) 
    if (!defined($hash->{tsl2561Gain}) || !$hash->{tsl2561Gain}) {
      $chScale = $chScale*16;
    }
  } else {
    # Get the correct scale depending on the integration time 
    if ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS) {
      $chScale = TSL2561_LUX_CHSCALE_TINT0;
    } elsif ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS) {
      $chScale = TSL2561_LUX_CHSCALE_TINT1;
    } else {
      $chScale = (1 << TSL2561_LUX_CHSCALE);
    }

    # Scale for gain (1x or 16x) 
    if (!defined($hash->{tsl2561Gain}) || !$hash->{tsl2561Gain}) {
      $chScale = $chScale << 4;
    }
  }
  
  return $chScale;
}

=head2 I2C_TSL2561_CalculateLux
  Title:    I2C_TSL2561_CalculateLux
  Function: Converts the raw sensor values to the standard SI lux equivalent. Returns 0 if the sensor is saturated and the values are unreliable.
  Returns:  number
  Args:     named arguments:
            - argument1 => hash:    $hash      hash of device
=cut

sub I2C_TSL2561_CalculateLux($) {
  my ($hash) = @_;
  my $name = $hash->{NAME};

  # Get the correct scale depending on gain and integration time 
  my $chScale = I2C_TSL2561_GetChannelScale($hash);
  if (AttrVal($name, 'floatArithmetics', 0)) {
    # Scale the channel values 
    my $channel0 = $chScale*$hash->{broadband};
    my $channel1 = $chScale*$hash->{ir};

    # Find the ratio of the channel values (Channel1/Channel0) 
    my $ratio = 0.0;
    if ($channel0 != 0) {
      $ratio = $channel1/$channel0;
    }
    
    # Calculate luminosity (see TSL2561 data sheet)
    my $lux = undef;
    if ($hash->{tsl2561Package} == TSL2561_PACKAGE_CS) {
      #  CS package
      if ($ratio <= 0.52) {
        $lux = 0.0315*$channel0 - 0.0593*$channel0*pow($ratio, 1.4);
      } elsif ($ratio <= 0.65) {
        $lux = 0.0229*$channel0 - 0.0291*$channel1;
      } elsif ($ratio <= 0.80) {
        $lux = 0.0157*$channel0 - 0.0180*$channel1;
      } elsif ($ratio <= 1.30) {
        $lux = 0.00338*$channel0 - 0.00260*$channel1;
      } else {
        $lux = 0.0;
      }
    } else {
      #  T, FN and CL package
      if ($ratio <= 0.50) {
        $lux = 0.0304*$channel0 - 0.062*$channel0*pow($ratio, 1.4);
      } elsif ($ratio <= 0.61) {
        $lux = 0.0224*$channel0 - 0.031*$channel1;
      } elsif ($ratio <= 0.80) {
        $lux = 0.0128*$channel0 - 0.0153*$channel1;
      } elsif ($ratio <= 1.30) {
        $lux = 0.00146*$channel0 - 0.00112*$channel1;
      } else {
        $lux = 0.0;
      }
    }
    
    if ($lux >= 100) {
      # Round to 3 significant digits if at least 100
      my $roundFactor = 10**(floor(log($lux)/log(10)) - 2);
      $lux = $roundFactor*floor($lux/$roundFactor + 0.5);
    } else {
      # Round to 1 fractional digit if less than 100
      $lux = floor(10*$lux + 0.5)/10;
    }
    
    return $lux;
  } else {
    # Scale the channel values 
    my $channel0 = ($hash->{broadband} * $chScale) >> TSL2561_LUX_CHSCALE;
    my $channel1 = ($hash->{ir} * $chScale) >> TSL2561_LUX_CHSCALE;

    # Find the ratio of the channel values (Channel1/Channel0) 
    my $ratio1 = 0;
    if ($channel0 != 0) {
      $ratio1 = ($channel1 << (TSL2561_LUX_RATIOSCALE+1)) / $channel0;
    }

    # round the ratio value  
    my $ratio = ($ratio1 + 1) >> 1;
    
    my $b=0;
    my $m=0;

    if ($hash->{tsl2561Package} == TSL2561_PACKAGE_CS) {
    #  CS package
      if (($ratio >= 0) && ($ratio <= TSL2561_LUX_K1C)) {
        $b=TSL2561_LUX_B1C;
        $m=TSL2561_LUX_M1C;
      } elsif ($ratio <= TSL2561_LUX_K2C) {
        $b=TSL2561_LUX_B2C;
        $m=TSL2561_LUX_M2C;
      } elsif ($ratio <= TSL2561_LUX_K3C) {
        $b=TSL2561_LUX_B3C;
        $m=TSL2561_LUX_M3C;
      } elsif ($ratio <= TSL2561_LUX_K4C) {
        $b=TSL2561_LUX_B4C;
        $m=TSL2561_LUX_M4C;
      } elsif ($ratio <= TSL2561_LUX_K5C) {
        $b=TSL2561_LUX_B5C;
        $m=TSL2561_LUX_M5C;
      } elsif ($ratio <= TSL2561_LUX_K6C) {
        $b=TSL2561_LUX_B6C;
        $m=TSL2561_LUX_M6C;
      } elsif ($ratio <= TSL2561_LUX_K7C) {
        $b=TSL2561_LUX_B7C;
        $m=TSL2561_LUX_M7C;
      } elsif ($ratio > TSL2561_LUX_K8C) {
        $b=TSL2561_LUX_B8C;
        $m=TSL2561_LUX_M8C;
      }
    } elsif ($hash->{tsl2561Package} == TSL2561_PACKAGE_T_FN_CL) {
      #  T, FN and CL package
      if (($ratio >= 0) && ($ratio <= TSL2561_LUX_K1T)) {
        $b=TSL2561_LUX_B1T;
        $m=TSL2561_LUX_M1T;
      } elsif ($ratio <= TSL2561_LUX_K2T) {
        $b=TSL2561_LUX_B2T;
        $m=TSL2561_LUX_M2T;
      } elsif ($ratio <= TSL2561_LUX_K3T) {
        $b=TSL2561_LUX_B3T;
        $m=TSL2561_LUX_M3T;
      } elsif ($ratio <= TSL2561_LUX_K4T) {
        $b=TSL2561_LUX_B4T;
        $m=TSL2561_LUX_M4T;
      } elsif ($ratio <= TSL2561_LUX_K5T) {
        $b=TSL2561_LUX_B5T;
        $m=TSL2561_LUX_M5T;
      } elsif ($ratio <= TSL2561_LUX_K6T) {
        $b=TSL2561_LUX_B6T;
        $m=TSL2561_LUX_M6T;
      } elsif ($ratio <= TSL2561_LUX_K7T) {
        $b=TSL2561_LUX_B7T;
        $m=TSL2561_LUX_M7T;
      } elsif ($ratio > TSL2561_LUX_K8T) {
        $b=TSL2561_LUX_B8T;
        $m=TSL2561_LUX_M8T;
      }
    }

    my $temp = (($channel0 * $b) - ($channel1 * $m));

    # Do not allow negative lux value 
    if ($temp < 0) {
      $temp = 0;
    }

    # Round lsb (2^(LUX_SCALE-1)) 
    $temp += (1 << (TSL2561_LUX_LUXSCALE-1));

    # Strip off fractional portion 
    my $lux = $temp >> TSL2561_LUX_LUXSCALE;

    # Signal I2C had no errors 
    return $lux;
  }
}

=head2 I2C_TSL2561_i2cread
  Title:     I2C_TSL2561_i2cread
  Function:  implements I2C read operation abstraction
  Returns:   1 on success, 0 on error
  Args:      - argument1 => hash
             - argument2 => I2C register
             - argument3 => number of bytes to read
=cut

sub I2C_TSL2561_i2cread($$$) {
  my ($hash, $reg, $nbyte) = @_;
  my $success = 1;
  
  local $SIG{__WARN__} = sub {
    my $message = shift;
    # turn warnings from RPII2C_HWACCESS_ioctl into exception
    if ($message =~ /Exiting subroutine via last at.*00_RPII2C.pm/) {
      die;
    } else {
      warn($message);
    }
  };
  
  if ($hash->{HiPi_used}) {
    eval {
      my @values = $hash->{devTSL2561}->bus_read($reg, $nbyte);
      I2C_TSL2561_I2CRec($hash, {
        direction => "i2cread",
        i2caddress => $hash->{I2C_Address},
        reg => $reg,
        nbyte => $nbyte,
        received => join (' ',@values),
      });
    };
    Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_TSL2561_Catch($@)) if $@;
  } elsif (defined (my $iodev = $hash->{IODev})) {
    eval {
      CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
      direction => "i2cread",
      i2caddress => $hash->{I2C_Address},
      reg => $reg,
      nbyte => $nbyte
      });
    };
    my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'};
    if (defined($sendStat) && $sendStat ne 'Ok') {
      readingsSingleUpdate($hash, 'state', TSL2561_STATE_I2C_ERROR, 1);
      Log3 ($hash, 5, $hash->{NAME} . ": i2cread on $iodev->{NAME} failed");
      $success = 0;
    } 
  } else {
    Log3 ($hash, 1, $hash->{NAME} . ': ' . "no IODev assigned to '$hash->{NAME}'");
    $success = 0;
  }
  
  return $success;
}

sub I2C_TSL2561_i2cwrite($$$) {
  my ($hash, $reg, @data) = @_;
  my $success = 1;
  
  if ($hash->{HiPi_used}) {
    eval {
      $hash->{devTSL2561}->bus_write($reg, join (' ',@data));
      I2C_TSL2561_I2CRec($hash, {
        direction => "i2cwrite",
        i2caddress => $hash->{I2C_Address},
        reg => $reg,
        data => join (' ',@data),
      });
    };
    Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_TSL2561_Catch($@)) if $@;
  } elsif (defined (my $iodev = $hash->{IODev})) {
    eval {
      CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
      direction => "i2cwrite",
      i2caddress => $hash->{I2C_Address},
      reg => $reg,
      data => join (' ',@data), 
      });
    };
    my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'};
    if (defined($sendStat) && $sendStat ne 'Ok') {
      readingsSingleUpdate($hash, 'state', TSL2561_STATE_I2C_ERROR, 1);
      Log3 ($hash, 5, $hash->{NAME} . ": i2cwrite on $iodev->{NAME} failed");
      $success = 0;
    }
  } else {
    Log3 ($hash, 1, $hash->{NAME} . ': ' . "no IODev assigned to '$hash->{NAME}'");
    $success = 0;
  }
  
  return $success;
}

1;

=pod
=item summary TSL2561 luminosity sensor
=item summary_DE TSL2561 Helligkeitssensor
=begin html

<a name="I2C_TSL2561"></a>
<h3>I2C_TSL2561</h3>
<ul>
    <a name="I2C_TSL2561"></a>
    <p>
    With this module you can read values from the ambient light sensor TSL2561
    via the i2c bus on Raspberry Pi.<br>
    The luminosity value returned is a good human eye reponse approximation of an 
    illumination measurement in the range of 0.1 to 40000+ lux (but not a replacement for a 
    precision measurement, relation between measured value and true value may vary by 40%).
    <br><br>
    
    <b>There are two possibilities connecting to I2C bus:</b><br>
    <ul>
        <li><b>via IODev module</b><br>
            The I2C messages are send through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
            or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
            <b>attribute IODev must be set</b><br>
            <br>
        </li>
        <li><b>via HiPi library</b><br>
            Add these two lines to your <b>/etc/modules</b> file to load the I2C relevant kernel modules
            automaticly during booting your Raspberry Pi.<br>
            <code><pre> 
            i2c-bcm2708
            i2c-dev
            </pre></code>
            Install HiPi perl modules:<br>
            <code><pre> wget http://raspberry.znix.com/hipifiles/hipi-install perl hipi-install</pre></code>
            To change the permissions of the I2C device create file:<br>
            <code><pre> /etc/udev/rules.d/98_i2c.rules</pre></code>
            with this content:<br>
            <code><pre> SUBSYSTEM=="i2c-dev", MODE="0666"</pre></code>
            <b>Reboot</b><br>
            <br>
            To use the sensor on the second I2C bus at P5 connector
            (only for version 2 of Raspberry Pi) you must add the bold
            line of following code to your FHEM start script:
            <code><pre>
            case "$1" in
            'start')
                <b>sudo hipi-i2c e 0 1</b>
            ...
            </pre></code>
        </li>
    </ul>
    <p>

    <b>Define</b>
    <ul>
        <code>define TSL2561 I2C_TSL2561 [&lt;I2C device&gt;] &lt;I2C address&gt</code><br><br>
        &lt;I2C device&gt; mandatory for HiPi, must be omitted if you connect via IODev<br>
        &lt;I2C address&gt; may be 0x29, 0x39 or 0x49 (and 'AUTO' when using IODev to search for device at startup and after an I2C error)<br>
        <br>
        Examples:
        <pre>
        define TSL2561 I2C_TSL2561 /dev/i2c-0 0x39
        attr TSL2561 poll_interval 5
        </pre>
        <pre>
        define TSL2561 I2C_TSL2561 0x39
        attr TSL2561 IODev I2CModule
        attr TSL2561 poll_interval 5
        </pre>
        <pre>
        define TSL2561 I2C_TSL2561 AUTO
        attr TSL2561 IODev I2CModule
        attr TSL2561 poll_interval 5
        </pre>
    </ul>

    <b>Set</b>
    <ul>
        <code>get &lt;name&gt; update</code><br><br>
        Force immediate illumination measurement and restart a new poll_interval.<br><br>
        Note that the new readings are not yet available after set returns because the
        measurement is performed asynchronously. Depending on the attributes integration time,
        autoGain and autoIntegrationTime this may require more than one second to complete.
    </ul>
    <p>

    <b>Readings</b>
    <ul>
        <li>luminosity<br>
            Good human eye reponse approximation of an illumination measurement in the range of 0.1 to 40000+ lux.<br>
            Rounded to 3 significant digits or one fractional digit.
        </li>
        <li>broadband<br>
            Broadband spectrum sensor sample.<br>
            Enable attribute normalizeRawValues for continuous readings independed of actual gain and integration time settings.
        </li>
        <li>ir<br>
            Infrared spectrum sensor sample.<br>
            Enable attribute normalizeRawValues for continuous readings independed of actual gain and integration time settings.
        </li>
        <li>gain<br>
            sensor gain used for current luminosity measurement (1 or 16)<br>
        </li>
        <li>integrationTime<br>
            integration time in seconds used for current luminosity measurement<br>
        </li>
        <li>state<br>
            Default: Initialized, valid values: Undefined, Defined, Initialized, Saturated, Disabled, I2C Error
        </li>
    </ul>
    <p>
     
    <a name="I2C_TSL2561attr"></a>
    <b>Attributes</b>
    <ul>
        <li>IODev<br>
            Set the name of an IODev module. If undefined the perl modules HiPi::Device::I2C are required.<br>
            Default: undefined<br>
        </li>
        <li>poll_interval<br>
            Set the polling interval in minutes to query the sensor for new measured  values.
            By changing this attribute a new illumination measurement will be triggered.<br>
            Default: 5, valid values: 1, 2, 5, 10, 20, 30<br>
        </li>
        <li>gain<br>
            Set gain factor. Attribute will be ignored if autoGain is enabled.<br>
            Default: 1, valid values: 1, 16
        </li>
        <li>integrationTime<br>
            Set time in ms the sensor takes to measure the light. Attribute will be ignored if autoIntegrationTime is enabled.<br>
            Default: 13, valid values: 13, 101, 402<br>
            See this <a href="https://learn.sparkfun.com/tutorials/tsl2561-luminosity-sensor-hookup-guide/using-the-arduino-library">tutorial</a>
            for more details.
        </li>
        <li>autoGain<br>
            Enable auto gain. If set to 1, the gain parameter is adjusted automatically depending on light conditions.<br>
            Default: 1, valid values: 0, 1<br>            
        </li>
        <li>autoIntegrationTime<br>
            Enable auto integration time. If set to 1, the integration time parameter is adjusted automatically depending on light conditions.<br>
            Default: 0, valid values: 0, 1<br>
        </li>
        <li>normalizeRawValues<br>
            Scale the sensor raw values broadband and ir depending on actual gain and integrationTime to the equivalent of the settings for maximum sensitivity (gain=16 and integrationTime=403ms). This feature may be useful when autoGain or autoIntegrationTime is enabled to provide continuous values instead of jumping values when gain or integration time changes.<br>
            Default: 0, valid values: 0, 1<br>
        </li>
        <li>floatArithmetics<br>
            Enable float arithmetics.<br>
            If set to 0, the luminosity is calculated using int arithmetics (for very low powered platforms).<br>
            If set to 1, the luminosity is calculated using float arithmetics, yielding some additional precision.
            Default: 1, valid values: 0, 1<br>            
        </li>
        <li>disable<br>
            Disable I2C bus access.<br>
            Default: 0, valid values: 0, 1
        </li>
    </ul>
    <p>

    <b>Notes</b>
    <ul>
      <li>Because the measurement may take several 100 milliseconds a measurement cycle will be executed asynchronously, so
          do not expect to have new values immediately available after "set update" returns. If autoGain or autoIntegrationTime
          are enabled, more than one measurement cycle will be required if light conditions change.
      </li>
      <li>With HiPi and especially IODev there are several I2C interfaces available, some blocking, some non-blocking and 
          some with different physical layers. The module has no knowledge of the specific properties of an interface and
          therefore module operation and timing may not be exactly the same with each interface type.
      </li>
      <li>If AUTO is used as device address, one address per measurement cycle will be tested. Depending on your device address 
          it may be necessary to execute "set update" several times to find your device.
      </li>
      <li>When using Firmata the I2C write/read delay attribute "i2c-config" of the FRM module can be set to any value.
      </li>
    </ul>
    <br>
</ul>

=end html

=cut
