diff --git a/fhem/CHANGED b/fhem/CHANGED index 4bdd60a0b..42b580305 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,7 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - feature: 51_I2C_TSL2561.pm: add support for using IODev, allows use without + HiPi being installed. By jensb, forum id 277192 - feature: SYSMON: Freq. for 8 CPU Cores - feature: 98_Modbus.pm: added base module for modbus devices - feature: 98_ModbusAttr.pm: added module for generic modbus devices diff --git a/fhem/FHEM/51_I2C_TSL2561.pm b/fhem/FHEM/51_I2C_TSL2561.pm index c61bb0552..2b10b42bc 100644 --- a/fhem/FHEM/51_I2C_TSL2561.pm +++ b/fhem/FHEM/51_I2C_TSL2561.pm @@ -2,28 +2,35 @@ 51_I2C_TSL2561.pm =head1 SYNOPSIS - Modul for FHEM for reading a TSL2561 luminosity sensor via I2C + Modul for FHEM for reading a TSL2561 ambient light sensor via I2C connected to the Raspberry Pi. contributed by Kai Stuke 2014 + $Id$ =head1 DESCRIPTION - 51_I2C_TSL2561.pm reads the luminosity of the digital luminosity sensor TSL2561 + 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 the HiPi Perl Modules - see: http://raspberrypi.znix.com/hipidocs/ + This module needs IODev FHEM modules or the HiPi perl modules + IODev see: RPII2C, FRM or NetzerI2C + HiPi see: http://raspberrypi.znix.com/hipidocs/ - For a simple automated installation:
+ For a simple automated installation of the HiPi perl modules:
wget http://raspberry.znix.com/hipifiles/hipi-install perl hipi-install - Example: + HiPi Example: define Luminosity I2C_TSL2561 /dev/i2c-0 0x39 attr Luminosity poll_interval 5 - -=head1 CAVEATS + + 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). @@ -45,11 +52,28 @@ 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 fratctional digit when float arithmetics are enabled + +=head1 TODO + HiPi compatibility test (required) + HiPi I2C error detection (optional) + autoIntegrationTime (optinal, decrease integration time when measurement gets saturated) =head1 CREDITS - Based on the module 51_I2C_BMP180.pm by Dirk Hoffmann + 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 @@ -61,6 +85,7 @@ =head1 AUTHOR - Kai Stuke kaihs@FHEM_Forum (forum.fhem.de) + modified by Jens Beyer jensb@FHEM_Forum (forum.fhem.de) =cut @@ -69,34 +94,62 @@ package main; use strict; use warnings; -use HiPi::Device::I2C; use Time::HiRes qw(usleep); use Scalar::Util qw(looks_like_number); -use Error qw(:try); +use POSIX (); +#use Error qw(:try); use constant { - TSL2561_VISIBLE =>2, # channel 0 - channel 1, - TSL2561_INFRARED =>1, # channel 1, - TSL2561_FULLSPECTRUM =>0, # channel 0, - # I2C address options TSL2561_ADDR_LOW => '0x29', TSL2561_ADDR_FLOAT => '0x39', # Default address (pin left floating) TSL2561_ADDR_HIGH => '0x49', + # 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 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 + + # Auto-gain thresholds + TSL2561_AGC_THI_13MS => 4850, # Max value at Ti 13ms = 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, + + # Clipping thresholds + TSL2561_CLIPPING_13MS => 4900, + TSL2561_CLIPPING_101MS => 37000, + TSL2561_CLIPPING_402MS => 65000, + # Lux calculations differ slightly for CS package - TSL2561_PACKAGE_CS =>0b0001, - TSL2561_PACKAGE_T_FN_CL =>0b0101, - - 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_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, @@ -155,40 +208,9 @@ use constant { TSL2561_LUX_B8C =>0x0000, # 0.000 * 2^LUX_SCALE TSL2561_LUX_M8C =>0x0000, # 0.000 * 2^LUX_SCALE - # Auto-gain thresholds - TSL2561_AGC_THI_13MS =>4850, # Max value at Ti 13ms = 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, - - # Clipping thresholds - TSL2561_CLIPPING_13MS =>4900, - TSL2561_CLIPPING_101MS =>37000, - TSL2561_CLIPPING_402MS =>65000, - - 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, - - TSL2561_INTEGRATIONTIME_13MS => 0x00, # 13.7ms - TSL2561_INTEGRATIONTIME_101MS => 0x01, # 101ms - TSL2561_INTEGRATIONTIME_402MS => 0x02, # 402ms - - TSL2561_GAIN_1X => 0x00, # No gain - TSL2561_GAIN_16X => 0x10, # 16x gain - + TSL2561_VISIBLE =>2, # channel 0 - channel 1 + TSL2561_INFRARED =>1, # channel 1 + TSL2561_FULLSPECTRUM =>0, # channel 0 }; ################################################## @@ -201,8 +223,6 @@ sub I2C_TSL2561_Poll($); sub I2C_TSL2561_Set($@); sub I2C_TSL2561_Get($); sub I2C_TSL2561_Undef($$); -sub I2C_TSL2561_ReadWord($$); -sub I2C_TSL2561_ReadByte($$); sub I2C_TSL2561_Enable($); sub I2C_TSL2561_Disable($); sub I2C_TSL2561_GetData($); @@ -211,10 +231,13 @@ sub I2C_TSL2561_SetGain($$); sub I2C_TSL2561_GetLuminosity($); sub I2C_TSL2561_CalculateLux($); +my $libcheck_hasHiPi = 1; + my %sets = ( 'gain' => "", 'integrationTime' => "", 'autoGain' => "", + 'floatArithmetics' => "", ); my %gets = ( @@ -247,19 +270,22 @@ my %validPackages = ( sub I2C_TSL2561_Initialize($) { my ($hash) = @_; + + eval "use HiPi::Device::I2C;"; + $libcheck_hasHiPi = 0 if($@); $hash->{DefFn} = 'I2C_TSL2561_Define'; $hash->{AttrFn} = 'I2C_TSL2561_Attr'; #$hash->{SetFn} = 'I2C_TSL2561_Set'; $hash->{GetFn} = 'I2C_TSL2561_Get'; $hash->{UndefFn} = 'I2C_TSL2561_Undef'; + $hash->{I2CRecFn} = 'I2C_TSL2561_I2CRec'; - $hash->{AttrList} = 'do_not_notify:0,1 showtime:0,1 ' . + $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 ' . $readingFnAttributes; - $hash->{tsl2561IntegrationTime} = TSL2561_INTEGRATIONTIME_13MS; - $hash->{tsl2561Gain} = TSL2561_GAIN_1X; - $hash->{tsl2561AutoGain} = 1; + 'gain:1,16 integrationTime:13,101,402 autoGain:0,1 ' . + 'floatArithmetics:0,1 ' . $readingFnAttributes; + $hash->{AttrList} .= " useHiPiLib:0,1 " if ($libcheck_hasHiPi); } =head2 I2C_TSL2561_Define @@ -275,81 +301,123 @@ sub I2C_TSL2561_Initialize($) { sub I2C_TSL2561_Define($$) { my ($hash, $def) = @_; my @a = split('[ \t][ \t]*', $def); - my $name = $a[0]; - my $dev = $a[2]; - my $address = lc $a[3]; + + readingsSingleUpdate($hash, 'state', 'Undefined', 1); Log3 $name, 5, "I2C_TSL2561_Define start"; - my $msg = ''; - if( (@a < 4)) { - $msg = 'wrong syntax: define I2C_TSL2561 devicename address'; - return undef; - } - $hash->{address} = $validAdresses{$address}; - if (!defined($hash->{address})) { - $msg = 'Wrong address, must be one of 0x29, 0x39, 0x49'; - return undef; + $hash->{HiPi_exists} = $libcheck_hasHiPi if ($libcheck_hasHiPi); + $hash->{HiPi_used} = 0; + + my $address = undef; + my $msg = ''; + if ((@a < 3)) { + $msg = 'wrong syntax: define I2C_TSL2561 [devicename] address'; + } elsif ((@a == 3)) { + $address = lc($a[2]); + } else { + $address = lc($a[3]); + if ($libcheck_hasHiPi) { + $hash->{HiPi_used} = 1; + } else { + $msg = '$name error: HiPi library not installed'; + } } - - # create default attributes - $msg = CommandAttr(undef, $name . ' poll_interval 5'); - #$msg = CommandAttr(undef, $name . ' gain 1'); - #$msg = CommandAttr(undef, $name . ' integrationTime 13'); - if ($msg) { - Log (1, $msg); + Log3 ($hash, 1, $msg); + return $msg; + } + + $address = $validAdresses{$address}; + if (defined($address)) { + $hash->{I2C_Address} = hex($address); + } else { + $msg = "Wrong address $address, must be one of 0x29, 0x39, 0x49"; + Log3 ($hash, 1, $msg); return $msg; } - # 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 => hex($address), - busmode => 'i2c', - ); - Log3 $name, 3, "I2C_TSL2561_Define device created"; - - # Make sure we're actually connected - - my $sensorId = I2C_TSL2561_ReadByte($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_ID); - if ( !($sensorId & 0b00010000) ) { - return $name . ': Error! I2C failure: Please check your i2c bus ' . $dev . ' and the connected device address: ' . $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, 'sensorId ' . $hash->{sensorType}; - - $hash->{tsl2561IntegrationTime} = TSL2561_INTEGRATIONTIME_13MS; - $hash->{tsl2561Gain} = TSL2561_GAIN_1X; - - I2C_TSL2561_SetIntegrationTime($hash,TSL2561_INTEGRATIONTIME_13MS); - I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X); - readingsSingleUpdate($hash, 'state', 'Initialized',1); - } else { - my @groups = split '\s', $(; - return "$name :Error! $dev isn't readable/writable by user " . getpwuid( $< ) . " or group(s) " . - getgrgid($_) . " " foreach(@groups); + # 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; } - - } else { - return $name . ': Error! I2C device not found: ' . $dev . '. Please check that these kernelmodules are loaded: i2c_bcm2708, i2c_dev'; } - Log3 $name, 5, "I2C_TSL2561_Define end"; + # preset 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->{tsl2561AutoGain})) { + $hash->{tsl2561AutoGain} = AttrVal($name, 'autoGain', 1); + } + + readingsSingleUpdate($hash, 'state', 'Defined', 1); + + eval { + I2C_TSL2561_Init($hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); + }; + Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_TSL2561_Catch($@)) if $@;; + + Log3 $name, 5, "I2C_TSL2561_Define end"; + return undef; +} + +sub I2C_TSL2561_Init($$) { + my ($hash, $args) = @_; + my $name = $hash->{NAME}; + + if ($hash->{HiPi_used}) { + # check for existing i2c device + my $i2cModulesLoaded = 0; + my $dev = shift @$args; + $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); + } + + readingsSingleUpdate($hash, 'state', 'Initialized', 1); + + return undef; +} + +sub I2C_TSL2561_Catch($) { + my $exception = shift; + if ($exception) { + $exception =~ /^(.*)( at.*FHEM.*)$/; + return $1; + } return undef; } @@ -367,17 +435,17 @@ sub I2C_TSL2561_Attr (@) { my $hash = $defs{$name}; my $msg = ''; - Log3 $name, 5, "I2C_TSL2561_Attr: attr " . $attr . " val " . $val; + Log3 $name, 5, "I2C_TSL2561_Attr: attr " . $attr . " val " . defined($val)? $val : "undef"; if ($attr eq 'poll_interval') { my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; if ($val > 0) { RemoveInternalTimer($hash); InternalTimer(1, 'I2C_TSL2561_Poll', $hash, 0); - } else { + } elsif (defined($val)) { $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0'; } - } elsif ($attr eq 'gain') { + } elsif ($attr eq 'gain') { my $gain = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; Log3 $name, 5, "attr gain is" . $gain; @@ -385,7 +453,7 @@ sub I2C_TSL2561_Attr (@) { I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X); } elsif ($gain == 16) { I2C_TSL2561_SetGain($hash, TSL2561_GAIN_16X); - } else { + } elsif (defined($val)) { $msg = 'Wrong gain defined. must be 1 or 16'; } } elsif ($attr eq 'integrationTime') { @@ -397,13 +465,18 @@ sub I2C_TSL2561_Attr (@) { I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_101MS); } elsif ($time == 402) { I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_402MS); - } else { + } 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->{tsl2561AutoGain} = $autoGain; + if (!$autoGain) { + I2C_TSL2561_Attr($hash, $name, 'gain', AttrVal($name, 'gain', 1)); + } + } elsif ($attr eq 'floatArithmetics') { + my $floatArithmetics = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; } return ($msg) ? $msg : undef; @@ -422,10 +495,12 @@ sub I2C_TSL2561_Poll($) { my ($hash) = @_; my $name = $hash->{NAME}; - # Read values + # Read new values I2C_TSL2561_Get($hash); + # Schedule next polling my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); + Log3 $name, 5, "I2C_TSL2561_Poll: $pollInterval min"; if ($pollInterval > 0) { InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_TSL2561_Poll', $hash, 0); } @@ -444,16 +519,32 @@ sub I2C_TSL2561_Poll($) { sub I2C_TSL2561_Get($) { my ( $hash ) = @_; my $name = $hash->{NAME}; - - my $lux = I2C_TSL2561_CalculateLux($hash); - readingsBeginUpdate($hash); - readingsBulkUpdate($hash,"luminosity",$lux); - readingsBulkUpdate($hash,"broadband",$hash->{broadband}); - readingsBulkUpdate($hash,"ir",$hash->{ir}); - readingsEndUpdate($hash,1); + + Log3 $name, 5, "I2C_TSL2561_Get start"; + + my $state = ReadingsVal($name, 'state', ''); + if ($state eq 'Error') { + # try to turn off the device to check I2C communication (hotplug and error recovery) + if (I2C_TSL2561_Disable($hash)) { + $state = 'Initialized'; + readingsSingleUpdate($hash, 'state', $state, 1); + } + } + if ($state ne 'Error') { + # read from TSL2561 and calculate luminosity + my $lux = I2C_TSL2561_CalculateLux($hash); + $state = ReadingsVal($name, 'state', ''); + if ($state eq 'Initialized') { + my $chScale = I2C_TSL2561_GetChannelScale($hash); + readingsBeginUpdate($hash); + readingsBulkUpdate($hash, "broadband", ceil($chScale*$hash->{broadband})); + readingsBulkUpdate($hash, "ir", ceil($chScale*$hash->{ir})); + readingsBulkUpdate($hash, "luminosity", $lux); + readingsEndUpdate($hash, 1); + } + } #readingsSingleUpdate($hash,"failures",ReadingsVal($hash->{NAME},"failures",0)+1,1); - } =head2 I2C_TSL2561_Set @@ -496,72 +587,125 @@ sub I2C_TSL2561_Undef($$) { my ($hash, $arg) = @_; RemoveInternalTimer($hash); - $hash->{devTSL2561}->close( ). + if ($hash->{HiPi_used}) { + $hash->{devTSL2561}->close() + } + return undef; } -=head2 I2C_TSL2561_ReadWord - Title: I2C_TSL2561_ReadWord - Function: Read 2 bytes from i2c device from given register. - Returns: number - Args: named arguments: - -argument1 => hash: $hash hash of device - -argument2 => number: $register - -=cut - -sub I2C_TSL2561_ReadWord($$) { - my ($hash, $register) = @_; +sub I2C_TSL2561_I2CRcvControl($$) { + my ($hash, $control) = @_; my $name = $hash->{NAME}; - my $retVal = undef; - - try { - my @values = $hash->{devTSL2561}->bus_read($register, 2); - - $retVal = $values[0] | $values[1] << 8; - - } catch Error with { - Log3 $name, 1, 'ERROR: I2C_TSL2561_ReadWord: i2c-bus_read failure'; - }; + my $enabled = $control & 0x3; + if ($enabled == TSL2561_CONTROL_POWERON) { + Log3 $name, 5, "I2C_TSL2561_Enable: is enabled"; + $hash->{sensorEnabled} = 1; + } else { + Log3 $name, 5, "I2C_TSL2561_Enable: is not enabled"; + readingsSingleUpdate($hash, 'state', 'Error', 1); + $hash->{sensorEnabled} = 0; + } - return $retVal; } -=head2 I2C_TSL2561_ReadByte - Title: I2C_TSL2561_ReadByte - Function: Read 1 byte from i2c device from given register. - Returns: number - Args: named arguments: - -argument1 => hash: $hash hash of device - -argument2 => number: $register - -=cut - -sub I2C_TSL2561_ReadByte($$) { - my ($hash, $register) = @_; +sub I2C_TSL2561_I2CRcvID($$) { + my ($hash, $sensorId) = @_; my $name = $hash->{NAME}; - my $retVal = undef; - - try { - Log3 $name, 5,'I2C_TSL2561_ReadByte: start '; - my @bytes = $hash->{devTSL2561}->bus_read($register, 1); - $retVal = $bytes[0]; - - Log3 $name, 5, 'I2C_TSL2561_ReadByte: ' . $retVal; - } catch Error with { - Log3 $name, 1, 'ERROR: I2C_TSL2561_ReadByte: i2c-bus_read failure'; - }; + if ( !($sensorId & 0b00010000) ) { + return $name . ': Error! I2C failure: Please check your i2c bus and the connected device address: ' . $hash->{I2C_Address}; + } - return $retVal; + 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, 'sensorId ' . $hash->{sensorType}; } +sub I2C_TSL2561_I2CRcvTiming ($$) { + my ($hash, $timing) = @_; + + $hash->{tsl2561IntegrationTime} = $timing & 0x03; + $hash->{tsl2561Gain} = $timing & 0x10; + + my $name = $hash->{NAME}; + Log3 $name, 5, "I2C_TSL2561_I2CRcvTiming: $timing, $hash->{tsl2561IntegrationTime}, $hash->{tsl2561Gain}"; +} + +sub I2C_TSL2561_I2CRcvChan0 ($$) { + my ($hash, $broadband) = @_; + + my $name = $hash->{NAME}; + Log3 $name, 5, 'I2C_TSL2561_I2CRcvChan0 ' . $broadband; + + $hash->{broadband} = $broadband; +} + +sub I2C_TSL2561_I2CRcvChan1 ($$) { + my ($hash, $ir) = @_; + + my $name = $hash->{NAME}; + Log3 $name, 5, 'I2C_TSL2561_I2CRcvChan1 ' . $ir; + + $hash->{ir} = $ir; +} + +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"; + } + } + } +} =head2 I2C_TSL2561_Enable Title: I2C_TSL2561_Enable Function: Enables the device - Returns: - + Returns: 1 if sensor was enabled, 0 if enabling sensor failed Args: named arguments: -argument1 => hash: $hash hash of device @@ -570,29 +714,33 @@ sub I2C_TSL2561_ReadByte($$) { sub I2C_TSL2561_Enable($) { my ($hash) = @_; my $name = $hash->{NAME}; - my $enabled = 0; Log3 $name, 5, 'I2C_TSL2561_Enable: start '; - try { - $hash->{devTSL2561}->bus_write( TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON ); - $enabled = I2C_TSL2561_ReadByte( $hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL); - if ($enabled == TSL2561_CONTROL_POWERON) { - Log3 $name, 5, "I2C_TSL2561_Enable: is enabled"; - } else { - Log3 $name, 5, "I2C_TSL2561_Enable: is not enabled"; - readingsSingleUpdate($hash, 'state', 'Error',1); + + # Detect TLS2561 package type and init integration time and gain + if (!defined($hash->{tsl2561Package})) { + # Get TLS2561 package type + if (I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_ID, 1)) { + # Preset integration time and gain + I2C_TSL2561_SetGain($hash, $hash->{tsl2561Gain}); } - } catch Error with { - Log3 $name, 1, 'ERROR: I2C_TSL2561_Enable: i2c-bus_write failure'; - }; + } + + # Enable TLS2561 + $hash->{sensorEnabled} = 0; + if (I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON)) { + I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, 1); + } + Log3 $name, 5, 'I2C_TSL2561_Enable: end '; - return $enabled; + + return $hash->{sensorEnabled}; } =head2 I2C_TSL2561_Disable Title: I2C_TSL2561_Disable Function: Enables the device - Returns: - + Returns: 1 if write was successful, 0 if write failed Args: named arguments: -argument1 => hash: $hash hash of device @@ -601,15 +749,13 @@ sub I2C_TSL2561_Enable($) { sub I2C_TSL2561_Disable($) { my ($hash) = @_; my $name = $hash->{NAME}; - Log3 $name, 5, 'I2C_TSL2561_Disable: start '; - try { - $hash->{devTSL2561}->bus_write( (TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWEROFF) ); - } catch Error with { - Log (1, $name . ': ERROR: I2C_TSL2561_Disable: i2c-bus_write failure'); - } + my $success = I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWEROFF); + $hash->{sensorEnabled} = 0; Log3 $name, 5, 'I2C_TSL2561_Disable: end '; + + return $success; } =head2 I2C_TSL2561_GetData @@ -626,25 +772,27 @@ sub I2C_TSL2561_GetData($) { my $name = $hash->{NAME}; # Enable the device by setting the control bit to 0x03 - I2C_TSL2561_Enable( $hash ); + if (I2C_TSL2561_Enable($hash)) { - # Wait x ms for ADC to complete - if ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS) { - usleep(14000); # 14ms - } elsif ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS) { - usleep(102000); # 102ms - } else { - usleep(403000); # 403ms + # Wait x ms for ADC to complete + if ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS) { + usleep(14000); # 14ms + } elsif ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS) { + usleep(102000); # 102ms + } else { + usleep(403000); # 403ms + } + + # Reads a two byte value from channel 0 (visible + infrared) + if (I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN0_LOW, 2)) { + + # Reads a two byte value from channel 1 (infrared) + I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN1_LOW, 2); + } } - # Reads a two byte value from channel 0 (visible + infrared) - $hash->{broadband} = I2C_TSL2561_ReadWord($hash, TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN0_LOW); - - # Reads a two byte value from channel 1 (infrared) - $hash->{ir} = I2C_TSL2561_ReadWord($hash, TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN1_LOW); - - # Turn the device off to save power - I2C_TSL2561_Disable( $hash ); + # Turn the device off to save power + I2C_TSL2561_Disable($hash); } =head2 I2C_TSL2561_SetIntegrationTime @@ -662,21 +810,18 @@ sub I2C_TSL2561_SetIntegrationTime($$) { my $name = $hash->{NAME}; # Enable the device by setting the control bit to 0x03 - I2C_TSL2561_Enable( $hash ); + if (I2C_TSL2561_Enable($hash)) { - #try { - # Update the timing register - Log3 $name, 5, "I2C_TSL2561_SetIntegrationTime: time is " . $time ; - Log3 $name, 5, "I2C_TSL2561_SetIntegrationTime: gain is " . $hash->{tsl2561Gain}; - $hash->{devTSL2561}->bus_write( (TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, $time | $hash->{tsl2561Gain}) ); - #} catch Error with { - # Log3 $name, 1, 'ERROR: I2C_TSL2561_SetIntegrationTime: i2c-bus_write failure'; - #} - # Update value placeholders - $hash->{tsl2561IntegrationTime} = $time; - - # Turn the device off to save power - I2C_TSL2561_Disable( $hash ); + # Update the timing register + Log3 $name, 5, "I2C_TSL2561_SetIntegrationTime: time " . $time ; + Log3 $name, 5, "I2C_TSL2561_SetIntegrationTime: gain " . $hash->{tsl2561Gain}; + if (I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, $time | $hash->{tsl2561Gain})) { + I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, 1); + } + } + + # Turn the device off to save power + I2C_TSL2561_Disable($hash); } =head2 I2C_TSL2561_SetGain @@ -692,29 +837,19 @@ sub I2C_TSL2561_SetIntegrationTime($$) { sub I2C_TSL2561_SetGain($$) { my ($hash, $gain) = @_; my $name = $hash->{NAME}; - my $timing = 0; - - # Enable the device by setting the control bit to 0x03 - I2C_TSL2561_Enable( $hash ); - #try { - Log3 $name, 5, 'I2C_TSL2561_SetGain: gain is ' . $gain; - Log3 $name, 5, 'I2C_TSL2561_SetGain: time is ' . $hash->{tsl2561IntegrationTime}; - $timing = $gain | $hash->{tsl2561IntegrationTime}; - Log3 $name, 5, ' I2C_TSL2561_SetGain: timing is ' . sprintf("%x", $timing); - # Update the timing register - my $ret = $hash->{devTSL2561}->bus_write( (TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, $timing) ); - Log3 $name, 5, "I2C_TSL2561_SetGain: return from write is " . sprintf("%x", $ret); - $ret = I2C_TSL2561_ReadByte($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING ); - Log3 $name, 5, "I2C_TSL2561_SetGain: register read is " . sprintf("%x", $ret); - #} catch Error with { - # Log3 $name, 1,'ERROR: I2C_TSL2561_SetGain: i2c-bus_write failure'; - #} - # Update value placeholders - $hash->{tsl2561Gain} = $gain; - - # Turn the device off to save power - I2C_TSL2561_Disable( $hash ); + # Enable the device by setting the control bit to 0x03 + if (I2C_TSL2561_Enable($hash)) { + # Update the timing register + Log3 $name, 5, "I2C_TSL2561_SetGain: gain " . $gain ; + Log3 $name, 5, "I2C_TSL2561_SetGain: time " . $hash->{tsl2561IntegrationTime}; + if (I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, $gain | $hash->{tsl2561IntegrationTime})) { + I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, 1); + } + } + + # Turn the device off to save power + I2C_TSL2561_Disable($hash); } =head2 I2C_TSL2561_GetLuminosity @@ -730,65 +865,106 @@ sub I2C_TSL2561_GetLuminosity($) { my ($hash) = @_; my $name = $hash->{NAME}; - my $valid = 0; - - # If Auto gain disabled get a single reading and continue - if ($hash->{tsl2561AutoGain}) { + # If Auto gain disabled get a single reading and continue + if (!$hash->{tsl2561AutoGain}) { I2C_TSL2561_GetData($hash); return; } + + # Read data until we find a valid range + my $agcCheck = 0; + my $hi = 0; + my $lo = 0; + my $it = 0; + my $lux = 0; + my $valid = 0; + while (!$valid) { + $it = $hash->{tsl2561IntegrationTime}; - # Read data until we find a valid range - my $agcCheck = 0; - my $hi = 0; - my $lo = 0; - my $it = 0; - my $lux = 0; - while (!$valid) { - $it = $hash->{tsl2561IntegrationTime}; + # Get the hi/low threshold for the current integration time + 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; + } else { + $hi = TSL2561_AGC_THI_402MS; + $lo = TSL2561_AGC_TLO_402MS; + } - # Get the hi/low threshold for the current integration time - 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; - } else { - $hi = TSL2561_AGC_THI_402MS; - $lo = TSL2561_AGC_TLO_402MS; - } + I2C_TSL2561_GetData($hash); - I2C_TSL2561_GetData($hash); + # Run an auto-gain check if we haven't already done so ... + if (!$agcCheck) { + if (($hash->{broadband} < $lo) && ($hash->{tsl2561Gain} == TSL2561_GAIN_1X)) { + # Increase the gain and try again + I2C_TSL2561_SetGain($hash, TSL2561_GAIN_16X); + # Drop the previous conversion results + I2C_TSL2561_GetData($hash); + # Set a flag to indicate we've adjusted the gain + $agcCheck = 1; + } elsif (($hash->{broadband} > $hi) && ($hash->{tsl2561Gain} == TSL2561_GAIN_16X)) { + # Drop gain to 1x and try again + I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X); + # Drop the previous conversion results + I2C_TSL2561_GetData($hash); + # Set a flag to indicate we've adjusted the gain + $agcCheck = 1; + } else { + # Nothing to look at here, keep moving .... + # Reading is either valid, or we're already at the chips limits + $valid = 1; + } + } else { + # If we've already adjusted the gain once, just return the new results. + # This avoids endless loops where a value is at one extreme pre-gain, + # and the the other extreme post-gain + $valid = 1; + } + } +} - # Run an auto-gain check if we haven't already done so ... - if ($agcCheck) { - if (($hash->{broadband} < $lo) && ($hash->{tsl2561Gain} == TSL2561_GAIN_1X)) { - # Increase the gain and try again - I2C_TSL2561_SetGain($hash, TSL2561_GAIN_16X); - # Drop the previous conversion results - I2C_TSL2561_GetData($hash); - # Set a flag to indicate we've adjusted the gain - $agcCheck = 1; - } elsif (($hash->{broadband} > $hi) && ($hash->{tsl2561Gain} == TSL2561_GAIN_16X)) { - # Drop gain to 1x and try again - I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X); - # Drop the previous conversion results - I2C_TSL2561_GetData($hash); - # Set a flag to indicate we've adjusted the gain - $agcCheck = 1; - } else { - # Nothing to look at here, keep moving .... - # Reading is either valid, or we're already at the chips limits - $valid = 1; - } - } else { - # If we've already adjusted the gain once, just return the new results. - # This avoids endless loops where a value is at one extreme pre-gain, - # and the the other extreme post-gain - $valid = 1; - } - } +# 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 (!$hash->{tsl2561Gain}) { + $chScale = $chScale << 4; + } + } + + return $chScale; } =head2 I2C_TSL2561_CalculateLux @@ -802,126 +978,242 @@ sub I2C_TSL2561_GetLuminosity($) { sub I2C_TSL2561_CalculateLux($) { my ($hash) = @_; - my $clipThreshold = 0; - my $chScale = 0; - - I2C_TSL2561_GetLuminosity($hash); - # Make sure the sensor isn't saturated! - 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 $name = $hash->{NAME}; - # Return 0 lux if the sensor is saturated - if (($hash->{broadband} > $clipThreshold) || ($hash->{ir} > $clipThreshold)) { - readingsSingleUpdate($hash, 'state', 'Saturated',1); - return 0; - } + I2C_TSL2561_GetLuminosity($hash); - # 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); - } + # Make sure the sensor isn't saturated! + 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; + } - # Scale for gain (1x or 16x) - if (!$hash->{tsl2561Gain}) { - $chScale = $chScale << 4; - } + # Return 0 lux if the sensor is saturated + if (($hash->{broadband} > $clipThreshold) || ($hash->{ir} > $clipThreshold)) { + readingsSingleUpdate($hash, 'state', 'Saturated', 1); + return 0; + } else { + readingsSingleUpdate($hash, 'state', 'Initialized', 1); + } - # Scale the channel values - my $channel0 = ($hash->{broadband} * $chScale) >> TSL2561_LUX_CHSCALE; - my $channel1 = ($hash->{ir} * $chScale) >> TSL2561_LUX_CHSCALE; + # 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 $ratio1 = 0; - if ($channel0 != 0) { - $ratio1 = ($channel1 << (TSL2561_LUX_RATIOSCALE+1)) / $channel0; - } + # 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*$channel1*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*$channel1*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; - # round the ratio value - my $ratio = ($ratio1 + 1) >> 1; - - my $b=0; - my $m=0; + # Find the ratio of the channel values (Channel1/Channel0) + my $ratio1 = 0; + if ($channel0 != 0) { + $ratio1 = ($channel1 << (TSL2561_LUX_RATIOSCALE+1)) / $channel0; + } - 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; - } - } + # round the ratio value + my $ratio = ($ratio1 + 1) >> 1; + + my $b=0; + my $m=0; - my $temp = (($channel0 * $b) - ($channel1 * $m)); + 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; + } + } - # Do not allow negative lux value - if ($temp < 0) { - $temp = 0; - } + my $temp = (($channel0 * $b) - ($channel1 * $m)); - # Round lsb (2^(LUX_SCALE-1)) - $temp += (1 << (TSL2561_LUX_LUXSCALE-1)); + # Do not allow negative lux value + if ($temp < 0) { + $temp = 0; + } - # Strip off fractional portion - my $lux = $temp >> TSL2561_LUX_LUXSCALE; + # Round lsb (2^(LUX_SCALE-1)) + $temp += (1 << (TSL2561_LUX_LUXSCALE-1)); - # Signal I2C had no errors - return $lux; + # Strip off fractional portion + my $lux = $temp >> TSL2561_LUX_LUXSCALE; + + # Signal I2C had no errors + return $lux; + } + +} + +sub I2C_TSL2561_i2cread($$$) { + my ($hash, $reg, $nbyte) = @_; + my $success = 1; + + 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})) { + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cread", + i2caddress => $hash->{I2C_Address}, + reg => $reg, + nbyte => $nbyte + }); + if ($hash->{$iodev->{NAME}.'_SENDSTAT'} eq 'error') { + $hash->{tsl2561Package} = undef; + readingsSingleUpdate($hash, 'state', 'I2C Error', 1); + $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})) { + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $hash->{I2C_Address}, + reg => $reg, + data => join (' ',@data), + }); + if ($hash->{$iodev->{NAME}.'_SENDSTAT'} eq 'error') { + $hash->{tsl2561Package} = undef; + readingsSingleUpdate($hash, 'state', 'I2C Error', 1); + $success = 0; + } + } else { + Log3 ($hash, 1, $hash->{NAME} . ': ' . "no IODev assigned to '$hash->{NAME}'"); + $success = 0; + } + + return $success; } 1; @@ -932,67 +1224,104 @@ sub I2C_TSL2561_CalculateLux($) {

I2C_TSL2561

- =end html