51_I2C_TSL2561.pm: support for non-blocking I2C IO (e.g. Firmata)

git-svn-id: https://svn.fhem.de/fhem/trunk@10443 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
jnsbyr 2016-01-10 14:50:08 +00:00
parent c96f7e84d6
commit b0037fdb1f
3 changed files with 380 additions and 272 deletions

View File

@ -1,5 +1,7 @@
# Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # 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. # Do not insert empty lines here, update check depends on it.
- feature: 51_I2C_TSL2561: support non-blocking I2C-IO, especially when
using Firmata over LAN
- feature: FB_CALLLIST: new attribute answMachine-is-missed-call to treat - feature: FB_CALLLIST: new attribute answMachine-is-missed-call to treat
incoming calls, which were taken by an answering incoming calls, which were taken by an answering
machine, as "missed call". Only relevant for machine, as "missed call". Only relevant for

View File

@ -74,14 +74,22 @@
I2C auto address mode added for IODev to compensate floating address selection I2C auto address mode added for IODev to compensate floating address selection
improved I2C read error handling for RPII2C IODev improved I2C read error handling for RPII2C IODev
16.04.2015 jensb 16.04.2015 jensb
make scaling of readings 'broadband' and 'ir' dependend on new attribute 'normalizeRawValues' make scaling of readings 'broadband' and 'ir' depended on new attribute 'normalizeRawValues'
18.04.2015 jensb 18.04.2015 jensb
new readings 'gain' and 'integrationTime' new readings 'gain' and 'integrationTime'
20.04.2015 jensb 20.04.2015 jensb
update reading 'state' in bulk along with luminusity when toggling between 'Initialized' and 'Saturated' 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
=head1 TODO =head1 TODO
HiPi, FRM and NetzerI2C I2C error detection (optional)
manual integration time (optional) manual integration time (optional)
=head1 CREDITS =head1 CREDITS
@ -131,7 +139,7 @@ use constant {
TSL2561_REGISTER_CHAN1_LOW => 0x0E, TSL2561_REGISTER_CHAN1_LOW => 0x0E,
TSL2561_REGISTER_CHAN1_HIGH => 0x0F, TSL2561_REGISTER_CHAN1_HIGH => 0x0F,
# I2C values # I2C register values
TSL2561_COMMAND_BIT => 0x80, # Must be 1, TSL2561_COMMAND_BIT => 0x80, # Must be 1,
TSL2561_CLEAR_BIT => 0x40, # Clears any pending interrupt (write 1 to clear) TSL2561_CLEAR_BIT => 0x40, # Clears any pending interrupt (write 1 to clear)
TSL2561_WORD_BIT => 0x20, # 1 = read/write word (rather than byte) TSL2561_WORD_BIT => 0x20, # 1 = read/write word (rather than byte)
@ -145,8 +153,8 @@ use constant {
TSL2561_INTEGRATIONTIME_13MS => 0x00, # 13.7ms TSL2561_INTEGRATIONTIME_13MS => 0x00, # 13.7ms
TSL2561_INTEGRATIONTIME_101MS => 0x01, # 101ms TSL2561_INTEGRATIONTIME_101MS => 0x01, # 101ms
TSL2561_INTEGRATIONTIME_402MS => 0x02, # 402ms TSL2561_INTEGRATIONTIME_402MS => 0x02, # 402ms
TSL2561_INTEGRATIONTIME_MANUAL_STOP => 0x03, # stop manual integration cycle TSL2561_INTEGRATIONTIME_MANUAL_STOP => 0x03, # stop manual integration cycle (not implemented)
TSL2561_INTEGRATIONTIME_MANUAL_START => 0x0b, # start manual integration cycle TSL2561_INTEGRATIONTIME_MANUAL_START => 0x0b, # start manual integration cycle (not implemented)
# Auto-gain thresholds # Auto-gain thresholds
TSL2561_AGC_THI_13MS => 4850, # Max value at Ti 13.7ms = 5047, TSL2561_AGC_THI_13MS => 4850, # Max value at Ti 13.7ms = 5047,
@ -220,28 +228,30 @@ use constant {
TSL2561_LUX_B8C =>0x0000, # 0.000 * 2^LUX_SCALE TSL2561_LUX_B8C =>0x0000, # 0.000 * 2^LUX_SCALE
TSL2561_LUX_M8C =>0x0000, # 0.000 * 2^LUX_SCALE TSL2561_LUX_M8C =>0x0000, # 0.000 * 2^LUX_SCALE
TSL2561_VISIBLE =>2, # channel 0 - channel 1 TSL2561_STATE_UNDEFINED => 'Undefined',
TSL2561_INFRARED =>1, # channel 1 TSL2561_STATE_DEFINED => 'Defined',
TSL2561_FULLSPECTRUM =>0, # channel 0 TSL2561_STATE_INITIALIZED => 'Initialized',
TSL2561_STATE_SATURATED => 'Saturated',
TSL2561_STATE_I2C_ERROR => 'I2C Error',
TSL2561_STATE_DISABLED => 'Disabled',
STATE_UNDEFINED => 'Undefined', TSL2561_ACQUI_STATE_IDLE => 0,
STATE_DEFINED => 'Defined', TSL2561_ACQUI_STATE_SETUP => 1,
STATE_INITIALIZED => 'Initialized', TSL2561_ACQUI_STATE_ENABLE_REQUESTED => 2,
STATE_SATURATED => 'Saturated', TSL2561_ACQUI_STATE_ENABLED => 3,
STATE_I2C_ERROR => 'I2C Error', TSL2561_ACQUI_STATE_DATA_AVAILABLE => 4,
STATE_DISABLED => 'Disabled', 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,
ACQUI_STATE_DISABLED => 0, TSL2561_CALC_STATE_IDLE => 0,
ACQUI_STATE_ENABLED => 1, TSL2561_CALC_STATE_DATA_REQUESTED => 1,
ACQUI_STATE_DATA_AVAILABLE => 2, TSL2561_CALC_STATE_DATA_RECEIVED => 2,
ACQUI_STATE_DATA_CH0_RECEIVED => 3, TSL2561_CALC_STATE_ERROR => 3,
ACQUI_STATE_DATA_CH1_RECEIVED => 4, TSL2561_CALC_STATE_COMPLETED => 4,
ACQUI_STATE_ERROR => 5,
CALC_STATE_IDLE => 0, TSL2561_MAX_CONSECUTIVE_OPERATIONS => 20,
CALC_STATE_DATA_REQUESTED => 1,
CALC_STATE_DATA_RECEIVED => 2,
CALC_STATE_ERROR => 3,
}; };
################################################## ##################################################
@ -257,6 +267,7 @@ sub I2C_TSL2561_Undef($$);
sub I2C_TSL2561_Enable($); sub I2C_TSL2561_Enable($);
sub I2C_TSL2561_Disable($); sub I2C_TSL2561_Disable($);
sub I2C_TSL2561_GetData($); sub I2C_TSL2561_GetData($);
sub I2C_TSL2561_SetTimingRegister($);
sub I2C_TSL2561_SetIntegrationTime($$); sub I2C_TSL2561_SetIntegrationTime($$);
sub I2C_TSL2561_SetGain($$); sub I2C_TSL2561_SetGain($$);
sub I2C_TSL2561_GetLuminosity($); sub I2C_TSL2561_GetLuminosity($);
@ -300,6 +311,7 @@ sub I2C_TSL2561_Initialize($) {
$libcheck_hasHiPi = 0 if($@); $libcheck_hasHiPi = 0 if($@);
$hash->{DefFn} = 'I2C_TSL2561_Define'; $hash->{DefFn} = 'I2C_TSL2561_Define';
$hash->{InitFn} = 'I2C_TSL2561_Init';
$hash->{AttrFn} = 'I2C_TSL2561_Attr'; $hash->{AttrFn} = 'I2C_TSL2561_Attr';
$hash->{SetFn} = 'I2C_TSL2561_Set'; $hash->{SetFn} = 'I2C_TSL2561_Set';
$hash->{UndefFn} = 'I2C_TSL2561_Undef'; $hash->{UndefFn} = 'I2C_TSL2561_Undef';
@ -319,7 +331,7 @@ sub I2C_TSL2561_Define($$) {
my $name = $a[0]; my $name = $a[0];
my $device; my $device;
readingsSingleUpdate($hash, 'state', STATE_UNDEFINED, 1); readingsSingleUpdate($hash, 'state', TSL2561_STATE_UNDEFINED, 1);
Log3 $name, 1, "I2C_TSL2561_Define start: " . @a . "/" . join(' ', @a); Log3 $name, 1, "I2C_TSL2561_Define start: " . @a . "/" . join(' ', @a);
@ -354,16 +366,17 @@ sub I2C_TSL2561_Define($$) {
return $msg; return $msg;
} }
$hash->{I2C_Address} = hex($address); if ($address eq TSL2561_ADDR_AUTO) {
if (!$hash->{HiPi_used}) { # start with lowest address in auto mode
if ($address eq TSL2561_ADDR_AUTO) { $hash->{autoAddress} = 1;
# start with lowest address in auto mode $address = TSL2561_ADDR_LOW;
$hash->{autoAddress} = 1; } else {
$address = TSL2561_ADDR_LOW; $hash->{autoAddress} = 0;
} else {
$hash->{autoAddress} = 0;
}
} }
if ($hash->{HiPi_used}) {
$hash->{autoAddress} = 0;
}
$hash->{I2C_Address} = hex($address);
# create default attributes # create default attributes
if (AttrVal($name, 'poll_interval', '?') eq '?') { if (AttrVal($name, 'poll_interval', '?') eq '?') {
@ -391,18 +404,26 @@ sub I2C_TSL2561_Define($$) {
$hash->{tsl2561Gain} = $attrVal == 16? TSL2561_GAIN_16X : TSL2561_GAIN_1X; $hash->{tsl2561Gain} = $attrVal == 16? TSL2561_GAIN_16X : TSL2561_GAIN_1X;
} }
if (!defined($hash->{acquiState})) { if (!defined($hash->{acquiState})) {
$hash->{acquiState} = ACQUI_STATE_DISABLED; $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
} }
if (!defined($hash->{calcState})) { if (!defined($hash->{calcState})) {
$hash->{calcState} = CALC_STATE_IDLE; $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
}
if (!defined($hash->{operationCounter})) {
$hash->{operationCounter} = 0;
}
if (!defined($hash->{blockingIO})) {
$hash->{blockingIO} = 0;
} }
readingsSingleUpdate($hash, 'state', STATE_DEFINED, 1); readingsSingleUpdate($hash, 'state', TSL2561_STATE_DEFINED, 1);
eval { if ($main::init_done || $hash->{HiPi_used}) {
I2C_TSL2561_Init($hash, $device); eval {
}; I2C_TSL2561_Init($hash, [ $device ]);
Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_TSL2561_Catch($@)) if $@;; };
Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_TSL2561_Catch($@)) if $@;;
}
Log3 $name, 5, "I2C_TSL2561_Define end"; Log3 $name, 5, "I2C_TSL2561_Define end";
return undef; return undef;
@ -436,7 +457,12 @@ sub I2C_TSL2561_Init($$) {
AssignIoPort($hash); AssignIoPort($hash);
} }
readingsSingleUpdate($hash, 'state', STATE_INITIALIZED, 1); # 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; return undef;
} }
@ -467,8 +493,8 @@ sub I2C_TSL2561_Attr (@) {
Log3 $name, 5, "I2C_TSL2561_Attr: start cmd=$cmd attr=$attr"; Log3 $name, 5, "I2C_TSL2561_Attr: start cmd=$cmd attr=$attr";
if ($attr eq 'poll_interval') { if ($attr eq 'poll_interval') {
my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
if ($val > 0) { if ($val > 0) {
# start new measurement cycle
RemoveInternalTimer($hash); RemoveInternalTimer($hash);
InternalTimer(gettimeofday() + 1, 'I2C_TSL2561_Poll', $hash, 0); InternalTimer(gettimeofday() + 1, 'I2C_TSL2561_Poll', $hash, 0);
} elsif (defined($val)) { } elsif (defined($val)) {
@ -499,16 +525,10 @@ sub I2C_TSL2561_Attr (@) {
} }
} elsif ($attr eq 'autoGain') { } elsif ($attr eq 'autoGain') {
my $autoGain = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; my $autoGain = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
$hash->{timingModified} = 1;
if (!$autoGain) {
I2C_TSL2561_Attr($hash, $name, 'gain', AttrVal($name, 'gain', 1));
}
} elsif ($attr eq 'autoIntegrationTime') { } elsif ($attr eq 'autoIntegrationTime') {
my $autoIntegrationTime = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; my $autoIntegrationTime = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
$hash->{timingModified} = 1;
if (!$autoIntegrationTime) {
I2C_TSL2561_Attr($hash, $name, 'integrationTime', AttrVal($name, 'integrationTime', 13));
}
} elsif ($attr eq 'normalizeRawValues') { } elsif ($attr eq 'normalizeRawValues') {
my $normalizeRawValues = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; my $normalizeRawValues = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
} elsif ($attr eq 'floatArithmetics') { } elsif ($attr eq 'floatArithmetics') {
@ -522,71 +542,68 @@ sub I2C_TSL2561_Attr (@) {
=head2 I2C_TSL2561_Poll =head2 I2C_TSL2561_Poll
Title: I2C_TSL2561_Poll Title: I2C_TSL2561_Poll
Function: Start polling the sensor at interval defined in attribute Function: Start polling the sensor at interval defined in attribute
Returns: - Returns: -
Args: named arguments: Args: named arguments:
-argument1 => hash - argument1 => hash
=cut =cut
sub I2C_TSL2561_Poll($) { sub I2C_TSL2561_Poll($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
RemoveInternalTimer($hash);
Log3 $name, 5, "I2C_TSL2561_Poll: start"; Log3 $name, 5, "I2C_TSL2561_Poll: start";
my $pollDelay = 60*AttrVal($hash->{NAME}, 'poll_interval', 0); # seconds polling my $pollDelay = 60*AttrVal($hash->{NAME}, 'poll_interval', 0); # seconds polling
if (!AttrVal($hash->{NAME}, "disable", 0)) { if (!AttrVal($hash->{NAME}, "disable", 0)) {
# Read new values # Request new samples from TSL2561 and calculate luminosity
my $state = ReadingsVal($name, 'state', ''); my $lux = I2C_TSL2561_GetLuminosity($hash);
if ($state eq STATE_I2C_ERROR) { if ($hash->{calcState} == TSL2561_CALC_STATE_DATA_REQUESTED) {
# try to turn off the device to check I2C communication (hotplug and error recovery) # Measurement in progress
if (I2C_TSL2561_Disable($hash)) { if ($hash->{acquiState} == TSL2561_ACQUI_STATE_ENABLED) {
$state = STATE_INITIALIZED; $pollDelay = I2C_TSL2561_GetIntegrationTime($hash) + 0.003; # seconds measurement time
readingsSingleUpdate($hash, 'state', $state, 1); if (!$hash->{blockingIO}) {
} elsif ($hash->{autoAddress}) { $pollDelay += 0.200; # extra time for async transport jitter compensation
# auto address mode, scan bus for device
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);
} }
$pollDelay = 10; # seconds retry delay
}
$hash->{tsl2561Package} = undef;
}
if ($state ne STATE_I2C_ERROR) {
# Request new samples from TSL2561 and calculate luminosity
my $lux = I2C_TSL2561_GetLuminosity($hash);
if ($hash->{calcState} == CALC_STATE_DATA_REQUESTED) {
$pollDelay = I2C_TSL2561_GetIntegrationTime($hash) + 0.001; # seconds integration time
} else { } else {
if ($hash->{calcState} == CALC_STATE_IDLE) { $pollDelay = 0.400; # seconds async I2C read reply timeout
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);
}
if ($state eq STATE_INITIALIZED && $hash->{saturated}) {
readingsBulkUpdate($hash, 'state', STATE_SATURATED, 1);
} elsif ($state eq STATE_SATURATED && !$hash->{saturated}) {
readingsBulkUpdate($hash, 'state', STATE_INITIALIZED, 1);
}
readingsEndUpdate($hash, 1);
}
} }
} 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 { } else {
readingsSingleUpdate($hash, 'state', STATE_DISABLED, 1); readingsSingleUpdate($hash, 'state', TSL2561_STATE_DISABLED, 1);
} }
# Schedule next polling # Schedule next polling
@ -594,6 +611,8 @@ sub I2C_TSL2561_Poll($) {
if ($pollDelay > 0) { if ($pollDelay > 0) {
InternalTimer(gettimeofday() + $pollDelay, 'I2C_TSL2561_Poll', $hash, 0); InternalTimer(gettimeofday() + $pollDelay, 'I2C_TSL2561_Poll', $hash, 0);
} }
return undef;
} }
sub I2C_TSL2561_Set($@) { sub I2C_TSL2561_Set($@) {
@ -606,7 +625,6 @@ sub I2C_TSL2561_Set($@) {
return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets) return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets)
} }
RemoveInternalTimer($hash);
I2C_TSL2561_Poll($hash); I2C_TSL2561_Poll($hash);
return undef; return undef;
} }
@ -632,15 +650,17 @@ sub I2C_TSL2561_I2CRcvControl($$) {
my $enabled = $control & 0x3; my $enabled = $control & 0x3;
if ($enabled == TSL2561_CONTROL_POWERON) { if ($enabled == TSL2561_CONTROL_POWERON) {
Log3 $name, 5, "I2C_TSL2561_I2CRcvControl: is enabled"; Log3 $name, 5, "I2C_TSL2561_I2CRcvControl: is enabled";
$hash->{sensorEnabled} = 1; $hash->{acquiState} = TSL2561_ACQUI_STATE_ENABLED;
$hash->{acquiState} = ACQUI_STATE_ENABLED;
$hash->{acquiStarted} = [gettimeofday]; $hash->{acquiStarted} = [gettimeofday];
} else { } else {
Log3 $name, 5, "I2C_TSL2561_I2CRcvControl: is disabled"; Log3 $name, 5, "I2C_TSL2561_I2CRcvControl: is disabled";
$hash->{sensorEnabled} = 0; $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
$hash->{acquiState} = ACQUI_STATE_DISABLED;
} }
if (!$hash->{blockingIO}) {
I2C_TSL2561_Poll($hash);
}
return undef;
} }
# #
@ -662,8 +682,22 @@ sub I2C_TSL2561_I2CRcvID($$) {
$package = 'T/FN/CL'; $package = 'T/FN/CL';
} }
$hash->{sensorType} = 'TSL2561 Package ' . $package . ' Rev. ' . ( $sensorId & 0x0f ); $hash->{sensorType} = 'TSL2561 Package ' . $package . ' Rev. ' . ( $sensorId & 0x0f );
Log3 $name, 5, 'I2C_TSL2561_I2CRcvID: sensorId ' . $hash->{sensorType}; 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;
} }
# #
@ -675,8 +709,13 @@ sub I2C_TSL2561_I2CRcvTiming ($$) {
$hash->{tsl2561IntegrationTime} = $timing & 0x03; $hash->{tsl2561IntegrationTime} = $timing & 0x03;
$hash->{tsl2561Gain} = $timing & 0x10; $hash->{tsl2561Gain} = $timing & 0x10;
Log3 $name, 5, "I2C_TSL2561_I2CRcvTiming: time $hash->{tsl2561IntegrationTime}, gain $hash->{tsl2561Gain}";
Log3 $name, 5, "I2C_TSL2561_I2CRcvTiming: $timing, $hash->{tsl2561IntegrationTime}, $hash->{tsl2561Gain}"; $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
if (!$hash->{blockingIO}) {
I2C_TSL2561_Poll($hash);
}
return undef;
} }
# #
@ -686,10 +725,11 @@ sub I2C_TSL2561_I2CRcvChan0 ($$) {
my ($hash, $broadband) = @_; my ($hash, $broadband) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
$hash->{broadband} = $broadband;
Log3 $name, 5, 'I2C_TSL2561_I2CRcvChan0 ' . $broadband; Log3 $name, 5, 'I2C_TSL2561_I2CRcvChan0 ' . $broadband;
$hash->{broadband} = $broadband; $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_CH0_RECEIVED;
$hash->{acquiState} = ACQUI_STATE_DATA_CH0_RECEIVED; return undef;
} }
# #
@ -699,10 +739,14 @@ sub I2C_TSL2561_I2CRcvChan1 ($$) {
my ($hash, $ir) = @_; my ($hash, $ir) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
$hash->{ir} = $ir;
Log3 $name, 5, 'I2C_TSL2561_I2CRcvChan1 ' . $ir; Log3 $name, 5, 'I2C_TSL2561_I2CRcvChan1 ' . $ir;
$hash->{ir} = $ir; $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_CH1_RECEIVED;
$hash->{acquiState} = ACQUI_STATE_DATA_CH1_RECEIVED; if (!$hash->{blockingIO}) {
I2C_TSL2561_Poll($hash);
}
return undef;
} }
# #
@ -750,15 +794,15 @@ sub I2C_TSL2561_I2CRec ($$) {
} }
} }
} }
return undef;
} }
=head2 I2C_TSL2561_Enable =head2 I2C_TSL2561_Enable
Title: I2C_TSL2561_Enable Title: I2C_TSL2561_Enable
Function: Enables the device Function: Enables the device
Returns: 1 if sensor was enabled, 0 if enabling sensor failed Returns: 1 if enabling sensor was initiated, 0 if enabling sensor failed
Args: named arguments: Args: named arguments:
-argument1 => hash: $hash hash of device - argument1 => hash: $hash hash of device
=cut =cut
sub I2C_TSL2561_Enable($) { sub I2C_TSL2561_Enable($) {
@ -766,44 +810,21 @@ sub I2C_TSL2561_Enable($) {
my $name = $hash->{NAME}; my $name = $hash->{NAME};
Log3 $name, 5, 'I2C_TSL2561_Enable: start '; Log3 $name, 5, 'I2C_TSL2561_Enable: start ';
my $success = 0;
# Detect TLS2561 package type and init integration time and gain if (I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWERON)) {
my $initialized = 1; $success = I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, 1);
if (!defined($hash->{tsl2561Package})) {
# Get TLS2561 package type
$initialized = 0;
if (I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_ID, 1)) {
# Preset integration time and gain
if (I2C_TSL2561_SetGain($hash, $hash->{tsl2561Gain})) {
$initialized = 1;
}
}
} }
# Enable TLS2561
$hash->{sensorEnabled} = 0;
if ($initialized) {
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);
}
if (!$hash->{sensorEnabled}) {
# Enable failed (no sensor at address or wrong I2C device)
$hash->{tsl2561Package} = undef;
}
}
Log3 $name, 5, 'I2C_TSL2561_Enable: end '; Log3 $name, 5, 'I2C_TSL2561_Enable: end ';
return $hash->{sensorEnabled}; return $success;
} }
=head2 I2C_TSL2561_Disable =head2 I2C_TSL2561_Disable
Title: I2C_TSL2561_Disable Title: I2C_TSL2561_Disable
Function: Enables the device Function: Disables the device
Returns: 1 if write was successful, 0 if write failed Returns: 1 if disabling sensor was initiated, 0 if disabling sensor failed
Args: named arguments: Args: named arguments:
-argument1 => hash: $hash hash of device - argument1 => hash: $hash hash of device
=cut =cut
sub I2C_TSL2561_Disable($) { sub I2C_TSL2561_Disable($) {
@ -812,7 +833,6 @@ sub I2C_TSL2561_Disable($) {
Log3 $name, 5, 'I2C_TSL2561_Disable: start '; Log3 $name, 5, 'I2C_TSL2561_Disable: start ';
my $success = I2C_TSL2561_i2cwrite($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_CONTROL, TSL2561_CONTROL_POWEROFF); 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 '; Log3 $name, 5, 'I2C_TSL2561_Disable: end ';
return $success; return $success;
@ -820,11 +840,10 @@ sub I2C_TSL2561_Disable($) {
=head2 I2C_TSL2561_GetData =head2 I2C_TSL2561_GetData
Title: I2C_TSL2561_GetData Title: I2C_TSL2561_GetData
Function: Private function to read luminosity on both channels Function: Private function to read luminosity on both channels
Returns: - Returns: -
Args: named arguments: Args: named arguments:
-argument1 => hash: $hash hash of device - argument1 => hash: $hash hash of device
=cut =cut
sub I2C_TSL2561_GetData($) { sub I2C_TSL2561_GetData($) {
@ -836,57 +855,124 @@ sub I2C_TSL2561_GetData($) {
my $operations = 0; my $operations = 0;
while (1) { while (1) {
$operations++; $operations++;
if ($hash->{acquiState} == ACQUI_STATE_ERROR) { if ($hash->{acquiState} == TSL2561_ACQUI_STATE_ERROR) {
$hash->{calcState} = CALC_STATE_ERROR;
readingsSingleUpdate($hash, 'state', STATE_I2C_ERROR, 1);
# Turn the device off to save power
I2C_TSL2561_Disable($hash);
$hash->{acquiState} = ACQUI_STATE_DISABLED;
$success = 0; $success = 0;
last; # Abort, Start again at next slow poll last; # Abort, Start again at next slow poll
} elsif ($operations > 10) { } elsif ($operations > 10) {
# Too many consecutive operations, abort # Too many consecutive operations, abort
$hash->{acquiState} = ACQUI_STATE_ERROR; $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
Log3 $name, 5, "I2C_TSL2561_GetData: state machine stuck, aborting"; Log3 $name, 5, "I2C_TSL2561_GetData: state machine stuck, aborting";
} elsif ($hash->{acquiState} == ACQUI_STATE_DISABLED) { } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_IDLE) {
# Enable the device by setting the control bit to 0x03 if (!defined($hash->{tsl2561Package})) {
if (!I2C_TSL2561_Enable($hash)) { # Choose an address to scan the I2C bus for device in auto address mode
$hash->{acquiState} = ACQUI_STATE_ERROR; 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} == ACQUI_STATE_ENABLED) { } 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 # Wait x ms for ADC to complete
$hash->{calcState} = CALC_STATE_DATA_REQUESTED;
my $now = [gettimeofday]; my $now = [gettimeofday];
if (tv_interval($hash->{acquiStarted}, $now) >= I2C_TSL2561_GetIntegrationTime($hash)) { if (tv_interval($hash->{acquiStarted}, $now) >= I2C_TSL2561_GetIntegrationTime($hash)) {
$hash->{acquiState} = ACQUI_STATE_DATA_AVAILABLE; $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_AVAILABLE;
} else { } else {
last; # Wait, check again after next fast poll last; # Wait for measurement to complete, check again after next fast poll
} }
} elsif ($hash->{acquiState} == ACQUI_STATE_DATA_AVAILABLE) { } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_AVAILABLE) {
# Reads a two byte value from channel 0 (visible + infrared) # Read a two byte value from channel 0 and channel 1 (visible + infrared)
if (!I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN0_LOW, 2)) { $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_REQUESTED;
$hash->{acquiState} = ACQUI_STATE_ERROR; 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} == ACQUI_STATE_DATA_CH0_RECEIVED) { } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_REQUESTED) {
# Reads a two byte value from channel 1 (infrared) last; # Wait for channel 0 or channel 1 data to be read
if (!I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_WORD_BIT | TSL2561_REGISTER_CHAN1_LOW, 2)) { } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_CH0_RECEIVED) {
$hash->{acquiState} = ACQUI_STATE_ERROR; # Read a two byte value from channel 1 (infrared)
} $hash->{acquiState} = TSL2561_ACQUI_STATE_DATA_REQUESTED;
} elsif ($hash->{acquiState} == ACQUI_STATE_DATA_CH1_RECEIVED) { last; # Wait for channel 1 data to be read
$hash->{calcState} = CALC_STATE_DATA_RECEIVED; } elsif ($hash->{acquiState} == TSL2561_ACQUI_STATE_DATA_CH1_RECEIVED) {
# Done, turn the device off to save power $hash->{calcState} = TSL2561_CALC_STATE_DATA_RECEIVED;
# Try to turn the device off to save power
I2C_TSL2561_Disable($hash); I2C_TSL2561_Disable($hash);
$hash->{acquiState} = ACQUI_STATE_DISABLED; $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
last; # Done, start again at next slow poll last; # Done, start again at next slow poll
} else { } else {
# Undefined state # Undefined state
$hash->{acquiState} = ACQUI_STATE_ERROR; $hash->{acquiState} = TSL2561_ACQUI_STATE_ERROR;
} }
} }
return $success; 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, 5, "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 =head2 I2C_TSL2561_SetIntegrationTime
Title: I2C_TSL2561_SetIntegrationTime Title: I2C_TSL2561_SetIntegrationTime
Function: Sets the integration time for the TSL2561 Function: Sets the integration time for the TSL2561
@ -901,26 +987,12 @@ sub I2C_TSL2561_SetIntegrationTime($$) {
my ($hash, $time) = @_; my ($hash, $time) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
# store the value even if $hash->{tsl2561Package} ist not set (yet). That happens # store the value even if $hash->{tsl2561Package} is not set (yet). That happens
# during fhem startup. # during fhem startup.
$hash->{tsl2561IntegrationTime} = $time; $hash->{tsl2561IntegrationTime} = $time;
# Enable the device by setting the control bit to 0x03 $hash->{timingModified} = 1;
my $success = 0;
if (!AttrVal($hash->{NAME}, "disable", 0) && defined($hash->{tsl2561Package})) {
my $state = ReadingsVal($name, 'state', '');
if ($state ne STATE_I2C_ERROR) {
# 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})) {
if (I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, 1)) {
$success = 1;
}
}
}
}
return $success; return undef;
} }
# #
@ -944,38 +1016,23 @@ sub I2C_TSL2561_GetIntegrationTime($) {
=head2 I2C_TSL2561_SetGain =head2 I2C_TSL2561_SetGain
Title: I2C_TSL2561_SetGain Title: I2C_TSL2561_SetGain
Function: Adjusts the gain on the TSL2561 (adjusts the sensitivity to light) Function: Adjusts the gain on the TSL2561 (adjusts the sensitivity to light)
Returns: - Returns: -
Args: named arguments: Args: named arguments:
-argument1 => hash: $hash hash of device - argument1 => hash: $hash hash of device
-argument1 => number: $gain constant for gain - argument1 => number: $gain constant for gain
=cut =cut
sub I2C_TSL2561_SetGain($$) { sub I2C_TSL2561_SetGain($$) {
my ($hash, $gain) = @_; my ($hash, $gain) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
# store the value even if $hash->{tsl2561Package} ist not set (yet). That happens # store the value even if $hash->{tsl2561Package} is not set (yet). That happens
# during fhem startup. # during fhem startup.
$hash->{tsl2561Gain} = $gain; $hash->{tsl2561Gain} = $gain;
# Enable the device by setting the control bit to 0x03 $hash->{timingModified} = 1;
my $success = 0;
if (!AttrVal($hash->{NAME}, "disable", 0) && defined($hash->{tsl2561Package})) {
my $state = ReadingsVal($name, 'state', '');
if ($state ne STATE_I2C_ERROR) {
# 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})) {
if (I2C_TSL2561_i2cread($hash, TSL2561_COMMAND_BIT | TSL2561_REGISTER_TIMING, 1)) {
$success = 1;
}
}
}
}
return $success; return undef;
} }
# #
@ -1010,36 +1067,50 @@ sub I2C_TSL2561_GetLuminosity($) {
# Log3 $name, 5, "I2C_TSL2561_GetLuminosity: start"; # Log3 $name, 5, "I2C_TSL2561_GetLuminosity: start";
$hash->{operationInProgress} = 1;
# Luminosity calculation state machine # Luminosity calculation state machine
my $lux = undef; my $lux = undef;
my $operations = 0;
while(1) { while(1) {
$operations++; $hash->{operationCounter}++;
Log3 $name, 5, "I2C_TSL2561_GetLuminosity: calc state $hash->{calcState} acqui state $hash->{acquiState}"; Log3 $name, 5, "I2C_TSL2561_GetLuminosity: calc state $hash->{calcState} acqui state $hash->{acquiState}";
if ($hash->{calcState} == CALC_STATE_ERROR) { if ($hash->{calcState} == TSL2561_CALC_STATE_ERROR) {
$hash->{calcState} = CALC_STATE_IDLE;
Log3 $name, 5, "I2C_TSL2561_GetLuminosity: error, aborting"; 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 last; # Abort, start again at next slow poll
} elsif ($operations > 10) { } elsif ($hash->{operationCounter} > TSL2561_MAX_CONSECUTIVE_OPERATIONS) {
# Too many consecutive operations, abort # Too many consecutive operations, abort
$hash->{calcState} = CALC_STATE_ERROR; $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
Log3 $name, 5, "I2C_TSL2561_GetLuminosity: state machine stuck, aborting"; Log3 $name, 5, "I2C_TSL2561_GetLuminosity: state machine stuck, aborting";
} elsif ($hash->{calcState} == CALC_STATE_IDLE) { } elsif ($hash->{calcState} == TSL2561_CALC_STATE_IDLE) {
# Request data # Enable device and request data
Log3 $name, 5, "I2C_TSL2561_GetLuminosity: starting new measurement"; Log3 $name, 5, "I2C_TSL2561_GetLuminosity: starting new measurement";
if (!I2C_TSL2561_GetData($hash)) {
$hash->{calcState} = CALC_STATE_ERROR;
}
} elsif ($hash->{calcState} == CALC_STATE_DATA_REQUESTED) {
# Wait for data
if (I2C_TSL2561_GetData($hash)) { if (I2C_TSL2561_GetData($hash)) {
if ($hash->{acquiState} == ACQUI_STATE_ENABLED) { $hash->{calcState} = TSL2561_CALC_STATE_DATA_REQUESTED;
last; # Wait for data to arrive, check again at next fast poll } 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 { } else {
$hash->{calcState} = CALC_STATE_ERROR; $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
} }
} elsif ($hash->{calcState} == CALC_STATE_DATA_RECEIVED) { } elsif ($hash->{calcState} == TSL2561_CALC_STATE_DATA_RECEIVED) {
# Data was received, optimize gain # Data was received, optimize gain
my $autoGain = AttrVal($name, 'autoGain', 1); my $autoGain = AttrVal($name, 'autoGain', 1);
if ($autoGain) { if ($autoGain) {
@ -1047,24 +1118,24 @@ sub I2C_TSL2561_GetLuminosity($) {
my $it = $hash->{tsl2561IntegrationTime}; my $it = $hash->{tsl2561IntegrationTime};
my $hi = TSL2561_AGC_THI_402MS; my $hi = TSL2561_AGC_THI_402MS;
my $lo = TSL2561_AGC_TLO_402MS; my $lo = TSL2561_AGC_TLO_402MS;
if ($it==TSL2561_INTEGRATIONTIME_13MS) { if ($it == TSL2561_INTEGRATIONTIME_13MS) {
$hi = TSL2561_AGC_THI_13MS; $hi = TSL2561_AGC_THI_13MS;
$lo = TSL2561_AGC_TLO_13MS; $lo = TSL2561_AGC_TLO_13MS;
} elsif ( $it==TSL2561_INTEGRATIONTIME_101MS) { } elsif ($it == TSL2561_INTEGRATIONTIME_101MS) {
$hi = TSL2561_AGC_THI_101MS; $hi = TSL2561_AGC_THI_101MS;
$lo = TSL2561_AGC_TLO_101MS; $lo = TSL2561_AGC_TLO_101MS;
} }
if (($hash->{broadband} < $lo) && ($hash->{tsl2561Gain} == TSL2561_GAIN_1X)) { if (($hash->{broadband} < $lo) && ($hash->{tsl2561Gain} == TSL2561_GAIN_1X)) {
# Increase gain and try again # Increase gain and try again
I2C_TSL2561_SetGain($hash, TSL2561_GAIN_16X); I2C_TSL2561_SetGain($hash, TSL2561_GAIN_16X);
# Drop the previous conversion results $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
$hash->{calcState} = CALC_STATE_IDLE; $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
next; next;
} elsif (($hash->{broadband} > $hi) && ($hash->{tsl2561Gain} == TSL2561_GAIN_16X)) { } elsif (($hash->{broadband} > $hi) && ($hash->{tsl2561Gain} == TSL2561_GAIN_16X)) {
# Drop gain and try again # Drop gain and try again
I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X); I2C_TSL2561_SetGain($hash, TSL2561_GAIN_1X);
# Drop the previous conversion results $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
$hash->{calcState} = CALC_STATE_IDLE; $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
next; next;
} else { } else {
# Reading is either valid, or we're already at the chips limits # Reading is either valid, or we're already at the chips limits
@ -1088,8 +1159,8 @@ sub I2C_TSL2561_GetLuminosity($) {
if ($autoIntegrationTime && $hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_402MS) { if ($autoIntegrationTime && $hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_402MS) {
# Drop integration time and try again # Drop integration time and try again
I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_101MS); I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_101MS);
# Drop the previous conversion results $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
$hash->{calcState} = CALC_STATE_IDLE; $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
next; next;
} else { } else {
# Integration time fixed or already below 402 ms, give up # Integration time fixed or already below 402 ms, give up
@ -1098,10 +1169,10 @@ sub I2C_TSL2561_GetLuminosity($) {
} elsif ($autoIntegrationTime } elsif ($autoIntegrationTime
&& ($hash->{broadband} < ($clipThreshold >> 2) && $hash->{ir} < ($clipThreshold >> 2)) && ($hash->{broadband} < ($clipThreshold >> 2) && $hash->{ir} < ($clipThreshold >> 2))
&& ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS || $hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS)) { && ($hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_13MS || $hash->{tsl2561IntegrationTime} == TSL2561_INTEGRATIONTIME_101MS)) {
# Integration time below 178 ms, maximize # Integration time below 178 ms, maximize and try again
I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_402MS); I2C_TSL2561_SetIntegrationTime($hash, TSL2561_INTEGRATIONTIME_402MS);
# Drop the previous conversion results $hash->{calcState} = TSL2561_CALC_STATE_IDLE;
$hash->{calcState} = CALC_STATE_IDLE; $hash->{acquiState} = TSL2561_ACQUI_STATE_IDLE;
next; next;
} else { } else {
# Readings are not saturated or auto integration time is disabled # Readings are not saturated or auto integration time is disabled
@ -1110,20 +1181,24 @@ sub I2C_TSL2561_GetLuminosity($) {
# Received data is valid, calculate luminosity # Received data is valid, calculate luminosity
$lux = I2C_TSL2561_CalculateLux($hash); $lux = I2C_TSL2561_CalculateLux($hash);
$hash->{calcState} = CALC_STATE_IDLE; $hash->{calcState} = TSL2561_CALC_STATE_COMPLETED;
last; # Done, start again at next slow poll last; # Done, start again at next slow poll
} else { } else {
# Undefined state # Undefined state
$hash->{calcState} = CALC_STATE_ERROR; $hash->{calcState} = TSL2561_CALC_STATE_ERROR;
} }
} }
$hash->{operationInProgress} = 0;
# Log3 $name, 5, "I2C_TSL2561_GetLuminosity: end"; # Log3 $name, 5, "I2C_TSL2561_GetLuminosity: end";
return $lux; return $lux;
} }
#
# get channel scale # get channel scale
#
sub I2C_TSL2561_GetChannelScale($) { sub I2C_TSL2561_GetChannelScale($) {
my ($hash) = @_; my ($hash) = @_;
my $name = $hash->{NAME}; my $name = $hash->{NAME};
@ -1167,11 +1242,10 @@ sub I2C_TSL2561_GetChannelScale($) {
=head2 I2C_TSL2561_CalculateLux =head2 I2C_TSL2561_CalculateLux
Title: 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. 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 Returns: number
Args: named arguments: Args: named arguments:
-argument1 => hash: $hash hash of device - argument1 => hash: $hash hash of device
=cut =cut
sub I2C_TSL2561_CalculateLux($) { sub I2C_TSL2561_CalculateLux($) {
@ -1322,6 +1396,15 @@ sub I2C_TSL2561_CalculateLux($) {
} }
} }
=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($$$) { sub I2C_TSL2561_i2cread($$$) {
my ($hash, $reg, $nbyte) = @_; my ($hash, $reg, $nbyte) = @_;
my $success = 1; my $success = 1;
@ -1357,8 +1440,10 @@ sub I2C_TSL2561_i2cread($$$) {
nbyte => $nbyte nbyte => $nbyte
}); });
}; };
if ($hash->{$iodev->{NAME}.'_SENDSTAT'} eq 'error') { my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'};
readingsSingleUpdate($hash, 'state', STATE_I2C_ERROR, 1); if (defined($sendStat) && $sendStat eq 'error') {
readingsSingleUpdate($hash, 'state', TSL2561_STATE_I2C_ERROR, 1);
Log3 ($hash, 5, $hash->{NAME} . ": i2cread on $iodev->{NAME} failed");
$success = 0; $success = 0;
} }
} else { } else {
@ -1393,8 +1478,10 @@ sub I2C_TSL2561_i2cwrite($$$) {
data => join (' ',@data), data => join (' ',@data),
}); });
}; };
if ($hash->{$iodev->{NAME}.'_SENDSTAT'} eq 'error') { my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'};
readingsSingleUpdate($hash, 'state', STATE_I2C_ERROR, 1); if (defined($sendStat) && $sendStat eq 'error') {
readingsSingleUpdate($hash, 'state', TSL2561_STATE_I2C_ERROR, 1);
Log3 ($hash, 5, $hash->{NAME} . ": i2cwrite on $iodev->{NAME} failed");
$success = 0; $success = 0;
} }
} else { } else {
@ -1525,7 +1612,8 @@ sub I2C_TSL2561_i2cwrite($$$) {
Default: undefined<br> Default: undefined<br>
</li> </li>
<li>poll_interval<br> <li>poll_interval<br>
Set the polling interval in minutes to query the sensor for new measured values.<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> Default: 5, valid values: 1, 2, 5, 10, 20, 30<br>
</li> </li>
<li>gain<br> <li>gain<br>
@ -1561,6 +1649,24 @@ sub I2C_TSL2561_i2cwrite($$$) {
Default: 0, valid values: 0, 1 Default: 0, valid values: 0, 1
</li> </li>
</ul> </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> <br>
</ul> </ul>

View File

@ -191,7 +191,7 @@ FHEM/52_I2C_PCA9532 klausw http://forum.fhem.de Sonstige
FHEM/52_I2C_PCA9685 klausw http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_PCA9685 klausw http://forum.fhem.de Sonstige Systeme
FHEM/52_I2C_PCF8574 klausw http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_PCF8574 klausw http://forum.fhem.de Sonstige Systeme
FHEM/52_I2C_SHT21 klausw http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_SHT21 klausw http://forum.fhem.de Sonstige Systeme
FHEM/52_I2C_TSL2561 kaihs http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_TSL2561 jensb http://forum.fhem.de Sonstige Systeme
FHEM/53_GHoma.pm klausw http://forum.fhem.de Sonstige Systeme FHEM/53_GHoma.pm klausw http://forum.fhem.de Sonstige Systeme
FHEM/55_GDS.pm betateilchen http://forum.fhem.de Unterstuetzende Dienste FHEM/55_GDS.pm betateilchen http://forum.fhem.de Unterstuetzende Dienste
FHEM/55_InfoPanel.pm betateilchen http://forum.fhem.de Unterstuetzende Dienste FHEM/55_InfoPanel.pm betateilchen http://forum.fhem.de Unterstuetzende Dienste