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;
}
# 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);
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);
}
} 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";
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 (undef, $name, $attr, $val) = @_;
my $hash = $defs{$name};
my $msg = '';
Log3 $name, 5, "I2C_TSL2561_Attr: attr " . $attr . " val " . $val;
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 {
$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, "attr gain is" . $gain;
if ($gain == 1) {
I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X);
} elsif ($gain == 16) {
I2C_TSL2561_SetGain($hash, TSL2561_GAIN_16X);
} else {
$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);
} else {
$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;
}
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};
# Read values
I2C_TSL2561_Get($hash);
my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
if ($pollInterval > 0) {
InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_TSL2561_Poll', $hash, 0);
}
}
=head2 I2C_TSL2561_Get
Title: I2C_TSL2561_Get
Function: Implements GetFn function.
Returns: string|undef
Args: named arguments:
-argument1 => hash: $hash hash of device
-argument2 => array: @a argument array
=cut
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);
#readingsSingleUpdate($hash,"failures",ReadingsVal($hash->{NAME},"failures",0)+1,1);
}
=head2 I2C_TSL2561_Set
Title: I2C_TSL2561_Set
Function: Implements SetFn function.
Returns: string|undef
Args: named arguments:
-argument1 => hash: $hash hash of device
-argument2 => array: @a argument array
=cut
sub I2C_TSL2561_Set($@) {
my ($hash, @a) = @_;
my $name =$a[0];
my $cmd = $a[1];
my $val = $a[2];
if(!defined($sets{$cmd})) {
return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets)
}
if ($cmd eq 'readValues') {
}
}
=head2 I2C_TSL2561_Undef
Title: I2C_TSL2561_Undef
Function: Implements UndefFn function.
Returns: undef
Args: named arguments:
-argument1 => hash: $hash hash of device
-argument2 => array: @a argument array
=cut
sub I2C_TSL2561_Undef($$) {
my ($hash, $arg) = @_;
RemoveInternalTimer($hash);
$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) = @_;
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';
};
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) = @_;
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';
};
return $retVal;
}
=head2 I2C_TSL2561_Enable
Title: I2C_TSL2561_Enable
Function: Enables the device
Returns: -
Args: named arguments:
-argument1 => hash: $hash hash of device
=cut
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);
}
} catch Error with {
Log3 $name, 1, 'ERROR: I2C_TSL2561_Enable: i2c-bus_write failure';
};
Log3 $name, 5, 'I2C_TSL2561_Enable: end ';
return $enabled;
}
=head2 I2C_TSL2561_Disable
Title: I2C_TSL2561_Disable
Function: Enables the device
Returns: -
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 ';
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');
}
Log3 $name, 5, 'I2C_TSL2561_Disable: end ';
}
=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};
# Enable the device by setting the control bit to 0x03
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
}
# 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 );
}
=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};
# Enable the device by setting the control bit to 0x03
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 );
}
=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};
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 );
}
=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
Returns: luminosity
Args: named arguments:
-argument1 => hash: $hash hash of device
=cut
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}) {
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;
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;
}
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;
}
}
}
=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 $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;
}
# Return 0 lux if the sensor is saturated
if (($hash->{broadband} > $clipThreshold) || ($hash->{ir} > $clipThreshold)) {
readingsSingleUpdate($hash, 'state', 'Saturated',1);
return 0;
}
# 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;
}
# 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;
}
1;
=pod
=begin html
I2C_TSL2561
With this module you can read values from the digital luminosity sensor TSL2561
via the i2c bus on Raspberry Pi.
Before you can use the module on the Raspberry Pi you must load the I2C kernel
modules.
Add these two lines to your /etc/modules file to load the kernel modules
automatically when booting your Raspberry Pi.
i2c-bcm2708
i2c-dev
Please note:
For the i2c communication, the perl modules HiPi::Device::I2C
are required.
For a simple automated installation:
wget http://raspberry.znix.com/hipifiles/hipi-install
perl hipi-install
Define
define TSL2561 I2C_TSL2561 <I2C device> <I2C address>
Examples:
define TSL2561 I2C_TSL2561 /dev/i2c-0 0x39
attr TSL2561 poll_interval 5
Attributes
- poll_interval
Set the polling interval in minutes to query the sensor for new measured
values.
Default: 5, valid values: 1, 2, 5, 10, 20, 30
- integrationTime
time in ms the sensor takes to measure the light.
Default: 13, valid values: 13, 101, 402
see this tutorial
for more details
- gain
gain factor
Default: 1, valid values: 1, 16
- autoGain
enable auto gain
Default: 1, valid values: 0, 1
if set to 1,the gain parameter is set automatically depending on light conditions
=end html
=cut