I2C_BH1750 [devicename] [0x%x|0x%x]\"\n",
+ BH1750_ADDR_DEFAULT,BH1750_ADDR_OTHER;
+
+ $hash->{I2C_Address}=BH1750_ADDR_DEFAULT; # default
+ $hash->{HiPi_used} = 0;
+
+ shift(@a);
+ if(@a) {
+ $_=shift(@a);
+ if(m+^/dev/+) {
+ $device = $_;
+ if ($libcheck_hasHiPi) {
+ $hash->{HiPi_used} = 1;
+ } else {
+ return '$name error: HiPi library not installed';
+ }
+ if(@a) {
+ $_=shift(@a);
+ /0x(5c|23)/i or return $usage;
+ $hash->{I2C_Address}=hex($_);
+ }
+ } elsif (/0x(5c|23)/i) {
+ $hash->{I2C_Address}=hex($_);
+ } else {
+ return $usage;
+ }
+ @a and return $usage;
+ }
+
+ $hash->{BASEINTERVAL} = 0;
+ $hash->{DELTA} = 0;
+ $hash->{PollState} = BH1750_POLLSTATE_IDLE;
+
+ readingsSingleUpdate($hash, 'state', BH1750_STATE_DEFINED, 1);
+ if ($main::init_done || $hash->{HiPi_used}) {
+ eval {
+ I2C_BH1750_Init($hash, [ $device ]);
+ };
+ Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_BH1750_Catch($@)) if $@;;
+ }
+
+ return undef;
+}
+
+sub I2C_BH1750_Init($$)
+{
+ my ($hash, $dev) = @_;
+ my $name = $hash->{NAME};
+
+ if ($hash->{HiPi_used}) {
+ # check for existing i2c device
+ my $i2cModulesLoaded = 0;
+ $i2cModulesLoaded = 1 if -e $dev;
+ if ($i2cModulesLoaded) {
+ if (-r $dev && -w $dev) {
+ $hash->{devBH1750} = HiPi::Device::I2C->new(
+ devicename => $dev,
+ address => $hash->{I2C_Address},
+ busmode => 'i2c',
+ );
+ Log3 $name, 3, "I2C_BH1750_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);
+ }
+
+
+ # start new measurement cycle
+ RemoveInternalTimer($hash);
+ InternalTimer(gettimeofday() + 10, 'I2C_BH1750_Poll', $hash, 0);
+
+ return undef;
+}
+
+sub I2C_BH1750_Catch($)
+{
+ my $exception = shift;
+ if ($exception) {
+ $exception =~ /^(.*)( at.*FHEM.*)$/;
+ return $1;
+ }
+ return undef;
+}
+
+
+
+sub I2C_BH1750_Attr (@)
+{
+ my ($cmd,$name,$attr,$val) = @_;
+ my $hash = $defs{$name};
+ my $error = "$name: ERROR attribute $attr ";
+ my $del = $cmd =~ /del/;
+ local $_;
+
+ Log3 $name, 3, "I2C_BH1750_Attr $cmd,$name,$attr,$val";
+
+ if ($attr eq "percentdelta") {
+ if($del) {
+ $val = 0; # default
+ } else {
+ $val =~ /^([0-9]+|[0-9]+\.?[0.9]+)$/ or return $error."needs numeric value";
+ }
+ $hash->{DELTA} = $val/100;
+ } elsif ($attr eq "poll_interval") {
+ RemoveInternalTimer($hash);
+ if($del) {
+ $hash->{BASEINTERVAL} = 0;
+ $hash->{PollState} = BH1750_POLLSTATE_IDLE;
+ } else {
+ if($val !~ /^\d+$/ or $val < 1) {
+ return $error."needs interger value";
+ } else {
+ $hash->{BASEINTERVAL} = 60*$val;
+ I2C_BH1750_Restart_Measure($hash);
+ }
+ }
+ }
+
+ return undef;
+}
+
+sub I2C_BH1750_Restart_Measure($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+ RemoveInternalTimer($hash);
+ $hash->{PollState} = BH1750_POLLSTATE_IDLE;
+ my $delay=$hash->{BASEINTERVAL};
+ I2C_BH1750_i2cwrite($hash, BH1750_POWER_DOWN);
+ $delay and InternalTimer(gettimeofday() + 10, 'I2C_BH1750_Poll', $hash, 0);
+}
+
+
+sub I2C_BH1750_Poll($)
+{
+ my ($hash) = @_;
+ my $name = $hash->{NAME};
+
+ Log3 $name, 4, "I2C_BH1750_Poll ".gettimeofday()." PollState=$hash->{PollState}";
+ RemoveInternalTimer($hash);
+ my $delay=$hash->{BASEINTERVAL};
+ $hash->{PollState} == BH1750_POLLSTATE_IDLE and $hash->{PollState}++;
+ my $state = $hash->{PollState};
+ if ($state == BH1750_POLLSTATE_START_MEASURE) {
+ I2C_BH1750_i2cwrite($hash, BH1750_POWER_ON);
+ # check in lowest resolution first
+ $delay = I2C_BH1750_start_measure($hash,BH1750_MT_MIN,BH1750_L_MODE_ONCE);
+ $hash->{PollState} = BH1750_POLLSTATE_PRE_LUX_WAIT;
+ } elsif($state == BH1750_POLLSTATE_PRE_LUX_WAIT) {
+ I2C_BH1750_request_measure($hash);
+ } elsif($state == BH1750_POLLSTATE_PRE_LUX_DONE) {
+ my $lux=$hash->{LUX};
+ my $i;
+ for($i=0; $i<@I2C_BH1750_ranges; $i++) {
+ $lux <= $I2C_BH1750_ranges[$i][0] and last;
+ }
+ if($i == @I2C_BH1750_ranges) {
+ $hash->{PollState} = BH1750_POLLSTATE_LUX_DONE;
+ return I2C_BH1750_Poll($hash);
+ } else {
+ # do finer reading
+ my (undef,$mt,$mode)=@{$I2C_BH1750_ranges[$i]};
+ Log3 $name, 4, "I2C_BH1750_Poll using mt=$mt, mode=$mode";
+ $delay=I2C_BH1750_start_measure($hash,$mt,$mode);
+ $hash->{PollState} = BH1750_POLLSTATE_LUX_WAIT;
+ }
+ } elsif($state == BH1750_POLLSTATE_LUX_WAIT) {
+ I2C_BH1750_request_measure($hash);
+ } elsif($state == BH1750_POLLSTATE_LUX_DONE) {
+ I2C_BH1750_update_lux($hash); # no finer resolution possible
+ $hash->{PollState} = BH1750_POLLSTATE_IDLE;
+ I2C_BH1750_i2cwrite($hash, BH1750_POWER_DOWN);
+ } else {
+ Log3 $name, 1, "I2C_BH1750_Poll wrong state state=$state";
+ $hash->{PollState} = BH1750_POLLSTATE_IDLE;
+ }
+
+ $delay and InternalTimer(gettimeofday() + $delay, 'I2C_BH1750_Poll', $hash, 0);
+
+ return undef;
+}
+
+sub I2C_BH1750_Set($@)
+{
+ my ( $hash, @args ) = @_;
+ my $name = $hash->{NAME};
+ my $cmd = $args[1];
+
+ if($cmd eq "update") {
+ RemoveInternalTimer($hash);
+ $hash->{PollState} = BH1750_POLLSTATE_START_MEASURE;
+ I2C_BH1750_Poll($hash);
+ } elsif($cmd eq "deleteminmax") {
+ delete($hash->{READINGS}{minimum});
+ delete($hash->{READINGS}{maximum});
+ } else {
+ return "Unknown argument ".$cmd.", choose one of update deleteminmax";
+ }
+
+ return undef;
+}
+
+sub I2C_BH1750_Undef($$)
+{
+ my ($hash, $arg) = @_;
+
+ RemoveInternalTimer($hash);
+
+ return undef;
+}
+
+
+sub I2C_BH1750_i2cread($$$)
+{
+ my ($hash, $reg, $nbyte) = @_;
+ my $success = 1;
+
+ Log3 $hash->{NAME}, 5, "I2C_BH1750_i2cread $reg,$nbyte";
+
+ local $SIG{__WARN__} = sub {
+ my $message = shift;
+ # turn warnings from RPII2C_HWACCESS_ioctl into exception
+ if ($message =~ /Exiting subroutine via last at.*00_RPII2C.pm/) {
+ die;
+ } else {
+ warn($message);
+ }
+ };
+
+ if ($hash->{HiPi_used}) {
+ eval {
+ my @values = $hash->{devBH1750}->bus_read($reg, $nbyte);
+ I2C_BH1750_I2CRec($hash, {
+ direction => "i2cread",
+ i2caddress => $hash->{I2C_Address},
+ reg => $reg,
+ nbyte => $nbyte,
+ received => join (' ',@values),
+ });
+ };
+ Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_BH1750_Catch($@)) if $@;
+ } elsif (defined (my $iodev = $hash->{IODev})) {
+ eval {
+ CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
+ direction => "i2cread",
+ i2caddress => $hash->{I2C_Address},
+ reg => $reg,
+ nbyte => $nbyte
+ });
+ };
+ my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'};
+ if (defined($sendStat) && $sendStat eq 'error') {
+ readingsSingleUpdate($hash, 'state', BH1750_STATE_I2C_ERROR, 1);
+ Log3 ($hash, 5, $hash->{NAME} . ": i2cread on $iodev->{NAME} failed");
+ $success = 0;
+ }
+ } else {
+ Log3 ($hash, 1, $hash->{NAME} . ': ' . "no IODev assigned to '$hash->{NAME}'");
+ $success = 0;
+ }
+
+ return $success;
+}
+
+sub I2C_BH1750_i2cwrite
+{
+ my ($hash, $reg, @data) = @_;
+ my $success = 1;
+
+ Log3 $hash->{NAME}, 5, "I2C_BH1750_i2write $reg,@data";
+ if ($hash->{HiPi_used}) {
+ eval {
+ $hash->{devBH1750}->bus_write($reg, join (' ',@data));
+ I2C_BH1750_I2CRec($hash, {
+ direction => "i2cwrite",
+ i2caddress => $hash->{I2C_Address},
+ reg => $reg,
+ data => join (' ',@data),
+ });
+ };
+ Log3 ($hash, 1, $hash->{NAME} . ': ' . I2C_BH1750_Catch($@)) if $@;
+ } elsif (defined (my $iodev = $hash->{IODev})) {
+ eval {
+ CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
+ direction => "i2cwrite",
+ i2caddress => $hash->{I2C_Address},
+ reg => $reg,
+ data => join (' ',@data),
+ });
+ };
+ my $sendStat = $hash->{$iodev->{NAME}.'_SENDSTAT'};
+ if (defined($sendStat) && $sendStat eq 'error') {
+ readingsSingleUpdate($hash, 'state', BH1750_STATE_I2C_ERROR, 1);
+ Log3 ($hash, 5, $hash->{NAME} . ": i2cwrite on $iodev->{NAME} failed");
+ $success = 0;
+ }
+ } else {
+ Log3 ($hash, 1, $hash->{NAME} . ': ' . "no IODev assigned to '$hash->{NAME}'");
+ $success = 0;
+ }
+
+ return $success;
+}
+
+sub I2C_BH1750_I2CRec ($$)
+{
+ my ($hash, $clientmsg) = @_;
+ my $name = $hash->{NAME};
+
+ Log3 $hash->{NAME}, 5, "I2C_BH1750_i2Rec";
+ my $pname = undef;
+ unless ($hash->{HiPi_used}) {
+ my $phash = $hash->{IODev};
+ $pname = $phash->{NAME};
+ while (my ( $k, $v ) = each %$clientmsg) {
+ $hash->{$k} = $v if $k =~ /^$pname/;
+ Log3 $hash->{NAME}, 5, "I2C_BH1750_i2Rec $k $v";
+ }
+ if($clientmsg->{$pname . "_SENDSTAT"} ne "Ok") {
+ Log3 $hash->{NAME}, 3, "I2C_BH1750_i2Rec bad sendstat: ".$clientmsg->{$pname."_SENDSTAT"};
+ if($clientmsg->{direction} eq "i2cread" or $clientmsg->{reg}) {
+ # avoid recoursion on power down
+ I2C_BH1750_Restart_Measure($hash);
+ }
+ return undef;
+ }
+
+ }
+
+ if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received})) {
+ my $register = $clientmsg->{reg};
+ Log3 $hash, 4, "$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[0] << 8 | $raw[1];
+ }
+ if ($register == BH1750_RAW_VALUE) {
+ if($word == 0xFFFF) {
+ readingsSingleUpdate($hash, 'state', BH1750_STATE_SATURATED, 1);
+ Log3 $hash, 3, "$name sensor saturated ";
+ I2C_BH1750_Restart_Measure($hash);
+ } else {
+ readingsSingleUpdate($hash, 'state', BH1750_STATE_OK, 1);
+ my $lux = $word/1.2*(69/$hash->{MT_VAL})/$hash->{MODE};
+ $hash->{LUX}=$lux;
+ Log3 $hash->{NAME}, 4, "I2C_BH1750_I2CRec: lux=$lux";
+ if($hash->{PollState} == BH1750_POLLSTATE_PRE_LUX_WAIT ||
+ $hash->{PollState} == BH1750_POLLSTATE_LUX_WAIT) {
+ $hash->{PollState}++;
+ I2C_BH1750_Poll($hash);
+ }
+ }
+ }
+ }
+ return undef;
+}
+
+sub I2C_BH1750_update_lux
+{
+ my($hash)=@_;
+ my $name=$hash->{NAME};
+ my $lux=$hash->{LUX};
+ my $delta=$hash->{DELTA};
+
+ if($delta) { # update only if delta large enough
+ my $lastlux=ReadingsNum($name,"luminosity",1000000);
+ $lux == $lastlux and return; # no delta, no update
+
+ # check if we have too less delta and return
+ if($lastlux > $lux) {
+ ($lastlux-$lastlux*$delta < $lux) and return;
+ } else {
+ ($lastlux+$lastlux*$delta > $lux) and return;
+ }
+ }
+ # round value
+ if($lux < 100) {
+ $lux = int($lux*10+0.5)/10;
+ } elsif($lux < 1000) {
+ $lux=int($lux+0.5);
+ } elsif($lux < 10000) {
+ $lux=int($lux/10+0.5); $lux *= 10;
+ } elsif($lux < 100000) {
+ $lux=int($lux/100+0.5); $lux *= 100;
+ } else {
+ $lux=int($lux/1000+0.5); $lux *= 1000;
+ }
+ Log3 $hash->{NAME}, 4, "I2C_BH1750_update_lux: luminosity=$lux";
+ readingsBeginUpdate($hash);
+ readingsBulkUpdate($hash, "luminosity", $lux);
+ $lux < ReadingsNum($name,"minimum", 1000000) and readingsBulkUpdate($hash, "minimum", $lux);
+ $lux > ReadingsNum($name,"maximum",-1000000) and readingsBulkUpdate($hash, "maximum", $lux);
+ readingsEndUpdate($hash, 1);
+}
+
+sub I2C_BH1750_request_measure
+{
+ my ($hash) = @_;
+ I2C_BH1750_i2cread($hash,BH1750_RAW_VALUE,2);
+}
+
+
+sub I2C_BH1750_start_measure
+{
+ my ($hash,$mt,$mode)=@_;
+ $hash->{MT_VAL} = $mt;
+ $hash->{MODE} = ($mode == BH1750_H_MODE2 || $mode == BH1750_H_MODE2_ONCE) ? 2 : 1;
+ my $hi=($mt>>5) | 0x40;
+ my $lo=($mt&0x1F) | 0x60;
+ I2C_BH1750_i2cwrite($hash,BH1750_RESET);
+ I2C_BH1750_i2cwrite($hash,$hi); # set MT value
+ I2C_BH1750_i2cwrite($hash,$lo);
+ I2C_BH1750_i2cwrite($hash,$mode);
+ my $mindelay = ($mode == BH1750_L_MODE || $mode == BH1750_L_MODE_ONCE) ? 24 : 180;
+ $mindelay = $mindelay * ($mt/BH1750_MT_DEFAULT);
+ Log3 $hash->{NAME}, 5, "I2C_BH1750_start_measure: duration ".int($mindelay)."ms";
+ $mindelay /=1000; # seconds
+
+ return $mindelay;
+}
+
+sub I2C_BH1750_sleep
+{
+ select(undef, undef, undef, $_[0]);
+}
+
+1;
+
+=pod
+=begin html
+
+
+I2C_BH1750
+
+
+
+ Module for the I2C BH1750 light sensor.
+
+ The BH1750 sensor supports a luminous flux from 0 up to 120k lx
+ and a resolution up to 0.11lx.
+ It supports different modes to be able to cover this large range of
+ data.
+ The I2C_BH1750 module tries always to get
+ the luminosity data from the sensor as good as possible. To achieve
+ this the module first reads flux data in the least sensitive mode and then
+ decides which mode to take to get best results.
+
+
+
+ For the I2C bus the same things are valid as described in the
+ I2C_TSL2561
+ module.
+
+ Define
+
+ define BH1750 I2C_BH1750 [<I2C device>] [I2C address]
+ <I2C device> mandatory for HiPi, must be omitted if you connect
+ via IODev
+ I2C address must be 0x23 or 0x5C (if omitted default address 0x23 is used)
+
+ Examples:
+
+ # Use HiPi with i2c address 0x5C:
+ define BH1750 I2C_BH1750 /dev/i2c-0 0x5C
+
+
+ # Use IODev I2C_2 with default i2c address 0x23
+ # set poll interval to 1 min
+ # generate luminosity value only if difference to last value is at least 10%
+ define BH1750 I2C_BH1750
+ attr BH1750 IODev I2C_2
+ attr BH1750 poll_interval 1
+ attr BH1750 percentdelta 10
+
+
+
+ Set
+
+ set <device name> update
+ Force immediate illumination measurement and restart a
+ new poll_interval.
+ Note that the new readings are not yet available after set returns
+ because the
+ measurement is performed asynchronously. Depending on the flux value
+ this may require more than one second to complete.
+
+ set <device name> deleteminmax
+ Delete the minimum maximum readings to start new
+ min/max measurement
+
+
+
+
+ Readings
+
+ - luminosity
+ Illumination measurement in the range of 0 to 121557 lx.
+ The generated luminosity value is stored with up to one
+ fractional digit for values below 100 and
+ rounded to 3 significant digits for all other values.
+ Compared with the accuracy of the sensor it makes no
+ sense to store the values with more precision.
+
+ - minimum
+ minimum of measured luminosity
+
+ - maximum
+ maximum of measured luminosity
+
+ - state
+ Default: Defined, Ok, Saturated, I2C Error
+
+
+
+
+
+ Attributes
+
+ - IODev
+ Set the name of an IODev module. If undefined the perl modules HiPi::Device::I2C are required.
+ Default: undefined
+
+ - poll_interval
+ 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.
+ valid values: 1, 2, 5, 10, 20, 30
+
+ - percentdelta
+ If set a luminosity reading is only generated if
+ the difference between the current luminosity value and the last reading is
+ at least percentdelta percents.
+
+
+
+
+
+=end html
+
+=cut