diff --git a/fhem/FHEM/51_I2C_BMP180.pm b/fhem/FHEM/51_I2C_BMP180.pm
index 4e53fb3bb..a124553ca 100644
--- a/fhem/FHEM/51_I2C_BMP180.pm
+++ b/fhem/FHEM/51_I2C_BMP180.pm
@@ -27,6 +27,7 @@
=head1 AUTHOR - Dirk Hoffmann
dirk@FHEM_Forum (forum.fhem.de)
modified for use with physical I2C devices by Klaus Wittstock (klausw)
+ modified to prevent division by zero when calibration data is invalid by Jens Beyer (jensb)
=cut
package main;
@@ -243,7 +244,7 @@ sub I2C_BMP180_Set($@) {
if ($cmd eq 'readValues') {
my $overSamplingSettings = AttrVal($hash->{NAME}, 'oversampling_settings', 3);
- if (defined($hash->{calibrationData}{ac1})) { # query sensor
+ if (defined($hash->{calibrationData}{md}) && $hash->{calibrationData}{md} != 0 && $hash->{calibrationData}{ac4} != 0) { # query sensor
I2C_BMP180_readUncompensatedTemperature($hash);
I2C_BMP180_readUncompensatedPressure($hash, $overSamplingSettings);
} else { #..but get calibration variables first
@@ -337,13 +338,15 @@ sub I2C_BMP180_GetPress ($$) {
my $ut = $hash->{uncompTemp};
delete $hash->{uncompTemp};
- my $up = ( ( ($raw[0] << 16) | ($raw[1] << 8) | $raw[2] ) >> (8 - $overSamplingSettings) );
+
+ return undef unless defined($hash->{calibrationData}{md}) && $hash->{calibrationData}{md} != 0 && $hash->{calibrationData}{ac4} != 0;
my $temperature = sprintf(
'%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f',
I2C_BMP180_calcTrueTemperature($hash, $ut) / 10
);
+ my $up = ( ( ($raw[0] << 16) | ($raw[1] << 8) | $raw[2] ) >> (8 - $overSamplingSettings) );
my $pressure = sprintf(
'%.' . AttrVal($hash->{NAME}, 'roundPressureDecimal', 1) . 'f',
I2C_BMP180_calcTruePressure($hash, $up, $overSamplingSettings) / 100
@@ -647,7 +650,19 @@ sub I2C_BMP180_DbLog_splitFn($) {
attr global altitude 220
-
+
+
+
+ Notes
+
+ - I2C bus timing
+ For all sensor operations an I2C interface with blocking IO is assumed (e.g. RPII2C).
+ If you use an I2C interface with non-blocking IO (e.g. FRM over ethernet) operation errors may occur,
+ especially if setting the attribute oversampling_settings to a value higher than 1.
+ This may be compensated depending on I2C interface used. For Firmata try setting the attribute
+ i2c-config in the FRM module to a value of about 30000 microseconds.
+
+
@@ -695,7 +710,7 @@ sub I2C_BMP180_DbLog_splitFn($) {
Define
define BMP180 <BMP180_name> <I2C_device>
- <I2C device> darf nicht verwendet werden, wenn der I2C Bus über das RPII2C Modul angesprochen wird. For HiPi ist es allerdings notwendig.
+ <I2C device> darf nicht verwendet werden, wenn der I2C Bus über das RPII2C Modul angesprochen wird. Für HiPi ist es allerdings notwendig.
Beispiel:
@@ -750,14 +765,26 @@ sub I2C_BMP180_DbLog_splitFn($) {
Default: 1, valid values: 0, 1, 2
altitude
- Wenn dieser Wert definiert ist, wird diese Angabe zusä für die Berechnung des
+ Wenn dieser Wert definiert ist, wird diese Angabe zusätzlich für die Berechnung des
Luftdrucks bezogen auf Meereshöhe (Normalnull) NN herangezogen.
Bemerkung: Dies ist ein globales Attribut.
attr global altitude 220
-
+
+
+
+ Hinweise
+
+ - I2C-Bustiming
+ Zur Abfrage des Sensors wird von einer I2C-Schnittstelle mit blockierendem IO-Zugriff (z.B. RPII2C) ausgegangen.
+ Bei I2C-Schnittstellen, die nicht-blockierend arbeiten (z.B. FRM mit Ethernet), kann es zu Verarbeitungsfehlern kommen,
+ insbesondere wenn das Attribut oversampling_settings auf einen Wert größer 1 eingestellt wird.
+ Dies lässt sich je nach I2C-Schnittstelle kompensieren. Bei Firmata empfiehlt es sich,
+ das Attribut i2c-config im Modul FRM auf einen Wert von ca. 30000 Mikrosekunden einzustellen.
+
+
=end html_DE
-=cut
\ No newline at end of file
+=cut
diff --git a/fhem/FHEM/52_I2C_PCA9685.pm b/fhem/FHEM/52_I2C_PCA9685.pm
index cac555fb8..0f02ada2e 100644
--- a/fhem/FHEM/52_I2C_PCA9685.pm
+++ b/fhem/FHEM/52_I2C_PCA9685.pm
@@ -115,8 +115,11 @@ sub I2C_PCA9685_Init($$) { #
}
AssignIoPort($hash);
#Mode register wiederherstellen
+ I2C_PCA9685_i2cread($hash, 1, 1); # Modreg2 schonmal lesen
+ I2C_PCA9685_i2cread($hash, 254, 1); # Frequenz fuer Internal
+ select(undef, undef, undef, 0.1);
I2C_PCA9685_Attr(undef, $name, "modreg1", AttrVal($name, "modreg1", ""));
- I2C_PCA9685_Attr(undef, $name, "modreg2", AttrVal($name, "modreg2", ""));
+ I2C_PCA9685_Attr(undef, $name, "modreg2", AttrVal($name, "modreg2", "OUTDRV"));
#alternative I2C Adressen wiederherstellen
I2C_PCA9685_i2cwrite($hash,AttrVal($name, $defaultreg{'sub1'}, "subadr1") << 1, 2) if defined AttrVal($name, "subadr1", undef);
I2C_PCA9685_i2cwrite($hash,AttrVal($name, $defaultreg{'sub2'}, "subadr2") << 1, 3) if defined AttrVal($name, "subadr2", undef);
@@ -126,7 +129,8 @@ sub I2C_PCA9685_Init($$) { #
I2C_PCA9685_Attr(undef, $name, "prescale", AttrVal($name, "prescale", $defaultreg{'presc'})) if defined AttrVal($name, "prescale", undef);
#Portzustände wiederherstellen
foreach (0..15) {
- I2C_PCA9685_Set($hash, $name,"Port".sprintf ('%02d', $_), ReadingsVal($name,"Port".$_,0) );
+ my $port = "Port".sprintf ('%02d', $_);
+ I2C_PCA9685_Set($hash, $name, $port, ReadingsVal($name,$port ,0) );
}
$hash->{STATE} = 'Initialized';
return;
@@ -175,29 +179,36 @@ sub I2C_PCA9685_Attr(@) { #
my @def = split (' ',$hash->{DEF});
I2C_PCA9685_Init($hash,\@def) if (defined ($hash->{IODev}));
}
- } elsif ($attr && $attr =~ m/^prescale$/i) { #Frequenz
+ } elsif ($attr && $attr =~ m/^prescale$/i) { # Frequenz
+ return undef unless ($main::init_done);
+ $val = 30 unless (defined($val)); #beim loeschen wieder auf Standard setzen
return "wrong value: $val for \"set $name $attr\" use 0-255"
unless(looks_like_number($val) && $val >= 0 && $val < 256);
my $modereg1 = defined $hash->{confregs}{0} ? $hash->{confregs}{0} : $defaultreg{'modreg1'};
my $modereg1mod = ( $modereg1 & 0x7F ) | $mr1{ "SLEEP" };
$msg = I2C_PCA9685_i2cwrite($hash, 0, $modereg1mod); #sleep Mode aktivieren
- $msg .= I2C_PCA9685_i2cwrite($hash, 254 ,$val); #Frequenz aktualisieren
- $msg .= I2C_PCA9685_i2cwrite($hash, 0 ,$modereg1); #sleep Mode wieder aus
- #Log3 $hash, 1, "testprescale: $modereg1 | $modereg1mod | $val";
- } elsif ($attr && $attr =~ m/^(subadr[1-3])|allcalladr$/i) {
- substr($attr,0,6,""); #weitere I2C Adressen
+ $msg = I2C_PCA9685_i2cwrite($hash, 254 ,$val); #Frequenz aktualisieren
+ $msg = I2C_PCA9685_i2cwrite($hash, 0 ,$modereg1); #sleep Mode wieder aus
+ foreach (0..15) { #Portzustände wiederherstellen
+ my $port = "Port".sprintf ('%02d', $_);
+ I2C_PCA9685_Set($hash, $name, $port, ReadingsVal($name,$port ,0) );
+ }
+ } elsif ($attr && $attr =~ m/^(subadr[1-3])|allcalladr$/i) { # weitere I2C Adressen
+ return undef unless ($main::init_done);
+ substr($attr,0,6,"");
my $regaddr = ($attr =~ m/^l/i) ? 5 : $attr + 1;
my $subadr = $val =~ /^0.*$/ ? oct($val) : $val;
return "I2C Address not valid" if $subadr > 127;
$msg = I2C_PCA9685_i2cwrite($hash, $regaddr ,$subadr << 1);
- } elsif ($attr && $attr =~ m/^modreg1$/i) { #Mode register 1
+ } elsif ($attr && $attr =~ m/^modreg1$/i) { # Mode register 1
+ return undef unless ($main::init_done);
my @inp = split(/,/, $val) if defined($val);
- my $data = 32; # Auto increment soll immer gesetzt sein
+ my $data = 32; # Auto increment soll immer gesetzt sein
foreach (@inp) {
return "wrong value: $_ for \"attr $name $attr\" use comma separated list of " . join(',', (sort { $mr1{ $a } <=> $mr1{ $b } } keys %setsP) )
unless(exists($mr1{$_}));
$data |= $mr1{$_};
- if ($_ eq "EXTCLK") { #wenn externer Oszillator genutzt werden soll, zuerst den sleep mode aktivieren (wenn er gelöscht wird dann noch reset machen)
+ if ($_ eq "EXTCLK") { #wenn externer Oszillator genutzt werden soll, zuerst den sleep mode aktivieren (wenn er gelöscht wird dann noch reset machen)
my $modereg1 = defined $hash->{confregs}{0} ? $hash->{confregs}{0} : $defaultreg{'modreg1'};
my $modereg1mod = ( $modereg1 & 0x7F ) | $mr1{ "SLEEP" };
Log3 $hash, 5, "$hash->{NAME}: sleep Mode aktivieren (Vorbereitung fuer EXTCLK)";
@@ -205,29 +216,28 @@ sub I2C_PCA9685_Attr(@) { #
$data += $mr1{"SLEEP"};
}
}
- #my $modereg1 = defined $hash->{confregs}{0} ? $hash->{confregs}{0} : $defaultreg{'modreg1'};
- #Log3 $hash, 1, "test1: " . ($hash->{confregs}{0} & $mr1{"EXTCLK"}) . "|" . $hash->{confregs}{0} ."|". $mr1{"EXTCLK"} . " test2: ". ($data & $mr1{"EXTCLK"}) ."|" . $data ."|". $mr1{"EXTCLK"};
if ( defined $hash->{confregs}{0} && ($hash->{confregs}{0} & $mr1{"EXTCLK"}) == $mr1{"EXTCLK"} && ($data & $mr1{"EXTCLK"}) == 0 ) { #reset wenn EXTCLK abgeschaltet wird
$msg = I2C_PCA9685_i2cwrite($hash, 0 , $data | 0x80);
}
$msg = I2C_PCA9685_i2cwrite($hash, 0 , $data);
} elsif ($attr && $attr =~ m/^modreg2$/i) { #Mode register 2
+ return undef unless ($main::init_done);
my @inp = split(/,/, $val) if defined($val);
- my $data = 0; # Auto increment soll immer gesetzt sein
+ my $data = 0; # Auto increment soll immer gesetzt sein
foreach (@inp) {
return "wrong value: $_ for \"attr $name $attr\" use comma separated list of " . join(',', (sort { $mr2{ $a } <=> $mr2{ $b } } keys %setsP) )
unless(exists($mr2{$_}));
$data += $mr2{$_};
}
- $msg = I2C_PCA9685_i2cwrite($hash, 1, $data);
+ $msg = I2C_PCA9685_i2cwrite($hash, 1, $data) if ($hash->{confregs}{1} != $data);
} elsif ($attr && $attr eq "OnStartup") {
- # Das muss noch angepasst werden !!!!!!!!!!!!!!!!!!!!
if (defined $val) {
foreach (split (/,/,$val)) {
my @pair = split (/=/,$_);
$msg = "wrong value: $_ for \"attr $hash->{NAME} $attr\" use comma separated =on|off|0..4095|last where = 0 - 15 "
unless ( scalar(@pair) == 2 &&
- (($pair[0] =~ m/^[0-9]|1[0-5]$/i && ( $pair[1] eq "last" || exists($setsP{$pair[1]}) ||
+ (($pair[0] =~ m/(^[0-9]|1[0-5])$/i &&
+ ( $pair[1] eq "last" || exists($setsP{$pair[1]}) ||
( $pair[1] =~ m/^\d+$/ && $pair[1] < 4095 ) ) ) )
);
}
@@ -237,59 +247,54 @@ sub I2C_PCA9685_Attr(@) { #
}
#############################################################################
sub I2C_PCA9685_Set($@) { #
- my ($hash, $name, @a) = @_;
- my $port = $a[0];
- my $val = $a[1];
-
+ my ($hash, $name, @rest) = @_;
+
my $dimstep = AttrVal($name, "dimstep", "1");
my $dimcount = AttrVal($name, "dimcount", "4095");
my $msg;
+ my $str = join(' ',@rest);
+ if ($str && $str =~ m/^(P(ort|)((0|)[0-9]|1[0-5]))/i) { # (mehrere) Ports ( regex unfertig)
+ #Log3 undef, 1, "$name: empfangen: $str";
+ if (index($str, ',') == -1) { # Nur ein Port
+ my ($port, $dim, $delay) = split(' ', $str);
+ #Log3 undef, 1, "$name: ein Wert: $port, $dim, $delay";
+ $msg = I2C_PCA9685_SetPort($hash, $port, $dim, $delay);
+ } elsif ($str =~ m/^(P(ort|)((0|)[0-9]|1[0-5]))(( ){0,3},( ){0,3}(P(ort|)((0|)[0-9]|1[0-5])){1,})( ){1,3}\d*(( ){1,3}\d*)?( ){0,3}$/i ) { # Format P[ort]x,P[ort]y[,P..] Dimwert[ Delay]
+ my @einzel = split(',', $str);
+ my (undef, $dim, $delay) = split(' ', $einzel[$#einzel]);
+ foreach (reverse @einzel) {
+ my ($port) = split(' ', $_);
+ #Log3 undef, 1, "$name: mehrere Ports gleich: $port, $dim" . (defined $delay ? ", $delay" : "" );
+ $msg = I2C_PCA9685_SetPort($hash, $port, $dim, $delay);
+ last if defined($msg);
+ }
- #my $str = join(" ",@a);
- #if ($str $$ $str =~ m/^(P(ort|)((0|)[0-9]|1[0-5])) $/i) { # mehrere Port (unfertig)
- #
- # if (index($str, ',') != -1) { # wenn mehrere Kanaele gesetzt werden sollen
- # my @einzel = split(',', $str);
- # my (undef, $tval, $tdval) = split(' ', $einzel[$#einzel]); # Dimmwerte von letztem Eintrag sichern
- # Log3 $hash, 1, "Tempval: $tval | $tdval";
- # foreach (reverse @einzel) {
- # my @cmd = split(' ', $_);
- # my ($dim, $delay);
- # my $port = $cmd[0];
- # $port =~ tr/P(ort|)//d;
- # if (defined($cmd[1])) {
- # $dim = $cmd[1];
- # $delay = $cmd[2];
- # } else {
- # $dim = $tval;
- # $delay = $tdval;
- # }
- # Log3 $hash, 1, "Werte fuer $port: $dim | $delay";
- # #hier
- #
- #
- # }
- # }
- #}
+ } elsif ($str =~ m/^(P(ort|)((0|)[0-9]|1[0-5]))( ){1,3}\d*(( ){1,3}\d*)?(( ){0,3},( ){0,3}(P(ort|)((0|)[0-9]|1[0-5]))( ){1,3}\d*(( ){1,3}\d*)?){1,}( ){0,3}$/i ) { # Mehrere Ports auf versch. Werte setzen
+ my @einzel = split(',', $str);
+ foreach (@einzel) {
+ my ($port, $dim, $delay) = split(' ', $_);
+ #Log3 undef, 1, "$name: mehrere Ports: $port, $dim" . (defined $delay ? ", $delay" : "" );
+ $msg = I2C_PCA9685_SetPort($hash, $port, $dim, $delay);
+ last if defined($msg);
+ }
- if ( $port && $port =~ m/^(P(ort|)((0|)[0-9]|1[0-5]))|(All)$/i) { # wenn ein Port oder alle
- return "wrong value: $val for \"set $name $port\" use one of: " .
- join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) .
- " 0..$dimcount"
- unless(exists($setsP{$val}) || ($val >= 0 && $val <= $dimcount));
- ($port =~ m/^All$/i) ? $port = 61 : $port =~ tr/P(ort|)//d; # Portnummer extrahieren oder 61 für All setzen (All Startreg ist 250)
- my $reg = 6 + 4 * $port; # Nummer des entspechenden LEDx_ON_L Registers (LED0_ON_L = 0x06) jede LED hat 4 Register
- my $data = I2C_PCA9685_CalcRegs($hash, $port, $val, $a[2]); # Registerinhalte berechnen
- $msg = I2C_PCA9685_i2cwrite($hash,$reg, $data); # Rausschicken
+ }
+ } elsif ($str =~ m/(a(ll|) \d{1,4}( \d{1,4})?)( ){0,3}$/i) { # Alle Ports gleichzeitig
+ my ($port, $dim, $delay) = split(' ', $str);
+ $port = 61; # Portnummer auf 61 für All setzen (All Startreg ist 250)
+ #Log3 undef, 1, "$name: alle Ports: $port, $dim" . (defined $delay ? ", $delay" : "" );
+ $msg = I2C_PCA9685_SetPort($hash, $port, $dim, $delay);
} else {
my $list = undef;
foreach (0..15) {
$list .= "Port" . sprintf ('%02d', $_) . ":slider,0,$dimstep,$dimcount ";
}
$list .= "all:slider,0,$dimstep,$dimcount";
- $msg = "Unknown argument $a[0], choose one of " . $list;
+ $msg = "Unknown argument $str, choose one of " . $list;
}
- return defined $msg ? $msg : undef
+
+ return (defined($msg) ? $msg."--" : undef);
+
}
#my $string = 'AA55FF0102040810204080';
#my @hex = ($string =~ /(..)/g);
@@ -306,6 +311,26 @@ sub I2C_PCA9685_Set($@) { #
#sprintf "%02X " x 4 . "\n", @octets;
# prints: 00 00 07 D1
#############################################################################
+sub I2C_PCA9685_SetPort($$$$) { #
+ my ($hash, $port, $dim, $delay) = @_;
+ my $name = $hash->{NAME};
+ my $dimcount = AttrVal($name, "dimcount", "4095");
+ $port =~ tr/P(ort|)//d; #Nummer aus Port extrahieren
+ return "wrong dimvalue: $dim for \"set $name $port\" use one of: " .
+ join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) .
+ " 0..$dimcount"
+ unless(exists($setsP{$dim}) || ($dim >= 0 && $dim <= $dimcount));
+
+ return "wrong delayvalue: $delay for \"set $name $port $dim\" use one of: " .
+ join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) .
+ " 0..$dimcount"
+ unless( not defined($delay) && ( exists($setsP{$delay}) || ($delay >= 0 && $delay <= $dimcount) ));
+
+ my ($data, $reg) = I2C_PCA9685_CalcRegs($hash, $port, $dim, $delay); # Registerinhalte berechnen
+ my $msg = I2C_PCA9685_i2cwrite($hash,$reg, $data); # Rausschicken
+ return defined $msg ? $msg : undef
+}
+#############################################################################
sub I2C_PCA9685_CalcRegs($$$$) { # Registerinhalte berechnen
my ($hash, $port, $val, $del) = @_;
my $dimcount = AttrVal($hash->{NAME}, "dimcount", "4095");
@@ -338,7 +363,8 @@ sub I2C_PCA9685_CalcRegs($$$$) { # Registerinhalte berechnen
$data = sprintf "%01d " x 4, @LEDx;
}
}
- return $data;
+ my $reg = 6 + 4 * $port; # Nummer des entspechenden LEDx_ON_L Registers (LED0_ON_L = 0x06) jede LED hat 4 Register
+ return $data, $reg;
}
#############################################################################
sub I2C_PCA9685_Get($@) { # Portwerte bei laden der Datailseite aktualisieren
@@ -380,7 +406,7 @@ sub I2C_PCA9685_i2cread($$$) { # Lesebefehl an Hardware absetzen (ant
sub I2C_PCA9685_i2cwrite($$$) { # Schreibbefehl an Hardware absetzen
my ($hash, $reg, @data) = @_;
if (defined (my $iodev = $hash->{IODev})) {
- Log3 $hash, 5, "$hash->{NAME}: $hash->{I2C_Address} write join (' ',@data) to Register $reg";
+ Log3 $hash, 5, "$hash->{NAME}: $hash->{I2C_Address} write " . join (' ',@data) . " to Register $reg";
CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, {
direction => "i2cwrite",
i2caddress => $hash->{I2C_Address},
@@ -487,7 +513,7 @@ sub I2C_PCA9685_UpdReadings($$$) { # vom IODev gesendete Werte in Read
}
} elsif ($reg == 254) { #wenn Frequenz Register
my $clock = AttrVal($name, "extClock", 25);
- $hash->{Frequency} = sprintf( "0x%.1f", $clock * 1000000 / (4096 * ($inh + 1)) ) . " Hz";
+ $hash->{Frequency} = sprintf( "%.1f", $clock * 1000000 / (4096 * ($inh + 1)) ) . " Hz";
} elsif ( $reg >= 0 && $reg < 6 ) { #Konfigurations Register
$hash->{confregs}{$reg} = $inh;
#folgendes evtl noch weg
@@ -528,7 +554,7 @@ sub I2C_PCA9685_UpdReadings($$$) { # vom IODev gesendete Werte in Read
Set
set <name> <port> <value> [<delay>]
- - where
<port> is one of Port00 to Port15
+ - where
<port> is one of Port0 to Port15
and <value> one of
@@ -538,13 +564,19 @@ sub I2C_PCA9685_UpdReadings($$$) { # vom IODev gesendete Werte in Read
<delay> defines the switch on time inside the PWM counting loop. It does not have an influence to the duty cycle. Default value is 0 and, possible values are 0..4095
-
-
+
+ -
+ It is also possible to change more than one port at the same time with comma separated values.
+ Also P instead of Port is Possible.
+
+
Examples:
set mod1 Port04 543
set mod1 Port14 434 765
+ set mod1 Port1, Port14 434 765
+ set mod1 Port1 on, P14 434 765
@@ -665,13 +697,19 @@ sub I2C_PCA9685_UpdReadings($$$) { # vom IODev gesendete Werte in Read
<delay> gibt den Wert innerhalb der Zählschleife an, an dem der Ausgang eingeschaltet wird. Damit lassen sich die 16 Ausgänge zu unterschiedlichen Zeiten einschalten um Stromspitzen zu minimieren.
Dieser Wert hat keinerlei Einfluss auf die Pulsbreite. Stardartwert ist 0, mögliche Werte sind 0..4095
-
+
+
+ Um mehrer Ports mit einem Befehl zu ändern können mehrere Befehle per Komma getrennt eingegeben werden.
+ Anstelle von Port kann auch einfach ein P verwendet werden.
+
Examples:
set mod1 Port04 543
set mod1 Port14 434 765
+ set mod1 Port1, Port14 434 765
+ set mod1 Port1 on, P14 434 765