2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-10 09:16:53 +00:00

51_I2C_BMP180.pm: prevent division by zero when calibration data is invalid by Jens Beyer (jensb)

52_I2C_PCA9685.pm: set outputs to old state after frequency change, set more than one port at the same time, bugfixing


git-svn-id: https://svn.fhem.de/fhem/trunk@10452 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
klauswitt 2016-01-10 22:45:12 +00:00
parent 1e0502907c
commit d8c9ec0474
2 changed files with 137 additions and 72 deletions

View File

@ -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
@ -649,6 +652,18 @@ sub I2C_BMP180_DbLog_splitFn($) {
</li>
</ul>
<br>
<b>Notes</b>
<ul>
<li>I2C bus timing<br>
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.<br><br>
</li>
</ul>
<br>
</ul>
=end html
@ -695,7 +710,7 @@ sub I2C_BMP180_DbLog_splitFn($) {
<b>Define</b>
<ul>
<code>define BMP180 &lt;BMP180_name&gt; &lt;I2C_device&gt;</code><br><br>
&lt;I2C device&gt; darf nicht verwendet werden, wenn der I2C Bus &uuml;ber das RPII2C Modul angesprochen wird. For HiPi ist es allerdings notwendig. <br>
&lt;I2C device&gt; darf nicht verwendet werden, wenn der I2C Bus &uuml;ber das RPII2C Modul angesprochen wird. F&uuml;r HiPi ist es allerdings notwendig. <br>
<br>
Beispiel:
<pre>
@ -750,13 +765,25 @@ sub I2C_BMP180_DbLog_splitFn($) {
Default: 1, valid values: 0, 1, 2<br><br>
</li>
<li>altitude<br>
Wenn dieser Wert definiert ist, wird diese Angabe zus&auml; f&uuml;r die Berechnung des
Wenn dieser Wert definiert ist, wird diese Angabe zus&auml;tzlich f&uuml;r die Berechnung des
Luftdrucks bezogen auf Meeresh&ouml;he (Normalnull) NN herangezogen.<br>
Bemerkung: Dies ist ein globales Attribut.<br><br>
<code>attr global altitude 220</code>
</li>
</ul>
<br>
<b>Hinweise</b>
<ul>
<li>I2C-Bustiming<br>
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&ouml;&szlig;er 1 eingestellt wird.
Dies l&auml;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.<br><br>
</li>
</ul>
<br>
</ul>
=end html_DE

View File

@ -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;
@ -176,21 +180,28 @@ sub I2C_PCA9685_Attr(@) { #
I2C_PCA9685_Init($hash,\@def) if (defined ($hash->{IODev}));
}
} 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
return undef unless ($main::init_done);
my @inp = split(/,/, $val) if defined($val);
my $data = 32; # Auto increment soll immer gesetzt sein
foreach (@inp) {
@ -205,13 +216,12 @@ 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
foreach (@inp) {
@ -219,15 +229,15 @@ sub I2C_PCA9685_Attr(@) { #
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 <port>=on|off|0..4095|last where <port> = 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
<b>Set</b>
<ul>
<code>set &lt;name&gt; &lt;port&gt; &lt;value&gt; [&lt;delay&gt;]</code><br><br>
<li>where <code>&lt;port&gt;</code> is one of Port00 to Port15<br>
<li>where <code>&lt;port&gt;</code> is one of Port0 to Port15<br>
and <code>&lt;value&gt;</code> one of<br>
<ul>
<code>
@ -538,13 +564,19 @@ sub I2C_PCA9685_UpdReadings($$$) { # vom IODev gesendete Werte in Read
</code>
</ul>
<code>&lt;delay&gt;</code> 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<br>
</li>
</li><br>
<li>
It is also possible to change more than one port at the same time with comma separated values.<br>
Also P instead of Port is Possible.
</li><br>
<br>
Examples:
<ul>
<code>set mod1 Port04 543</code><br>
<code>set mod1 Port14 434 765</code><br>
<code>set mod1 Port1, Port14 434 765</code><br>
<code>set mod1 Port1 on, P14 434 765</code><br>
</ul><br>
</ul>
@ -666,12 +698,18 @@ sub I2C_PCA9685_UpdReadings($$$) { # vom IODev gesendete Werte in Read
<code>&lt;delay&gt;</code> gibt den Wert innerhalb der Z&auml;hlschleife an, an dem der Ausgang eingeschaltet wird. Damit lassen sich die 16 Ausg&auml;nge zu unterschiedlichen Zeiten einschalten um Stromspitzen zu minimieren.
Dieser Wert hat keinerlei Einfluss auf die Pulsbreite. Stardartwert ist 0, m&ouml;gliche Werte sind 0..4095<br>
</li>
<li>
Um mehrer Ports mit einem Befehl zu &auml;ndern k&ouml;nnen mehrere Befehle per Komma getrennt eingegeben werden.
Anstelle von Port kann auch einfach ein P verwendet werden.
</li>
<br>
Examples:
<ul>
<code>set mod1 Port04 543</code><br>
<code>set mod1 Port14 434 765</code><br>
<code>set mod1 Port1, Port14 434 765</code><br>
<code>set mod1 Port1 on, P14 434 765</code><br>
</ul><br>
</ul>