| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- ##############################################
- # $Id: 52_I2C_PCF8574.pm 12059 2016-08-22 21:14:59Z klauswitt $
- package main;
- use strict;
- use warnings;
- use SetExtensions;
- use Scalar::Util qw(looks_like_number);
- my %setsP = (
- 'off' => 0,
- 'on' => 1,
- );
- sub I2C_PCF8574_Initialize($) {
- my ($hash) = @_;
- #$hash->{Match} = ".*";
- $hash->{DefFn} = "I2C_PCF8574_Define";
- $hash->{InitFn} = 'I2C_PCF8574_Init';
- $hash->{AttrFn} = "I2C_PCF8574_Attr";
- $hash->{SetFn} = "I2C_PCF8574_Set";
- $hash->{StateFn} = "I2C_PCF8574_State";
- $hash->{GetFn} = "I2C_PCF8574_Get";
- $hash->{UndefFn} = "I2C_PCF8574_Undef";
- $hash->{ParseFn} = "I2C_PCF8574_Parse";
- $hash->{I2CRecFn} = "I2C_PCF8574_I2CRec";
- $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:1,0 showtime:1,0 ".
- "poll_interval OnStartup ".
- "InputPorts:multiple-strict,0,1,2,3,4,5,6,7 ".
- "$readingFnAttributes";
- }
- ###################################
- sub I2C_PCF8574_Set($@) { #
- my ($hash, @a) = @_;
- my $name =$a[0];
- my $cmd = $a[1];
- my $val = $a[2];
- my @inports = sort(split( " ",AttrVal($name, "InputPorts", "")));
- my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" );
- if ( $cmd && $cmd =~ m/^P(ort|)((0|)[0-7])(,(P|)(ort|)((0|)[0-7])){0,7}$/i) {
- return "wrong value: $val for \"set $name $cmd\" use one of: off, on"
- unless(exists($setsP{$val}));
- my @scmd = split(",", $cmd);
- my $msg = undef;
- foreach (@scmd) {
- $_ =~ tr/[a-zA-Z]//d; #Nummer aus String extrahieren
- $msg .= (defined $msg ? "," : "") . "Port" . $_ if ( $_ ~~ @inports ); #Pruefen ob entsprechender Port Input ist
- }
- return "$name error: $msg is defined as input" if $msg;
- my $sbyte = 0;
- foreach (0..7) {
- if ($_ ~~ @inports) { #Port der als Input konfiguriert ist wird auf 1 gesetzt
- $sbyte += 1 << (1 * $_);
- } elsif( $_ ~~ @scmd ) { #Port der geaendert werden soll
- $sbyte += $setsP{$val} << (1 * $_);
- } else { #alle anderen Portwerte werden den Readings entnommen
- $sbyte += $setsP{ReadingsVal($name,'Port'.$_,"off")} << (1 * $_); #->sonst aus dem Reading holen
- }
- }
- $sendpackage{data} = $sbyte;
- } elsif ( $cmd && $cmd eq "setfromreading" ) {
- my $sbyte = 0;
- foreach (0..7) {
- if ($_ ~~ @inports) { #Port der als Input konfiguriert ist wird auf 1 gesetzt
- $sbyte += 1 << (1 * $_);
- } else { #alle anderen Portwerte werden den Readings entnommen
- $sbyte += $setsP{ReadingsVal($name,'Port'.$_,"off")} << (1 * $_);
- }
- }
- $sendpackage{data} = $sbyte;
- } else {
- my $list = undef;
- foreach (0..7) {
- next if ( $_ ~~ @inports ); #Inputs ueberspringen
- $list .= "Port" . $_ . ":" . join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) . " ";
- }
- return "Unknown argument $a[1], choose one of " . $list if defined $list;
- return "Unknown argument $a[1]";
- }
- return "$name: no IO device defined" unless ($hash->{IODev});
- my $phash = $hash->{IODev};
- my $pname = $phash->{NAME};
- CallFn($pname, "I2CWrtFn", $phash, \%sendpackage);
- ##########################################################
- # IOWrite($hash, \%sendpackage);
- ##########################################################
- ##########################################################
- # Look for all devices with the same code, and set state, timestamp
- #my $code = "$hash->{I2C_Address} $hash->{BTN}";
- #my $code = "$hash->{NAME} $hash->{I2C_Address}";
- #my $tn = TimeNow();
- #my $defptr = $modules{I2C_PCF8574}{defptr}{$code};
- #foreach my $n (keys %{ $defptr }) {
- # readingsSingleUpdate($defptr->{$n}, "state", $v, 1);
- # }
- ##########################################################
- return undef;
- }
- ###################################
- sub I2C_PCF8574_Get($@) {
- my ($hash, @a) = @_;
- my $name =$a[0];
- my %sendpackage = ();
- #%sendpackage = ( direction => "i2cread", id => (defined( $hash->{ID} )? $hash->{ID} : "00"), i2caddress => $hash->{I2C_Address});
- %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cread");
- return "$name: no IO device defined" unless ($hash->{IODev});
- #neu: ueber CallFn auf eigene Funktion
- my $phash = $hash->{IODev};
- my $pname = $phash->{NAME};
- CallFn($pname, "I2CWrtFn", $phash, \%sendpackage);
- #alt: fuer IOWrite
- #IOWrite($hash, \%sendpackage);
- }
- ###################################
- sub I2C_PCF8574_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_PCF8574_Init($hash,\@def) if (defined ($hash->{IODev}));
- }
- }
- if ($attr eq 'poll_interval') {
- if ( defined($val) ) {
- if ( looks_like_number($val) && $val > 0) {
- RemoveInternalTimer($hash);
- InternalTimer(1, 'I2C_PCF8574_Poll', $hash, 0);
- } else {
- $msg = "$hash->{NAME}: Wrong poll intervall defined. poll_interval must be a number > 0";
- }
- } else {
- RemoveInternalTimer($hash);
- }
- } elsif ($attr && $attr eq "InputPorts") {
- if ( defined($val) ) {
- my @inp = split(" ", $val);
- foreach (@inp) {
- $msg = "wrong value: $_ for \"set $name $attr\" use space separated numbers 0-7" unless ($_ >= 0 && $_ < 8);
- }
- }
- } elsif ($attr && $attr eq "OnStartup") {
- if (defined $val) {
- foreach (split (/,/,$val)) {
- my @pair = split (/=/,$_);
- $msg = "wrong value: $_ for \"attr $hash->{NAME} $attr\" use comma separated <port>=on|off|last where <port> = 0 - 7"
- unless ( scalar(@pair) == 2 && ($pair[0] =~ m/^[0-7]$/i && ( $pair[1] eq "last" || exists($setsP{$pair[1]}) ) ) );
- }
- }
- }
- return $msg
- }
- ###################################
- sub I2C_PCF8574_Define($$) { #
- my ($hash, $def) = @_;
- my @a = split("[ \t]+", $def);
- $hash->{STATE} = 'defined';
- if ($main::init_done) {
- eval { I2C_PCF8574_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); };
- return I2C_PCF8574_Catch($@) if $@;
- }
- return undef;
- }
- ###################################
- sub I2C_PCF8574_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 <name> I2C_PCA9532 <i2caddress>";
- }
-
- if (defined (my $address = shift @$args)) {
- $hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address;
- } else {
- return "$name I2C Address not valid";
- }
-
- #fuer die Nutzung von IOWrite
- #my $code = ( defined( $hash->{ID} )? $hash->{ID} : "00" ) . " " . $hash->{I2C_Address};
- #my $ncode = 1;
- #my $name = $a[0];
- #$hash->{CODE}{$ncode++} = $code;
- #$modules{I2C_PCF8574}{defptr}{$code}{$name} = $hash;
- AssignIoPort($hash);
- $hash->{STATE} = 'Initialized';
- I2C_PCF8574_Set($hash, $name, "setfromreading");
-
- return;
- }
- ###################################
- sub I2C_PCF8574_Catch($) {
- my $exception = shift;
- if ($exception) {
- $exception =~ /^(.*)( at.*FHEM.*)$/;
- return $1;
- }
- return undef;
- }
- ###################################
- sub I2C_PCF8574_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-7]$/i) {
- my $po = $sname; #noch ändern
- $po =~ tr/[a-zA-Z]//d; #Nummer aus String extrahieren
-
- my @inports = sort(split(/ /,AttrVal($hash->{NAME}, "InputPorts", "")));
- unless ( $po ~~ @inports) {
- my %onstart = split /[,=]/, AttrVal($hash->{NAME}, "OnStartup", "");
- if ( exists($onstart{$po}) && exists($setsP{$onstart{$po}})) {
- Log3 $hash, 5, "$hash->{NAME}: $sname soll auf $onstart{$po} gesetzt werden";
- readingsSingleUpdate($hash,$sname, $onstart{$po}, 1);
- } else {
- Log3 $hash, 5, "$hash->{NAME}: $sname soll auf Altzustand: $sval gesetzt werden";
- $hash->{READINGS}{$sname}{VAL} = $sval;
- $hash->{READINGS}{$sname}{TIME} = $tim;
- }
-
- } else {
- Log3 $hash, 5, "$hash->{NAME}: $sname ist Eingang";
- }
- }
- return undef;
- }
- ###################################
- sub I2C_PCF8574_Undef($$) { #
- my ($hash, $name) = @_;
- if ( defined (AttrVal($hash->{NAME}, "poll_interval", undef)) ) {
- RemoveInternalTimer($hash);
- }
- #foreach my $c (keys %{ $hash->{CODE} } ) {
- # $c = $hash->{CODE}{$c};
- # my $c = ( defined( $hash->{ID} )? $hash->{ID} : "00" ) . " " . $hash->{I2C_Address};
- # As after a rename the $name my be different from the $defptr{$c}{$n}
- # we look for the hash.
- # foreach my $dname (keys %{ $modules{I2C_PCF8574}{defptr}{$c} }) {
- # delete($modules{I2C_PCF8574}{defptr}{$c}{$dname})
- # if($modules{I2C_PCF8574}{defptr}{$c}{$dname} == $hash);
- # }
- # }
- return undef;
- }
- ###################################
- sub I2C_PCF8574_Poll($) { #for attr poll_intervall -> readout pin values
- my ($hash) = @_;
- my $name = $hash->{NAME};
- # Read values
- I2C_PCF8574_Get($hash, $name);
- my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
- if ($pollInterval > 0) {
- InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_PCF8574_Poll', $hash, 0);
- }
- }
- ###################################
- sub I2C_PCF8574_I2CRec($@) { # ueber CallFn vom physical 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/ ;
- }
- my $sval;
- if ($clientmsg->{direction} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok") {
- readingsBeginUpdate($hash);
- if ($clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received})) { # =~ m/^(0x|)[0-9A-F]{1,2}$/xi) {
- foreach (0..7) {
- #$sval = hex($clientmsg->{received}) & (1 << $_);
- $sval = $clientmsg->{received} & (1 << $_);
- $sval = $sval == 0 ? "off" :"on";
- readingsBulkUpdate($hash, 'Port'.$_ , $sval) if (ReadingsVal($name,'Port'.$_,"") ne $sval);
- }
- readingsBulkUpdate($hash, 'state', $clientmsg->{received});
- } elsif ($clientmsg->{direction} eq "i2cwrite" && defined($clientmsg->{data})) { # =~ m/^(0x|)[0-9A-F]{1,2}$/xi) {
- my @inports = sort(split( " ",AttrVal($name, "InputPorts", "")));
- foreach (0..7) {
- #$sval = hex($clientmsg->{data}) & (1 << $_);
- $sval = $clientmsg->{data} & (1 << $_);
- $sval = $sval == 0 ? "off" :"on";
- readingsBulkUpdate($hash, 'Port'.$_ , $sval) unless (ReadingsVal($name,'Port'.$_,"") eq $sval || $_ ~~ @inports );
- }
- readingsBulkUpdate($hash, 'state', $clientmsg->{data});
- }
- #readingsBulkUpdate($hash, 'state', join(" ", $clientmsg->{received}));
- readingsEndUpdate($hash, 1);
- }
- }
- ###################################
- sub I2C_PCF8574_Parse($$) { #wird ueber dispatch vom physical device aufgerufen (dispatch wird im mom nicht verwendet)
- my ($hash, $msg) = @_;
- my($sid, $addr, @msg) = split(/ /,$msg);
- #Log3 $hash, 4, "Vom Netzerparse $hash->{NAME}: sid: $sid, Msg: @msg";
-
- my $def = $modules{I2C_PCF8574}{defptr}{"$sid $addr"};
- if($def) {
- my @list;
- foreach my $n (keys %{ $def }) {
- my $lh = $def->{$n}; # Hash bekommen
- $n = $lh->{NAME}; # It may be renamed
- return "" if(IsIgnored($n)); # Little strange.
- ################################################
- my $cde = join(" ",@msg);
- my $sval;
- readingsBeginUpdate($lh);
- if ( int(@msg) == 1) {
- for (my $i = 0; $i < 8; $i++) {
- #$sval = hex($cde) & (1 << $i);
- $sval = $cde & (1 << $i);
- $sval = $sval == 0 ? "0" :"1";
- readingsBulkUpdate($lh, 'P'.$i , $sval) if (ReadingsVal($n,'P'.$i,2) ne $sval);
- }
- }
- readingsBulkUpdate($lh, 'state', join(" ", @msg));
- readingsEndUpdate($lh, 1);
- ################################################
- Log3 $n, 4, "I2C_PCF8574 $n $cde";
- push(@list, $n);
- }
- return @list;
- } else {
- Log3 $hash, 3, "I2C_PCF8574 Unknown device $addr Id $sid";
- #return "UNDEFINED I2C_PCF8574_$addr$sid I2C_PCF8574 $addr $sid";
- }
- }
- ###################################
- 1;
- =pod
- =item device
- =item summary controls/reads GPIOs from an via I2C connected PCF8574 port extender
- =item summary_DE steuern/lesen der GPIOs eines über I2C angeschlossenen PCF8574
- =begin html
- <a name="I2C_PCF8574"></a>
- <h3>I2C_PCF8574</h3>
- (en | <a href="commandref_DE.html#I2C_PCF8574">de</a>)
- <ul>
- <a name="I2C_PCF8574"></a>
- Provides an interface to the PCA9532 8 channel port extender IC. On Raspberry Pi the Interrupt Pin can be connected to an GPIO and <a href="#RPI_GPIO">RPI_GPIO</a> can be used to get the port values if an interrupt occurs.<br>
- The I2C messages are send through an I2C interface module like <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
- or <a href="#NetzerI2C">NetzerI2C</a> so this device must be defined first.<br>
- <b>attribute IODev must be set</b><br>
- <a name="I2C_PCF8574Define"></a><br>
- <b>Define</b>
- <ul>
- <code>define <name> I2C_PCF8574 <I2C Address></code><br>
- where <code><I2C Address></code> is without direction bit<br>
- </ul>
- <a name="I2C_PCF8574Set"></a>
- <b>Set</b>
- <ul>
- <code>set <name> <port[,port[...]]> <value></code><br><br>
- <ul>
- <li><code><port></code> is one of Port0 to Port7 and <code><value></code> is one of:<br>
- <ul>
- <code>
- off<br>
- on<br>
- </code>
- </ul>
- </li>
- </ul>
- <br>
- Example:
- <ul>
- <code>set mod1 Port4 on</code><br>
- <code>set mod1 Port4,Port6 off</code><br>
- <code>set mod1 Port4,6 on</code><br>
- </ul><br>
- </ul>
- <a name="I2C_PCF8574Get"></a>
- <b>Get</b>
- <ul>
- <code>get <name></code>
- <br><br>
- refreshes all readings
- </ul><br>
- <a name="I2C_PCF8574Attr"></a>
- <b>Attributes</b>
- <ul>
- <li>poll_interval<br>
- Set the polling interval in minutes to query the GPIO's level<br>
- Default: -, valid values: decimal number<br><br>
- </li>
- <li>InputPorts<br>
- Space separated list of Portnumers that are used as Inputs<br>
- Ports in this list can't be written<br>
- Default: no, valid values: 0 1 2 .. 7<br><br>
- </li>
- <li>OnStartup<br>
- Comma separated list of output ports and their desired state after start<br>
- Without this atribut all output ports will set to last state<br>
- Default: -, valid values: <port>=on|off|last where <port> = 0 - 7<br><br>
- </li>
- <li><a href="#IODev">IODev</a></li>
- <li><a href="#ignore">ignore</a></li>
- <li><a href="#do_not_notify">do_not_notify</a></li>
- <li><a href="#showtime">showtime</a></li>
- </ul>
- <br>
- </ul>
- =end html
- =begin html_DE
- <a name="I2C_PCF8574"></a>
- <h3>I2C_PCF8574</h3>
- (<a href="commandref.html#I2C_PCF8574">en</a> | de)
- <ul>
- <a name="I2C_PCF8574"></a>
- Ermöglicht die Verwendung eines PCF8574 I2C 8 Bit Portexenders.
- Auf einem Raspberry Pi kann der Interrupt Pin des PCF8574 mit einem GPIO verbunden werden und ¨ber die Interrupt Funktionen von <a href="#RPI_GPIO">RPI_GPIO</a> l&aml;sst sich dann ein get für den PCF8574 bei Pegel&aml;nderung ausl&oml;sen.<br>
- I2C-Botschaften werden über ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</a>, <a href="#FRM">FRM</a>
- oder <a href="#NetzerI2C">NetzerI2C</a> gesendet. Daher muss dieses vorher definiert werden.<br>
- <b>Das Attribut IODev muss definiert sein.</b><br>
- <a name="I2C_PCF8574Define"></a><br>
- <b>Define</b>
- <ul>
- <code>define <name> I2C_PCF8574 <I2C Address></code><br>
- Der Wert <code><I2C Address></code> ist ohne das Richtungsbit<br>
- </ul>
- <a name="I2C_PCF8574Set"></a>
- <b>Set</b>
- <ul>
- <code>set <name> <port[,port[...]]> <value></code><br><br>
- <ul>
- <li><code><port></code> kann Port0 bis Port7 annehmen und <code><value></code> folgende Werte:<br>
- <ul>
- <code>
- off<br>
- on<br>
- </code>
- </ul>
- </li>
- </ul>
- <br>
- Beispiel:
- <ul>
- <code>set mod1 Port4 on</code><br>
- <code>set mod1 Port4,Port6 off</code><br>
- <code>set mod1 Port4,6 on</code><br>
- </ul><br>
- </ul>
- <a name="I2C_PCF8574Get"></a>
- <b>Get</b>
- <ul>
- <code>get <name></code>
- <br><br>
- Aktualisierung aller Werte
- </ul><br>
- <a name="I2C_PCF8574Attr"></a>
- <b>Attribute</b>
- <ul>
- <li>poll_interval<br>
- Aktualisierungsintervall aller Werte in Minuten.<br>
- Standard: -, gültige Werte: Dezimalzahl<br><br>
- </li>
- <li>InputPorts<br>
- Durch Leerzeichen getrennte Portnummern die als Inputs genutzt werden.<br>
- Ports in dieser Liste können nicht geschrieben werden.<br>
- Standard: no, gültige Werte: 0 1 2 .. 7<br><br>
- </li>
- <li>OnStartup<br>
- Durch Komma getrennte Output Ports und ihr gewünschter Status nach dem Start.<br>
- Ohne dieses Attribut werden alle Ausgänge nach dem Start auf den letzten Status gesetzt.<br>
- Standard: -, gültige Werte: <port>=on|off|last wobei <port> = 0 - 7<br><br>
- </li>
- <li><a href="#IODev">IODev</a></li>
- <li><a href="#ignore">ignore</a></li>
- <li><a href="#do_not_notify">do_not_notify</a></li>
- <li><a href="#showtime">showtime</a></li>
- </ul>
- <br>
- </ul>
- =end html_DE
- =cut
|