diff --git a/fhem/CHANGED b/fhem/CHANGED index 98c475b04..e933dc989 100644 --- a/fhem/CHANGED +++ b/fhem/CHANGED @@ -1,5 +1,6 @@ # 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. + - feature: new module 52_I2C_PCA9685.pm added (klausw) - change: 98_weekprofile: create default profile if master device has no week profile - bugfix: allowed without a validFor is invalid. diff --git a/fhem/FHEM/52_I2C_PCA9685.pm b/fhem/FHEM/52_I2C_PCA9685.pm new file mode 100644 index 000000000..28fb8f2b6 --- /dev/null +++ b/fhem/FHEM/52_I2C_PCA9685.pm @@ -0,0 +1,713 @@ +############################################################################## +# $Id$ +# +############################################################################## +# Modul for I2C PWM Driver PCA9685 +# +# define I2C_PCA9685 +# set +# +# contributed by Klaus Wittstock (2015) email: klauswittstock bei gmail punkt com +# +############################################################################## + +#Inhalte des Hashes: +#i2caddress 00-127(7F) I2C-Adresse +#direction i2cread|i2cwrite Richtung +#reg 00-255|"" Registeradresse (kann weggelassen werden fuer IC's ohne Registeradressierung) +#nbyte Zahl Anzahl Register, die bearbeitet werden sollen (im mom 0-99) +#data 00-255 ... Daten die an I2C geschickt werden sollen (muessen, wenn nbyte benutzt wird immer ein Vielfaches Desselben sein) +#received 00-255 ... Daten die vom I2C empfangen wurden, durch Leerzeichen getrennt (bleibt leer wenn Daten geschrieben werden) +#pname_SENDSTAT Ok|error zeigt uebertragungserfolg an + +package main; +use strict; +use warnings; +use SetExtensions; +#use POSIX; +use Scalar::Util qw(looks_like_number); + +my $setdim = ":slider,0,1,4095 "; + +my %setsP = ( +'off' => 0, +'on' => 1, +); + +my %defaultreg = ( +'modereg1' => 32, #32-> Bit 5 -> Autoincrement +'modereg2' => 0, +'sub1' => 113, +'sub2' => 114, +'sub3' => 116, +'allc' => 112, +'presc' => 30, +); + +my %mr1 = ( +'EXTCLK' => 64, +'SLEEP' => 16, +'SUB1' => 8, +'SUB2' => 4, +'SUB3' => 2, +'ALLCALL' => 1, +); + +my %mr2 = ( +'INVRT' => 16, +'OCH' => 8, +'OUTDRV'=> 4, +'OUTNE1'=> 2, +'OUTNE0'=> 1, +); +############################################################################# +sub I2C_PCA9685_Initialize($) { # + my ($hash) = @_; + $hash->{DefFn} = "I2C_PCA9685_Define"; + $hash->{InitFn} = 'I2C_PCA9685_Init'; + $hash->{UndefFn} = "I2C_PCA9685_Undefine"; + $hash->{AttrFn} = "I2C_PCA9685_Attr"; + $hash->{StateFn} = "I2C_PCA9685_State"; + $hash->{SetFn} = "I2C_PCA9685_Set"; + $hash->{GetFn} = "I2C_PCA9685_Get"; + $hash->{I2CRecFn} = "I2C_PCA9685_I2CRec"; + $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:1,0 showtime:1,0 ". + "prescale:slider,0,1,255 OnStartup ". + "subadr1 subadr2 subadr3 allcalladr ". + "modreg1:multiple-strict,EXTCLK,SUB1,SUB2,SUB3,ALLCALL ". + "modreg2:multiple-strict,INVRT,OCH,OUTDRV,OUTNE0,OUTNE1 ". + "$readingFnAttributes dummy:0,1"; +} +############################################################################# +sub I2C_PCA9685_SetState($$$$) { #-------wozu? + my ($hash, $tim, $vt, $val) = @_; + + $val = $1 if($val =~ m/^(.*) \d+$/); + #return "Undefined value $val" if(!defined($it_c2b{$val})); + return undef; +} +############################################################################# +sub I2C_PCA9685_Define($$) { + my ($hash, $def) = @_; + my @a = split("[ \t]+", $def); + $hash->{STATE} = 'defined'; + if ($main::init_done) { + eval { I2C_PCA9685_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); }; + return I2C_PCA9685_Catch($@) if $@; + } + return undef; +} +############################################################################# +sub I2C_PCA9685_Init($$) { # + my ( $hash, $args ) = @_; + #my @a = split("[ \t]+", $args); + my $name = $hash->{NAME}; + if (defined $args && int(@$args) != 1) { + return "Define: Wrong syntax. Usage:\n" . + "define I2C_PCA9685 "; + } + #return "$name I2C Address not valid" unless ($a[0] =~ /^(0x|)([0-7]|)[0-9A-F]$/xi); + my $msg = undef; + if (defined (my $address = shift @$args)) { + $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address; + } else { + return "$name I2C Address not valid"; + } + AssignIoPort($hash); + #Mode register wiederherstellen + I2C_PCA9685_Attr(undef, $name, "modreg1", AttrVal($name, "modreg1", "")); + I2C_PCA9685_Attr(undef, $name, "modreg2", AttrVal($name, "modreg2", "")); + #alternative I2C Adressen wiederherstellen + I2C_PCA9685_I2CSet($hash,AttrVal($name, "subadr1", $defaultreg{'sub1'}) << 1, 2) if defined AttrVal($name, "subadr1", undef); + I2C_PCA9685_I2CSet($hash,AttrVal($name, "subadr2", $defaultreg{'sub2'}) << 1, 3) if defined AttrVal($name, "subadr2", undef); + I2C_PCA9685_I2CSet($hash,AttrVal($name, "subadr3", $defaultreg{'sub3'}) << 1, 4) if defined AttrVal($name, "subadr3", undef); + I2C_PCA9685_I2CSet($hash,AttrVal($name, "allcalladr", $defaultreg{'allc'}) << 1, 5) if defined AttrVal($name, "allcalladr", undef); + #PWM Frequenz wiederherstellen + 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) ); + } + $hash->{STATE} = 'Initialized'; + return; +} + +{"RX: ". ReadingsVal($name,'RX',"-") . " / TX: ". ReadingsVal($name,'TX',"-")} + +############################################################################# +sub I2C_PCA9685_Catch($) { # + my $exception = shift; + if ($exception) { + $exception =~ /^(.*)( at.*FHEM.*)$/; + return $1; + } + return undef; +} +############################################################################# +sub I2C_PCA9685_State($$$$) { # reload readings at FHEM start + my ($hash, $tim, $sname, $sval) = @_; + Log3 $hash, 4, "$hash->{NAME}: $sname kann auf $sval wiederhergestellt werden $tim"; + if ($sname =~ m/^Port(((0|)[0-9])|(1[0-5]))$/i) { + substr($sname,0,4,""); + $sname = sprintf('%d', $sname); + my %onstart = split /[,=]/, AttrVal($hash->{NAME}, "OnStartup", ""); + if ( exists($onstart{$sname}) && ( exists($setsP{$onstart{$sname}}) || ($onstart{$sname} =~ m/^\d+$/ && $onstart{$sname} < 4095) ) ) { + Log3 $hash, 5, "$hash->{NAME}: Port" . sprintf('%02d', $sname) . " soll auf $onstart{$sname} gesetzt werden"; + readingsSingleUpdate($hash,"Port". sprintf('%02d', $sname), $onstart{$sname}, 1); + } else { + Log3 $hash, 5, "$hash->{NAME}: Port" . sprintf('%02d', $sname) . " soll auf Altzustand: $sval gesetzt werden"; + $hash->{READINGS}{'Port'. sprintf('%02d', $sname)}{VAL} = $sval; + $hash->{READINGS}{'Port'. sprintf('%02d', $sname)}{TIME} = $tim; + } + } + return undef; +} +############################################################################# +sub I2C_PCA9685_Undefine($$) { # + my ($hash, $arg) = @_; + return undef +} +############################################################################# +sub I2C_PCA9685_Attr(@) { # + my ($command, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $msg = ''; + if ($command && $command eq "set" && $attr && $attr eq "IODev") { + if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $val)) { + main::AssignIoPort($hash,$val); + my @def = split (' ',$hash->{DEF}); + I2C_PCA9685_Init($hash,\@def) if (defined ($hash->{IODev})); + } + } elsif ($attr && $attr =~ m/^prescale$/i) { #Frequenz + 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_I2CSet($hash,$modereg1mod, 0); #sleep Mode aktivieren + $msg .= I2C_PCA9685_I2CSet($hash,$val, 254); #Frequenz aktualisieren + $msg .= I2C_PCA9685_I2CSet($hash,$modereg1, 0); #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 + 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_I2CSet($hash,$subadr << 1,$regaddr); + } elsif ($attr && $attr =~ m/^modreg1$/i) { #Mode register 1 + my @inp = split(/,/, $val) if defined($val); + 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) + 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)"; + $msg = I2C_PCA9685_I2CSet($hash,$modereg1mod, 0); #sleep Mode aktivieren + $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_I2CSet($hash, $data | 0x80, 0); + } + $msg = I2C_PCA9685_I2CSet($hash, $data, 0); + } elsif ($attr && $attr =~ m/^modreg2$/i) { #Mode register 2 + my @inp = split(/,/, $val) if defined($val); + 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_I2CSet($hash, $data, 1); + } 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[1] =~ m/^\d+$/ && $pair[1] < 4095 ) ) ) ) + ); + } + } + } + return ($msg) ? $msg : undef; +} +############################################################################# +sub I2C_PCA9685_Set($@) { # + my ($hash, @a) = @_; + my $name = $a[0]; + my $port = $a[1]; + my $val = $a[2]; + unless (@a == 3) { + + } + my $msg; + my $dimstep = AttrVal($name, "dimstep", "1"); + my $dimcount = AttrVal($name, "dimcount", "4095"); + + if ( $port && $port =~ m/^(Port((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 : substr($port,0,4,""); #Portnummer extrahieren oder 61 für All setzen + my $reg = 6 + 4 * $port; #Nummer des entspechenden LEDx_ON_L Registers (LED0_ON_L = 0x06) jede LED hat 4 Register + my $data; + if ($val eq "on") { + $data = "0 16 0 0"; + } elsif ($val eq "off") { + $data = "0 0 0 16"; + } else { + my $delaytime = 0; + if ($dimcount < 4095) { #DimmWert anpassen bei anderem Faktor + $val = int($val * 4095 / $dimcount); + } + if (defined $a[3]) { #Delaytime angegeben? + return "wrong delay value: $a[3] for \"set $name Port$port $val $a[3]\" use value between 0 and $dimcount" + unless ($a[3] >= 0 && $a[3] <= $dimcount); + if ($dimcount < 4095) { #DelayWert anpassen bei anderem Faktor + $a[3] = int($a[3] * 4095 / $dimcount); + } + $delaytime = $a[3] + } else { #...wenn nicht aus Reading holen (für all kommt immer 0 raus) + $delaytime = ReadingsVal($name,'Port_d'.sprintf ('%02d', $port),"0"); + } + my $LEDx_OFF = $delaytime + $val - (( $val + $delaytime < 4096 ) ? 0 : 4096); + if ($LEDx_OFF == $delaytime) { #beide Register dürfen nicht gleichen Inhalt haben, das entpricht "aus" + $data = "0 0 0 16"; + } else { + my @LEDx = unpack("C*", pack("S", $delaytime)); + push @LEDx, unpack("C*", pack("S", $LEDx_OFF)); #Array $LEDx[0] = LEDx_ON_L, $LEDx[1] = LEDx_ON_H, $LEDx[2] = LEDx_OFF_L, $LEDx[3] = LEDx_OFF_H + $data = sprintf "%01d " x 4, @LEDx; + } + } + $msg = I2C_PCA9685_I2CSet($hash,$data,$reg); + } 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[1], choose one of " . $list; + } + return defined $msg ? $msg : undef +} + #my $string = 'AA55FF0102040810204080'; + #my @hex = ($string =~ /(..)/g); + #my @dec = map { hex($_) } @hex; + #my @bytes = map { pack('C', $_) } @dec; + #or + #my @bytes = map { pack('C', hex($_)) } ($string =~ /(..)/g); + #or + #my $bytes = pack "H*", $hex; + #---------------------- + #$int = 2001; + #$bint = pack("N", $int); + #@octets = unpack("C4", $bint); + #sprintf "%02X " x 4 . "\n", @octets; + # prints: 00 00 07 D1 +############################################################################# +sub I2C_PCA9685_I2CSet { # I2CWrtFn vom IODev aufrufen !!!!!wieder auf IODev umleiten!!! + my ($hash, $data, $reg) = @_; + + if (defined (my $iodev = $hash->{IODev})) { + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $hash->{I2C_Address}, + reg => $reg, + data => $data, + }) if (defined $hash->{I2C_Address}); + } else { + I2C_PCA9685_UpdReadings($hash, $reg, $data); # Zeile zum testen (Werte werden direkt zu I2CRec umgeleitet) + #return "no IODev assigned to '$hash->{NAME}'"; + } +} +############################################################################# +sub I2C_PCA9685_Get($@) { # ---- wie beim reload der Detailseite ausführen + my ($hash, @a) = @_; + my $name =$a[0]; + + my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cread" ); + $sendpackage{reg} = 0x6; #startadresse zum lesen + $sendpackage{nbyte} = 63; + return "$name: no IO device defined" unless ($hash->{IODev}); + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + CallFn($pname, "I2CWrtFn", $phash, \%sendpackage); + +} +############################################################################# +sub I2C_PCA9685_I2CRec($@) { # vom IODev aufgerufen + my ($hash, $clientmsg) = @_; + my $name = $hash->{NAME}; + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals fuer alle Keys in $clientmsg die mit dem physical Namen beginnen + $hash->{$k} = $v if $k =~ /^$pname/ ; + } + if ($clientmsg->{direction} && defined($clientmsg->{reg}) && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok") { + if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) { + my @rec = split(" ",$clientmsg->{received}); + Log3 $hash, 3, "$name: wrong amount of registers transmitted from $pname" unless (@rec == $clientmsg->{nbyte}); + foreach (reverse 0..$#rec) { #reverse, damit Inputs (Register 0 und 1 als letztes geschrieben werden) + I2C_PCA9685_UpdReadings($hash, $_ + $clientmsg->{reg} , $rec[$_]); + } + readingsSingleUpdate($hash,"state", "Ok", 1); + } elsif ( $clientmsg->{direction} eq "i2cwrite" && defined($clientmsg->{data}) ) { #readings aktualisieren wenn uebertragung ok + I2C_PCA9685_UpdReadings($hash, $clientmsg->{reg} , $clientmsg->{data}); + readingsSingleUpdate($hash,"state", "Ok", 1); + + } else { + readingsSingleUpdate($hash,"state", "transmission error", 1); + Log3 $hash, 3, "$name: failure in message from $pname"; + Log3 $hash, 3, (defined($clientmsg->{direction}) ? "Direction: " . $clientmsg->{direction} : "Direction: undef"). + (defined($clientmsg->{i2caddress}) ? " I2Caddress: " . sprintf("0x%.2X", $clientmsg->{i2caddress}) : " I2Caddress: undef"). + (defined($clientmsg->{reg}) ? " Register: " . sprintf("0x%.2X", $clientmsg->{reg}) : " Register: undef"). + (defined($clientmsg->{data}) ? " Data: " . sprintf("0x%.2X", $clientmsg->{data}) : " Data: undef"). + (defined($clientmsg->{received}) ? " received: " . sprintf("0x%.2X", $clientmsg->{received}) : " received: undef"); + } + } else { + readingsSingleUpdate($hash,"state", "transmission error", 1); + Log3 $hash, 3, "$name: failure in message from $pname"; + Log3 $hash, 3, (defined($clientmsg->{direction}) ? "Direction: " . $clientmsg->{direction} : "Direction: undef"). + (defined($clientmsg->{i2caddress}) ? " I2Caddress: " . sprintf("0x%.2X", $clientmsg->{i2caddress}) : " I2Caddress: undef"). + (defined($clientmsg->{reg}) ? " Register: " . sprintf("0x%.2X", $clientmsg->{reg}) : " Register: undef"). + (defined($clientmsg->{data}) ? " Data: " . sprintf("0x%.2X", $clientmsg->{data}) : " Data: undef"). + (defined($clientmsg->{received}) ? " received: " . sprintf("0x%.2X", $clientmsg->{received}) : " received: undef"); + } +} +############################################################################# +sub I2C_PCA9685_CalcVal($@) { # Readings aus Registerwerten berechnen + my ($dimcount, @reginh) = @_; + my $delay = undef; + my $dimval; + if ($reginh[1] > 15) { + $dimval = "on"; + } elsif ($reginh[3] > 15) { + $dimval = "off"; + } else { + $delay = $reginh[1] * 256 + $reginh[0]; + my $temp = $reginh[3] * 256 + $reginh[2]; + $dimval = $temp - $delay + (( $temp > $delay ) ? 0 : 4096); + if ($dimcount < 4095) { #Wert anpassen bei anderem Faktor + $dimval = int($dimval * $dimcount / 4095); + $delay = int($delay * $dimcount / 4095); + } + } + return $dimval, $delay; +} +############################################################################# +sub I2C_PCA9685_UpdReadings($$$) { # vom IODev gesendete Werte in Readings/Internals schreiben ---- WAS IST WENN MEHRERE PORTS AM STÜCK ABGEFRAGT WERDEN??? (evtl mit einzelabfrage zusammen) + my ($hash, $reg, $inh) = @_; + my $name = $hash->{NAME}; + #$inh = hex($inh); + #Log3 $hash, 1, "$name UpdReadings Start Register: " .sprintf("0x%.2X", $reg).", Inhalt: $inh"; + my @reginh = split(" ", $inh); + my $dimstep = AttrVal($name, "dimstep", "1"); + my $dimcount = AttrVal($name, "dimcount", "4095"); + my $delay = undef; + my $dimval; + readingsBeginUpdate($hash); + if ($reg == 250 && @reginh == 4) { # wenn All + ($dimval, $delay) = I2C_PCA9685_CalcVal($dimcount, @reginh); + foreach (0..15) { + readingsBulkUpdate($hash, 'Port'.sprintf('%02d', $_) , $dimval) if (ReadingsVal($name, 'Port'.sprintf('%02d', $_), "failure") ne $dimval); #nur wenn Wert geaendert + readingsBulkUpdate($hash, 'Port_d'.sprintf('%02d', $_) , $delay) if (defined $delay && ReadingsVal($name, 'Port_d'.$hash->{confregs}, "failure") ne $delay); #nur wenn Wert geaendert + } + } elsif ( $reg < 70 && $reg > 5 && @reginh == 4) { #Wenn PortRegister + my $port = sprintf ('%02d', ($reg - 6) / 4); + ($dimval, $delay) = I2C_PCA9685_CalcVal($dimcount, @reginh); + readingsBulkUpdate($hash, 'Port'.$port , $dimval) if (ReadingsVal($name, 'Port'.$port, "failure") ne $dimval); #nur wenn Wert geaendert + readingsBulkUpdate($hash, 'Port_d'.$port , $delay) if (defined $delay && ReadingsVal($name, 'Port_d'.$port, "failure") ne $delay); #nur wenn Wert geaendert + + # WAS IST WENN MEHRERE PORTS AM STÜCK ABGEFRAGT WERDEN??? + } elsif ( $reg < 70 && $reg > 5 && @reginh > 4 ) { + for (my $i = 0; $i < @reginh; $i++) { + next unless ( ($reg + $i - 2) / 4 =~ m/^\d+$/ ); + my @regpart = [ $reginh[$i], $reginh[$i + 1], $reginh[$i + 2], $reginh[$i + 3] ]; + my $port = sprintf ('%02d', ($reg + $i - 6) / 4); + ($dimval, $delay) = I2C_PCA9685_CalcVal($dimcount, @regpart); + readingsBulkUpdate($hash, 'Port'.$port , $dimval) if (ReadingsVal($name, 'Port'.$port, "failure") ne $dimval); #nur wenn Wert geaendert + readingsBulkUpdate($hash, 'Port_d'.$port , $delay) if (defined $delay && ReadingsVal($name, 'Port_d'.$port, "failure") ne $delay); #nur wenn Wert geaendert + Log3 $hash, 5, "$name: lese mehrere Ports - Reg: $reg ; i: $i; |$regpart[0]|$regpart[1]|$regpart[2]|$regpart[3]|"; + $i += 3; + } + } elsif ($reg == 254) { #wenn Frequenz Register + my $clock = AttrVal($name, "extClock", 25); + $hash->{Frequency} = sprintf( "0x%.1f", $clock * 1000000 / (4096 * ($inh + 1)) ) . " Hz"; + } elsif ( $reg >= 0 && $reg < 6 ) { #Konfigurations Register + $hash->{confregs}{$reg} = $inh; + #folgendes evtl noch weg + $hash->{CONF} = (defined $hash->{confregs}{0} ? sprintf('0x%.2X ', $hash->{confregs}{0}) : "0x__ ") . + (defined $hash->{confregs}{1} ? sprintf('0x%.2X ', $hash->{confregs}{1}) : "0x__ ") . + (defined $hash->{confregs}{2} ? sprintf('0x%.2X ', $hash->{confregs}{2}) : "0x__ ") . + (defined $hash->{confregs}{3} ? sprintf('0x%.2X ', $hash->{confregs}{3}) : "0x__ ") . + (defined $hash->{confregs}{4} ? sprintf('0x%.2X ', $hash->{confregs}{4}) : "0x__ ") . + (defined $hash->{confregs}{5} ? sprintf('0x%.2X ', $hash->{confregs}{5}) : "0x__ "); + } + readingsEndUpdate($hash, 1); + return; +} +############################################################################# + +1; + +=pod +=begin html + + +

I2C_PCA9685

+(en | de) +
    + + Provides an interface to the PCA9685 I2C 16 channel PWM IC. + The I2C messages are send through an I2C interface module like RPII2C, FRM + or NetzerI2C so this device must be defined first.
    + attribute IODev must be set
    +
    + Define +
      + define <name> I2C_PCA9685 <I2C Address>
      + where <I2C Address> can be written as decimal value or 0xnn
      +
    + + + Set +
      + set <name> <port> <value> [<delay>]

      +
    • where <port> is one of Port00 to Port15
      + and <value> one of
      +
        + + off
        + on
        + 0..4095
        +
        +
      + <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
      +
    • + +
      + Examples: +
        + set mod1 Port04 543
        + set mod1 Port14 434 765
        +

      +
    + + + Get +
      + get <name> +

      + refreshes all readings +

    + + + Attributes +
      +
    • subadr1,subadr2,subadr3,allcalladr
      + Alternative slave addresses, if you want to control more than one PCA9685 with one define + Respective flag in modreg1 must be set as well
      + Default: subadr1=113,subadr2=114,subadr3=116,allcalladr=112, valid values: valid I2C Address

      +
    • +
    • OnStartup
      + Comma separated list of output ports/PWM registers and their desired state after start
      + Without this atribut all output ports will set to last state
      + Default: -, valid values: <port>=on|off|0..4095|last where <port> = 0 - 15

      +
    • +
    • prescale
      + Sets PWM Frequency. The Formula is: Fx = 25MHz/(4096 * (prescale + 1)) The corresponding frequency value is shown under internals (valid for the internal 25MHz clock).
      + Default: 30 (200Hz), valid values: 0-255

      +
    • +
    • modreg1
      + Comma separated list of: +
        +
      • EXTCLK
        + If set the an external connected clock will be used instead of the internal 25MHz oscillator +
      • +
      • SUB1
        + If set the PCA9685 responds to I2C-bus subaddress 1. +
      • +
      • SUB2
        + If set the PCA9685 responds to I2C-bus subaddress 2. +
      • +
      • SUB3
        + If set the PCA9685 responds to I2C-bus subaddress 3. +
      • +
      • ALLCALL
        + If set the PCA9685 responds to I2C-bus allcall address. +
      • +
      +
    • +
    • modreg2
      + Comma separated list of: +
        +
      • INVRT
        + If set the Output logic state is inverted.
        +
      • +
      • OCH
        + If set the outputs changes on ACK (after every byte sent).
        + Otherwise the output changes on STOP command (bus write action finished)
        +
      • +
      • OUTDRV
        + If set the outputs are configured with a totem pole structure.
        + Otherwise the outputs are configured with open-drain.
        +
      • + Behaviour when OE = 1 (if OE = 0 the output will act according OUTDRV configuration): +
      • OUTNE0
        + If set:
        + LEDn = 1 when OUTDRV = 1
        + LEDn = high-impedance when OUTDRV = 0
        + If not set: + LEDn = 0.
        +
      • +
      • OUTNE1
        + LEDn = high-impedance.
        + OUTNE1 overrides OUTNE0

        +
      • +
      +
    • +
    • IODev
    • +
    • ignore
    • +
    • do_not_notify
    • +
    • showtime
    • +
    +
    +
+ +=end html + +=begin html_DE + + +

I2C_PCA9685

+(en | de) +
    + + Ermöglicht die Verwendung eines PCA9685 I2C 16 Kanal PWM IC. + I2C-Botschaften werden über ein I2C Interface Modul wie beispielsweise das RPII2C, FRM + oder NetzerI2C gesendet. Daher muss dieses vorher definiert werden.
    + Das Attribut IODev muss definiert sein.
    +
    + Define +
      + define <name> I2C_PCA9685 <I2C Address>
      + Der Wert <I2C Address> ist ein zweistelliger Hex-Wert im Format 0xnn oder eine Dezimalzahl
      +
    + + + Set +
      + set <name> <port> <value> [<delay>]

      +
    • Als <port> kann Port00 bis Port15 verwendet werden
      + <value> kann folgende Werte annehmen:
      +
        + + off
        + on
        + 0..4095
        +
        +
      + <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
      +
    • + +
      + Examples: +
        + set mod1 Port04 543
        + set mod1 Port14 434 765
        +

      +
    + + + Get +
      + get <name> +

      + Aktualisierung aller Werte +

    + + + Attribute +
      +
    • subadr1,subadr2,subadr3,allcalladr
      + Alternative slave Adressen, if you want to control more than one PCA9685 with one define + Zusätzlich zu diesen Registern müssen die Passenden Bits in modreg1 gesetzt werden.
      + Standard: subadr1=113,subadr2=114,subadr3=116,allcalladr=112, gültige Werte: I2C Adresse

      +
    • +
    • OnStartup
      + Comma separated list of output ports/PWM registers and their desired state after start
      + Without this atribut all output ports will set to last state
      + Standard: last, gültige Werte: <port>=on|off|0..4095|last wobei <port> = 0 - 15

      +
    • +
    • prescale
      + Sets PWM Frequency. The Formula is: Fx = 25MHz/(4096 * (prescale + 1)) The corresponding frequency value is shown under internals (valid for the internal 25MHz clock).
      + Standard: 30 (200Hz), gültige Werte: 0-255

      +
    • +
    • modreg1
      + Durch Komma getrennte Liste von: +
        +
      • EXTCLK
        + Anstelle des internen 25MHz Oszillators wird ein extern Angeschlossener verwendet. +
      • +
      • SUB1
        + Wenn gesetzt, antwortet der PCA9685 auf I2C-bus Subadresse 1. +
      • +
      • SUB2
        + Wenn gesetzt, antwortet der PCA9685 auf I2C-bus Subadresse 2. +
      • +
      • SUB3
        + Wenn gesetzt, antwortet der PCA9685 auf I2C-bus Subadresse 3. +
      • +
      • ALLCALL
        + Wenn gesetzt, antwortet der PCA9685 auf I2C-bus Allcall Adresse. +
      • +
      +
    • +
    • modreg2
      + Durch Komma getrennte Liste von: +
        +
      • INVRT
        + Wenn gesetzt, werden die Ausgänge invertiert.
        +
      • +
      • OCH
        + If set the outputs changes on ACK (after every byte sent).
        + Otherwise the output changes on STOP command (bus write action finished)
        +
      • +
      • OUTDRV
        + Wenn gesetzt, werden die Ausgänge als totem pole konfiguriert.
        + Andernfalls sind sie open-drain.
        +
      • + Verhalten bei OE = 1 (wenn OE = 0 verhalten sich die Ausgänge wie in OUTDRV eingestellt): +
      • OUTNE0
        + Wenn gesetzt:
        + LEDn = 1 wenn OUTDRV = 1
        + LEDn = hochohmig wenn OUTDRV = 0
        + Wenn nicht gesetzt: + LEDn = 0.
        +
      • +
      • OUTNE1
        + LEDn = hochohmig.
        + Wenn OUTNE1 gesetzt wird OUTNE0 ignoriert.

        +
      • +
      +
    • +
    • IODev
    • +
    • ignore
    • +
    • do_not_notify
    • +
    • showtime
    • +
    +
    +
+ +=end html_DE + +=cut \ No newline at end of file diff --git a/fhem/FHEM/52_I2C_SHT21.pm b/fhem/FHEM/52_I2C_SHT21.pm index 2a38eaf58..99813fe34 100644 --- a/fhem/FHEM/52_I2C_SHT21.pm +++ b/fhem/FHEM/52_I2C_SHT21.pm @@ -6,10 +6,6 @@ package main; use strict; use warnings; -use Time::HiRes qw(usleep); -use Scalar::Util qw(looks_like_number); -#use Error qw(:try); - use constant { SHT21_I2C_ADDRESS => '0x40', }; @@ -69,8 +65,8 @@ sub I2C_SHT21_Init($$) { } if (defined (my $address = shift @$args)) { - $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address; - return "$name I2C Address not valid" unless ($address < 128 && $address > 3); + $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address; + return "$name I2C Address not valid" unless ($address < 128 && $address > 3); } else { $hash->{I2C_Address} = hex(SHT21_I2C_ADDRESS); } @@ -108,7 +104,6 @@ sub I2C_SHT21_Catch($) { return undef; } - sub I2C_SHT21_Attr (@) {# hier noch Werteueberpruefung einfuegen my ($command, $name, $attr, $val) = @_; my $hash = $defs{$name}; @@ -121,8 +116,6 @@ sub I2C_SHT21_Attr (@) {# hier noch Werteueberpruefung einfuegen } } if ($attr eq 'poll_interval') { - #my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0; - if ($val > 0) { RemoveInternalTimer($hash); InternalTimer(1, 'I2C_SHT21_Poll', $hash, 0); @@ -160,8 +153,7 @@ sub I2C_SHT21_Set($@) { } if ($cmd eq 'readValues') { - I2C_SHT21_readTemperature($hash); - I2C_SHT21_readHumidity($hash); + I2C_SHT21_triggerTemperature($hash); } } @@ -174,36 +166,34 @@ sub I2C_SHT21_Undef($$) { sub I2C_SHT21_I2CRec ($$) { my ($hash, $clientmsg) = @_; - my $name = $hash->{NAME}; - my $phash = $hash->{IODev}; - my $pname = $phash->{NAME}; - while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals fuer alle Keys in $clientmsg die mit dem physical Namen beginnen - $hash->{$k} = $v if $k =~ /^$pname/ ; - } - #alte Variante zur Temp Hum Unterscheidung - #if ( $clientmsg->{direction} && $clientmsg->{type} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) { - # if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) { - # Log3 $hash, 5, "empfangen: $clientmsg->{received}"; - # I2C_SHT21_GetTemp ($hash, $clientmsg->{received}) if $clientmsg->{type} eq "temp" && $clientmsg->{nbyte} == 2; - # I2C_SHT21_GetHum ($hash, $clientmsg->{received}) if $clientmsg->{type} eq "hum" && $clientmsg->{nbyte} == 2; - # } - #} + my $name = $hash->{NAME}; + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals fuer alle Keys in $clientmsg die mit dem physical Namen beginnen + $hash->{$k} = $v if $k =~ /^$pname/ ; + } # Bit 1 of the two LSBs indicates the measurement type (‘0’: temperature, ‘1’ humidity) if ( $clientmsg->{direction} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) { if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) { Log3 $hash, 5, "empfangen: $clientmsg->{received}"; my @raw = split(" ",$clientmsg->{received}); - I2C_SHT21_GetTemp ($hash, $clientmsg->{received}) if !($raw[1] & 2) && $clientmsg->{nbyte} == 2; - I2C_SHT21_GetHum ($hash, $clientmsg->{received}) if ($raw[1] & 2) && $clientmsg->{nbyte} == 2; + I2C_SHT21_GetTemp ($hash, $clientmsg->{received}) if !($raw[1] & 2) && $clientmsg->{nbyte} == 3; + I2C_SHT21_GetHum ($hash, $clientmsg->{received}) if ($raw[1] & 2) && $clientmsg->{nbyte} == 3; } } } sub I2C_SHT21_GetTemp ($$) { my ($hash, $rawdata) = @_; - my @raw = split(" ",$rawdata); - my $temperature = $raw[0] << 8 | $raw[1]; + my @raw = split(" ",$rawdata); + I2C_SHT21_triggerHumidity($hash); #schnell noch Feuchtemessung anstoßen. + if ( defined (my $crc = I2C_SHT21_CheckCrc(@raw)) ) { #CRC Test + Log3 $hash, 2, "CRC error temperature data(MSB LSB Chechsum): $rawdata, Checksum calculated: $crc"; + $hash->{CRCErrorTemperature}++; + return; + } + my $temperature = $raw[0] << 8 | $raw[1]; $temperature = ( 175.72 * $temperature / 2**16 ) - 46.85; $temperature = sprintf( '%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f', @@ -214,10 +204,15 @@ sub I2C_SHT21_GetTemp ($$) { sub I2C_SHT21_GetHum ($$) { my ($hash, $rawdata) = @_; - my @raw = split(" ",$rawdata); + my @raw = split(" ",$rawdata); + if ( defined (my $crc = I2C_SHT21_CheckCrc(@raw)) ) { #CRC Test + Log3 $hash, 2, "CRC error humidity data(MSB LSB Chechsum): $rawdata, Checksum calculated: $crc"; + $hash->{CRCErrorHumidity}++; + return; + } my $name = $hash->{NAME}; my $temperature = ReadingsVal($name,"temperature","0"); - + my $humidity = $raw[0] << 8 | $raw[1]; $humidity = ( 125 * $humidity / 2**16 ) - 6; $humidity = sprintf( @@ -230,54 +225,72 @@ sub I2C_SHT21_GetHum ($$) { 'state', 'T: ' . $temperature . ' H: ' . $humidity ); - #readingsBulkUpdate($hash, 'temperature', $temperature); readingsBulkUpdate($hash, 'humidity', $humidity); readingsEndUpdate($hash, 1); } - -sub I2C_SHT21_readTemperature($) { +sub I2C_SHT21_triggerTemperature($) { my ($hash) = @_; my $name = $hash->{NAME}; return "$name: no IO device defined" unless ($hash->{IODev}); my $phash = $hash->{IODev}; my $pname = $phash->{NAME}; - # Write 0xF3 to device. This requests a temperature reading + # Write 0xF3 to device. This requests a "no hold master" temperature reading my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" }; - $i2creq->{data} = hex("F3"); + $i2creq->{data} = hex("F3"); CallFn($pname, "I2CWrtFn", $phash, $i2creq); - usleep(85000); #fuer 14bit - - # Read the two byte result from device - my $i2cread = { i2caddress => $hash->{I2C_Address}, direction => "i2cread" }; - $i2cread->{nbyte} = 2; - $i2cread->{type} = "temp"; - CallFn($pname, "I2CWrtFn", $phash, $i2cread); - + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_SHT21_readValue', $hash, 0); #nach 1s Wert lesen (85ms sind fuer 14bit Wert notwendig) return; } -sub I2C_SHT21_readHumidity($) { +sub I2C_SHT21_triggerHumidity($) { my ($hash) = @_; my $name = $hash->{NAME}; return "$name: no IO device defined" unless ($hash->{IODev}); my $phash = $hash->{IODev}; my $pname = $phash->{NAME}; - # Write 0xF5 to device. This requests a humidity reading + # Write 0xF5 to device. This requests a "no hold master" humidity reading my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" }; - $i2creq->{data} = hex("F5"); + $i2creq->{data} = hex("F5"); CallFn($pname, "I2CWrtFn", $phash, $i2creq); - usleep(39000); #fuer 12bit + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_SHT21_readValue', $hash, 0); #nach 1s Wert lesen (39ms sind fuer 12bit Wert notwendig) + return; +} - # Read the two byte result from device +sub I2C_SHT21_readValue($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + return "$name: no IO device defined" unless ($hash->{IODev}); + my $phash = $hash->{IODev}; + my $pname = $phash->{NAME}; + + # Reset Internal Timer to Poll Sub + RemoveInternalTimer($hash); + my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); + InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_SHT21_Poll', $hash, 0) if ($pollInterval > 0); + # Read the two byte result from device + 1byte CRC my $i2cread = { i2caddress => $hash->{I2C_Address}, direction => "i2cread" }; - $i2cread->{nbyte} = 2; - $i2cread->{type} = "hum"; + $i2cread->{nbyte} = 3; CallFn($pname, "I2CWrtFn", $phash, $i2cread); - return; # $retVal; + return; +} + +sub I2C_SHT21_CheckCrc(@) { + my @data = @_; + my $crc = 0; + my $poly = 0x131; #P(x)=x^8+x^5+x^4+1 = 100110001 + for (my $n = 0; $n < (scalar(@data) - 1); ++$n) { + $crc ^= $data[$n]; + for (my $bit = 8; $bit > 0; --$bit) { + $crc = ($crc & 0x80 ? $poly : 0 ) ^ ($crc << 1); + } + } + return ($crc = $data[2] ? undef : $crc); } sub I2C_SHT21_DbLog_splitFn($) { @@ -326,14 +339,10 @@ sub I2C_SHT21_DbLog_splitFn($) { Set the polling interval in minutes to query data from sensor
Default: 5, valid values: 1,2,5,10,20,30

-
  • roundHumidityDecimal
    - Number of decimal places for humidity value
    +
  • roundHumidityDecimal, roundTemperatureDecimal
    + Number of decimal places for humidity or temperature value
    Default: 1, valid values: 0 1 2

  • -
  • roundTemperatureDecimal
    - Number of decimal places for temperature value
    - Default: 1, valid values: 0,1,2

    -
  • IODev
  • do_not_notify
  • showtime
  • @@ -372,14 +381,10 @@ sub I2C_SHT21_DbLog_splitFn($) { Aktualisierungsintervall aller Werte in Minuten.
    Standard: 5, gültige Werte: 1,2,5,10,20,30

    -
  • roundHumidityDecimal
    - Anzahl Dezimalstellen für den Feuchtewert
    +
  • roundHumidityDecimal, roundTemperatureDecimal
    + Anzahl Dezimalstellen für den Feuchte-, oder Temperaturwert
    Standard: 1, gültige Werte: 0 1 2

  • -
  • roundTemperatureDecimal
    - Anzahl Dezimalstellen für den Temperaturwert
    - Standard: 1, gültige Werte: 0,1,2

    -
  • IODev
  • do_not_notify
  • showtime
  • diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index 60fae2b4d..e0cf58fcc 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -188,6 +188,7 @@ FHEM/52_I2C_MCP23008 klausw http://forum.fhem.de Sonstige FHEM/52_I2C_MCP23017 klausw http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_MCP342x klausw http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_PCA9532 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_SHT21 klausw http://forum.fhem.de Sonstige Systeme FHEM/52_I2C_TSL2561 kaihs http://forum.fhem.de Sonstige Systeme