mirror of
https://github.com/fhem/fhem-mirror.git
synced 2025-01-31 18:59:33 +00:00
initial release
git-svn-id: https://svn.fhem.de/fhem/trunk@5323 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
parent
b20a649c7e
commit
bed08a8258
530
fhem/FHEM/00_RPII2C.pm
Normal file
530
fhem/FHEM/00_RPII2C.pm
Normal file
@ -0,0 +1,530 @@
|
||||
##############################################
|
||||
# $Id: 00_RPII2C.pm klausw
|
||||
package main;
|
||||
|
||||
use strict;
|
||||
use warnings;
|
||||
use Time::HiRes qw(gettimeofday);
|
||||
use Device::SMBus;
|
||||
|
||||
#my $clientsI2C = ":I2C_PC.*:I2C_SHT21:I2C_MCP23017:I2C_BMP180:";
|
||||
|
||||
my @clients = qw(
|
||||
I2C_LCD
|
||||
I2C_DS1307
|
||||
I2C_PC.*
|
||||
I2C_MCP23017
|
||||
I2C_BMP180
|
||||
I2C_SHT21
|
||||
);
|
||||
|
||||
my $gpioprg = "/usr/local/bin/gpio"; #WiringPi GPIO utility
|
||||
|
||||
|
||||
#my %matchListI2C = ( #kann noch weg?
|
||||
# "1:I2C_PCF8574"=> ".*",
|
||||
# "2:FHT" => "^81..(04|09|0d)..(0909a001|83098301|c409c401)..",
|
||||
#);
|
||||
|
||||
sub RPII2C_Initialize($) {
|
||||
my ($hash) = @_;
|
||||
|
||||
# Provider
|
||||
$hash->{Clients} = join (':',@clients);
|
||||
#$hash->{WriteFn} = "RPII2C_Write"; #wird vom client per IOWrite($@) aufgerufen
|
||||
$hash->{I2CWrtFn} = "RPII2C_Write"; #zum testen als alternative für IOWrite
|
||||
|
||||
# Normal devices
|
||||
$hash->{DefFn} = "RPII2C_Define";
|
||||
$hash->{UndefFn} = "RPII2C_Undef";
|
||||
$hash->{GetFn} = "RPII2C_Get";
|
||||
$hash->{SetFn} = "RPII2C_Set";
|
||||
#$hash->{AttrFn} = "RPII2C_Attr";
|
||||
$hash->{NotifyFn} = "RPII2C_Notify";
|
||||
$hash->{AttrList}= "do_not_notify:1,0 ignore:1,0 showtime:1,0 " .
|
||||
"$readingFnAttributes";
|
||||
}
|
||||
#####################################
|
||||
sub RPII2C_Define($$) { #
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t][ \t]*", $def);
|
||||
|
||||
unless(@a == 3) {
|
||||
my $msg = "wrong syntax: define <name> RPII2C <0|1>";
|
||||
Log3 undef, 2, $msg;
|
||||
return $msg;
|
||||
}
|
||||
if(-e $gpioprg) { #I2C Devices für FHEM User lesbar machen
|
||||
if(-x $gpioprg) {
|
||||
if(-u $gpioprg) {
|
||||
my $exp = $gpioprg.' load i2c';
|
||||
$exp = `$exp`;
|
||||
} else {
|
||||
Log3 $hash, 1, "file $gpioprg is not setuid";
|
||||
}
|
||||
} else {
|
||||
Log3 $hash, 1, "file $gpioprg is not executable";
|
||||
}
|
||||
} else {
|
||||
Log3 $hash, 1, "file $gpioprg doesnt exist";
|
||||
} #system "/usr/local/bin/gpio load i2c";
|
||||
|
||||
my $name = $a[0];
|
||||
my $dev = $a[2];
|
||||
|
||||
$hash->{NOTIFYDEV} = "global";
|
||||
|
||||
#$hash->{Clients} = $clientsI2C;
|
||||
#$hash->{MatchList} = \%matchListI2C;
|
||||
|
||||
if($dev eq "none") {
|
||||
Log3 $name, 1, "$name device is none, commands will be echoed only";
|
||||
$attr{$name}{dummy} = 1;
|
||||
return undef;
|
||||
}
|
||||
$hash->{DeviceName} = "/dev/i2c-".$dev;
|
||||
$hash->{STATE} = "initialized";
|
||||
return undef;
|
||||
}
|
||||
|
||||
#####################################
|
||||
sub RPII2C_Notify { #
|
||||
my ($hash,$dev) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $type = $hash->{TYPE};
|
||||
if( grep(m/^(INITIALIZED|REREADCFG)$/, @{$dev->{CHANGED}}) ) {
|
||||
RPII2C_forall_clients($hash,\&RPII2C_Init_Client,undef);;
|
||||
} elsif( grep(m/^SAVE$/, @{$dev->{CHANGED}}) ) {
|
||||
}
|
||||
}
|
||||
#####################################
|
||||
sub RPII2C_forall_clients($$$) { #
|
||||
my ($hash,$fn,$args) = @_;
|
||||
foreach my $d ( sort keys %main::defs ) {
|
||||
if ( defined( $main::defs{$d} )
|
||||
&& defined( $main::defs{$d}{IODev} )
|
||||
&& $main::defs{$d}{IODev} == $hash ) {
|
||||
&$fn($main::defs{$d},$args);
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
#####################################
|
||||
sub RPII2C_Init_Client($@) { #
|
||||
my ($hash,$args) = @_;
|
||||
if (!defined $args and defined $hash->{DEF}) {
|
||||
my @a = split("[ \t][ \t]*", $hash->{DEF});
|
||||
$args = \@a;
|
||||
}
|
||||
my $name = $hash->{NAME};
|
||||
Log3 $name,5,"im init client fuer $name ";
|
||||
my $ret = CallFn($name,"InitFn",$hash,$args);
|
||||
if ($ret) {
|
||||
Log3 $name,2,"error initializing '".$hash->{NAME}."': ".$ret;
|
||||
}
|
||||
}
|
||||
#####################################
|
||||
sub RPII2C_Undef($$) { #
|
||||
my ($hash, $arg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
foreach my $d (sort keys %defs) {
|
||||
if(defined($defs{$d}) &&
|
||||
defined($defs{$d}{IODev}) &&
|
||||
$defs{$d}{IODev} == $hash)
|
||||
{
|
||||
Log3 $name, 3, "deleting port for $d";
|
||||
delete $defs{$d}{IODev};
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
#####################################
|
||||
sub RPII2C_Set($@) { #writeBlock noch nicht fertig
|
||||
my ($hash, @a) = @_;
|
||||
my $name = shift @a;
|
||||
my $type = shift @a;
|
||||
my @sets = ('writeByte', 'writeByteReg', 'writeBlock'); #, 'writeNBlock');
|
||||
return "Unknown argument $type, choose one of " . join(" ", @sets) if @a < 2;
|
||||
|
||||
foreach (@a) { #Hexwerte prüfen und in Dezimalwerte wandeln
|
||||
return "$name: $_ is no 1byte hexadecimal value" if $_ !~ /^(0x|)[0-9A-F]{1,2}$/xi ;
|
||||
$_ = hex;
|
||||
}
|
||||
my $i2ca = shift @a;
|
||||
return "$name: I2C Address not valid" unless ($i2ca > 3 && $i2ca < 128); #prüfe auf Hexzahl zwischen 4 und 7F
|
||||
|
||||
my $i2chash = { i2caddress => $i2ca, direction => "i2cwrite" };
|
||||
my ($reg, $nbyte, $data) = undef;
|
||||
if ($type eq "writeByte") {
|
||||
$data = join(" ", @a);
|
||||
} elsif ($type eq "writeByteReg") {
|
||||
$reg = shift @a;
|
||||
$data = join(" ", @a);
|
||||
} elsif ($type eq "writeBlock") {
|
||||
$reg = shift @a;
|
||||
$nbyte = int(@a);
|
||||
return "$name maximal blocksize (32byte) exeeded" if $nbyte > 32;
|
||||
$data = join(" ", @a);
|
||||
$i2chash->{direction} = "i2cblockwrite";
|
||||
#####kommt weg da sinnlos??!!! Achtung $nbyte stimmt derzeit nicht
|
||||
# } elsif ($type eq "writeNBlock") {
|
||||
# $reg = shift @a;
|
||||
# return "$name register address must be a hexvalue" if (!defined($reg) || $reg !~ /^(0x|)[0-9A-F]{1,4}$/xi);
|
||||
# $nbyte = shift @a;
|
||||
# return "$name number of bytes must be decimal value" if (!defined($nbyte) || $nbyte !~ /^[0-9]{1,2}$/);
|
||||
# return "$name data values must be n times number of bytes" if (int(@a) % $nbyte != 0);
|
||||
# $data = join(" ", @a);
|
||||
#########################################################################
|
||||
}else {
|
||||
return "Unknown argument $type, choose one of " . join(" ", @sets);
|
||||
}
|
||||
|
||||
$i2chash->{reg} = $reg if defined($reg); #startadresse zum lesen
|
||||
$i2chash->{nbyte} = $nbyte if defined($nbyte);
|
||||
$i2chash->{data} = $data if defined($data);
|
||||
RPII2C_HWACCESS($hash, $i2chash);
|
||||
undef $i2chash; #Hash löschen
|
||||
return undef;
|
||||
}
|
||||
##################################### fertig?
|
||||
sub RPII2C_Get($@) { #
|
||||
my ($hash, @a) = @_;
|
||||
my $nargs = int(@a);
|
||||
my $name = $hash->{NAME};
|
||||
my @gets = ('read');
|
||||
unless ( exists($a[1]) && $a[1] ne "?" && grep {/^$a[1]$/} @gets ) {
|
||||
return "Unknown argument $a[1], choose one of " . join(" ", @gets);
|
||||
}
|
||||
if ($a[1] eq "read") {
|
||||
return "use: \"get $name $a[1] <i2cAddress> [<RegisterAddress> [<Number od bytes to get>]]\"" if(@a < 3);
|
||||
return "$name: I2C Address not valid" unless ( $a[2] =~ /^(0x|)([0-7]|)[0-9A-F]$/xi);
|
||||
return "$name register address must be a hexvalue" if (defined($a[3]) && $a[3] !~ /^(0x|)[0-9A-F]{1,4}$/xi);
|
||||
return "$name number of bytes must be decimal value" if (defined($a[4]) && $a[4] !~ /^[0-9]{1,2}$/);
|
||||
my $i2chash = { i2caddress => hex($a[2]), direction => "i2cread" };
|
||||
$i2chash->{reg} = hex($a[3]) if defined($a[3]); #startadresse zum lesen
|
||||
$i2chash->{nbyte} = $a[4] if defined($a[4]);
|
||||
Log3 $hash, 1, "Reg: ". $i2chash->{reg};
|
||||
my $status = RPII2C_HWACCESS($hash, $i2chash);
|
||||
#my $received = join(" ", @{$i2chash->{received}}); #als Array
|
||||
my $received = $i2chash->{received}; #als Scalar
|
||||
undef $i2chash; #Hash löschen
|
||||
return "$received transmission: $status";
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
#####################################
|
||||
sub RPII2C_Write($$) { #wird vom Client aufgerufen
|
||||
my ($hash, $clientmsg) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
my $ankommen = "$name: vom client empfangen";
|
||||
foreach my $av (keys %{$clientmsg}) { $ankommen .= "|" . $av . ": " . $clientmsg->{$av}; }
|
||||
Log3 $hash, 5, $ankommen;
|
||||
|
||||
if ( $clientmsg->{direction} && $clientmsg->{i2caddress} ) {
|
||||
$clientmsg->{$name . "_" . "SENDSTAT"} = RPII2C_HWACCESS($hash, $clientmsg);
|
||||
}
|
||||
|
||||
foreach my $d ( sort keys %main::defs ) { #zur Botschaft passenden Clienten ermitteln geht auf Client: I2CRecFn
|
||||
#Log3 $hash, 1, "d: $d". ($main::defs{$d}{IODev}? ", IODev: $main::defs{$d}{IODev}":"") . ($main::defs{$d}{I2C_Address} ? ", I2C: $main::defs{$d}{I2C_Address}":"") . ($clientmsg->{i2caddress} ? " CI2C: $clientmsg->{i2caddress}" : "");
|
||||
if ( defined( $main::defs{$d} )
|
||||
&& defined( $main::defs{$d}{IODev} ) && $main::defs{$d}{IODev} == $hash
|
||||
&& defined( $main::defs{$d}{I2C_Address} ) && defined($clientmsg->{i2caddress})
|
||||
&& $main::defs{$d}{I2C_Address} eq $clientmsg->{i2caddress} ) {
|
||||
my $chash = $main::defs{$d};
|
||||
Log3 $hash, 5, "$name ->Client gefunden: $d". ($main::defs{$d}{I2C_Address} ? ", I2Caddress: $main::defs{$d}{I2C_Address}":"") . ($clientmsg->{data} ? " Data: $clientmsg->{data}" : "");
|
||||
CallFn($d, "I2CRecFn", $chash, $clientmsg);
|
||||
undef $clientmsg #Hash löschen nachdem Daten verteilt wurden
|
||||
}
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
#####################################
|
||||
sub RPII2C_HWACCESS($$) {
|
||||
my ($hash, $clientmsg) = @_;
|
||||
my $status = "error";
|
||||
my $inh = undef;
|
||||
Log3 $hash, 5, "$hash->{NAME}: HWaccess I2CAddr: " . sprintf("%.2X", $clientmsg->{i2caddress});
|
||||
my $dev = Device::SMBus->new(
|
||||
I2CBusDevicePath => $hash->{DeviceName},
|
||||
I2CDeviceAddress => hex( sprintf("%.2X", $clientmsg->{i2caddress}) ),
|
||||
);
|
||||
if (defined($clientmsg->{nbyte}) && defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cblockwrite") { #Registerblock beschreiben
|
||||
my @data = split(" ", $clientmsg->{data});
|
||||
my $dataref = \@data;
|
||||
$inh = $dev->writeBlockData( $clientmsg->{reg} , $dataref );
|
||||
my $wr = join(" ", @{$dataref});
|
||||
Log3 $hash, 5, "$hash->{NAME}: Block schreiben Register: " . sprintf("%.2X", $clientmsg->{reg}) . " Inhalt: " . $wr . " N: ". int(@data) ." Returnvar.: $inh";
|
||||
$status = "Ok" if $inh == 0;
|
||||
#kommt wieder weg#################
|
||||
} elsif (defined($clientmsg->{nbyte}) && defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cwrite") { #Registerbereich (mehrfach) beschreiben
|
||||
my @data = split(" ", $clientmsg->{data});
|
||||
foreach (0..$#data) {
|
||||
my $i = $_ -( int($_ / $clientmsg->{nbyte}) * $clientmsg->{nbyte} );
|
||||
$inh = $dev->writeByteData( ($clientmsg->{reg} + $i ) ,$data[$_]);
|
||||
Log3 $hash, 5, "$hash->{NAME} NReg schreiben; Reg: " . ($clientmsg->{reg} + $i) . " Inh: " . $data[$_] . " Returnvar.: $inh";
|
||||
last if $inh != 0;
|
||||
$status = "Ok" if $inh == 0;
|
||||
}
|
||||
#hier Mehrfachbeschreibung eines Registers noch entfernen und dafür Bereich mit Registeroperationen beschreiben
|
||||
} elsif (defined($clientmsg->{reg}) && defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cwrite") { #Register beschreiben
|
||||
my @data = split(" ", $clientmsg->{data});
|
||||
foreach (@data) {
|
||||
$inh = $dev->writeByteData($clientmsg->{reg},$_);
|
||||
Log3 $hash, 5, "$hash->{NAME}; Register ".sprintf("%.2X", $clientmsg->{reg})." schreiben - Inhalt: " .sprintf("%.2X",$_) . " Returnvar.: $inh";
|
||||
last if $inh != 0;
|
||||
$status = "Ok" if $inh == 0;
|
||||
}
|
||||
} elsif (defined($clientmsg->{data}) && $clientmsg->{direction} eq "i2cwrite") { #Byte(s) schreiben
|
||||
my @data = split(" ", $clientmsg->{data});
|
||||
foreach (@data) {
|
||||
$inh = $dev->writeByte($_);
|
||||
Log3 $hash, 5, "$hash->{NAME} Byte schreiben; Inh: " . $_ . " Returnvar.: $inh";
|
||||
last if $inh != 0;
|
||||
$status = "Ok" if $inh == 0;
|
||||
}
|
||||
} elsif (defined($clientmsg->{reg}) && $clientmsg->{direction} eq "i2cread") { #Register lesen
|
||||
my $nbyte = defined($clientmsg->{nbyte}) ? $clientmsg->{nbyte} : 1;
|
||||
my $rmsg = "";
|
||||
for (my $n = 0; $n < $nbyte; $n++) {
|
||||
$inh = $dev->readByteData($clientmsg->{reg} + $n );
|
||||
Log3 $hash, 5, "$hash->{NAME}; Register ".sprintf("%.2X", $clientmsg->{reg} + $n )." lesen - Inhalt: ".sprintf("%.2X",$inh);
|
||||
last if ($inh < 0);
|
||||
#$rmsg .= sprintf("%.2X",$inh);
|
||||
$rmsg .= $inh;
|
||||
$rmsg .= " " if $n <= $nbyte;
|
||||
$status = "Ok" if ($n + 1) == $nbyte;
|
||||
}
|
||||
#@{$clientmsg->{received}} = split(" ", $rmsg) if($rmsg); #Daten als Array übertragen
|
||||
$clientmsg->{received} = $rmsg if($rmsg); #Daten als Scalar übertragen
|
||||
} elsif ($clientmsg->{direction} eq "i2cread") { #Byte lesen
|
||||
my $nbyte = defined($clientmsg->{nbyte}) ? $clientmsg->{nbyte} : 1;
|
||||
my $rmsg = "";
|
||||
for (my $n = 0; $n < $nbyte; $n++) {
|
||||
$inh = $dev->readByte();
|
||||
Log3 $hash, 5, "$hash->{NAME} Byte lesen; Returnvar.: $inh";
|
||||
last if ($inh < 0);
|
||||
$rmsg .= $inh;
|
||||
$rmsg .= " " if $n <= $nbyte;
|
||||
$status = "Ok" if ($n + 1) == $nbyte;
|
||||
}
|
||||
#@{$clientmsg->{received}} = split(" ", $rmsg) if($rmsg); #Daten als Array übertragen
|
||||
$clientmsg->{received} = $rmsg if($rmsg); #Daten als Scalar übertragen
|
||||
}
|
||||
$hash->{STATE} = $status;
|
||||
$hash->{ERRORCNT} = defined($hash->{ERRORCNT}) ? $hash->{ERRORCNT} += 1 : 1 if $status ne "Ok";
|
||||
$clientmsg->{$hash->{NAME} . "_" . "RAWMSG"} = $inh;
|
||||
|
||||
return $status;
|
||||
}
|
||||
|
||||
=pod
|
||||
=begin html
|
||||
|
||||
<a name="RPII2C"></a>
|
||||
<h3>RPII2C</h3>
|
||||
<ul>
|
||||
<a name="RPII2C"></a>
|
||||
Provides access to Raspberry Pi's I2C interfaces for some logical modules and also directly.<br>
|
||||
This modul will basically work on every linux system that provides <code>/dev/i2c-x</code>.<br><br>
|
||||
|
||||
<b>preliminary:</b><br>
|
||||
<ul>
|
||||
<li>
|
||||
This module uses gpio utility from <a href="http://wiringpi.com/download-and-install/">WiringPi</a> library change access rights of I2C-Interface<br>
|
||||
WiringPi installation is described here: <a href="#RPI_GPIO">RPI_GPIO</a><br>
|
||||
Alternatively for other systems (BeagleBone, etc.) you can manually change access rights for <code>/dev/i2c-x</code>. You will need write-/read access for user that runs FHEM. This can be doen e.g. in etc/init.d/fhem<br>
|
||||
|
||||
</li>
|
||||
<li>
|
||||
installation of i2c dependencies:<br>
|
||||
<code>sudo apt-get install libi2c-dev i2c-tools build-essential</code><br>
|
||||
</li>
|
||||
<li>
|
||||
load I2C kernel modules:<br>
|
||||
open /etc/modules<br>
|
||||
<code>sudo nano /etc/modules</code><br>
|
||||
add theese lines<br>
|
||||
<code>
|
||||
i2c-dev<br>
|
||||
i2c-bcm2708<br>
|
||||
</code>
|
||||
</li>
|
||||
<li>
|
||||
To access the I2C-Bus the Device::SMBus module is necessary:<br>
|
||||
<code>sudo cpan Device::SMBus</code><br>
|
||||
</li>
|
||||
</ul>
|
||||
<a name="RPII2CDefine"></a><br>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> RPII2C <I2C Bus Number></code><br>
|
||||
where <code><I2C Bus Number></code> is the number of the I2C bus that should be used (0 or 1)<br><br>
|
||||
</ul>
|
||||
|
||||
<a name="RPII2CSet"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<li>
|
||||
Write one byte (or more bytes sequentially) directly to an I2C device (for devices that have only one register to write):<br>
|
||||
<code>set <name> writeByte <I2C Address> <value></code><br><br>
|
||||
</li>
|
||||
<li>
|
||||
Write one byte (or more bytes sequentially) to the specified register of an I2C device:<br>
|
||||
<code>set <name> writeByteReg <I2C Address> <Register Address> <value></code><br><br>
|
||||
</li>
|
||||
<li>
|
||||
Write n-bytes to an register range, beginning at the specified register:<br>
|
||||
<code>set <name> writeBlock <I2C Address> <Register Address> <value></code><br><br>
|
||||
</li>
|
||||
<li>
|
||||
Same as writeBlock but writes register range sequentially. The numbers of byte to write must be a multipe of the number of register.
|
||||
<code>set <name> writeNBlock <I2C Address> <Register Address> <number of registers> <value></code><br><br>
|
||||
</li><br>
|
||||
Examples:
|
||||
<ul>
|
||||
Write 0xAA to device with I2C address 0x60<br>
|
||||
<code>set test1 writeByte 60 AA</code><br>
|
||||
Write 0xAA to register 0x01 of device with I2C address 0x6E<br>
|
||||
<code>set test1 writeByteReg 6E 01 AA</code><br>
|
||||
Write 0xAA to register 0x01 of device with I2C address 0x6E, after it write 0x55 to 0x02 as two separate commands<br>
|
||||
<code>set test1 writeByteReg 6E 01 AA 55</code><br>
|
||||
Write 0xA4 to register 0x03, 0x00 to register 0x04 and 0xDA to register 0x05 of device with I2C address 0x60 as an block command<br>
|
||||
<code>set test1 writeBlock 60 03 A4 00 DA</code><br>
|
||||
|
||||
</ul><br>
|
||||
</ul>
|
||||
|
||||
<a name="RPII2CGet"></a>
|
||||
<b>Get</b>
|
||||
<ul>
|
||||
<code>get <name> read <I2C Address> [<Register Address> [<number of registers>]] </code>
|
||||
<br>
|
||||
gets value of I2C device's registers<br><br>
|
||||
Examples:
|
||||
<ul>
|
||||
Reads byte from device with I2C address 0x60<br>
|
||||
<code>get test1 writeByte 60</code><br>
|
||||
Reads register 0x01 of device with I2C address 0x6E.<br>
|
||||
<code>get test1 read 6E 01 AA 55</code><br>
|
||||
Reads register 0x03 to 0x06 of device with I2C address 0x60.<br>
|
||||
<code>get test1 read 60 03 4</code><br>
|
||||
</ul><br>
|
||||
</ul><br>
|
||||
|
||||
<a name="RPII2CAttr"></a>
|
||||
<b>Attributes</b>
|
||||
<ul>
|
||||
<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="RPII2C"></a>
|
||||
<h3>RPII2C</h3>
|
||||
<ul>
|
||||
<a name="RPII2C"></a>
|
||||
Ermöglicht den Zugriff auf die I2C Schnittstellen des Raspberry Pi über logische Module. Register von I2C IC's können auch direkt gelesen und geschrieben werden.<br><br>
|
||||
Dieses Modul funktioniert grunsätzlich auf allen Linux Systemen, die <code>/dev/i2c-x</code> bereitstellen.<br><br>
|
||||
|
||||
<b>Vorbereitung:</b><br>
|
||||
<ul>
|
||||
<li>
|
||||
Dieses Modul nutzt das gpio Utility der <a href="http://wiringpi.com/download-and-install/">WiringPi</a> Bibliothek um FHEM Schreibrechte auf die I2C Schnittstelle zu geben.<br>
|
||||
WiringPi Installation ist hier beschrieben: <a href="#RPI_GPIO">RPI_GPIO</a><br>
|
||||
Für andere Systeme (BeagleBone, etc.) oder auch für das Raspberry kann auf WiringPi verzichtet werden. In diesem Fall müssen die Dateien <code>/dev/i2c-x</code> Schreib-/Leserechte, für den User unter dem FHEM läuft, gesetzt bekommen. (z.B. in der etc/init.d/fhem)<br>
|
||||
</li>
|
||||
<li>
|
||||
Installation der I2C Abhängigkeiten:<br>
|
||||
<code>sudo apt-get install libi2c-dev i2c-tools build-essential</code><br>
|
||||
</li>
|
||||
<li>
|
||||
I2C Kernelmodule laden:<br>
|
||||
modules Datei öffnen<br>
|
||||
<code>sudo nano /etc/modules</code><br>
|
||||
folgendes einfügen<br>
|
||||
<code>
|
||||
i2c-dev<br>
|
||||
i2c-bcm2708<br>
|
||||
</code>
|
||||
</li>
|
||||
<li>
|
||||
Desweiteren ist das Perl Modul Device::SMBus für den Zugrff auf den I2C Bus notwendig:<br>
|
||||
<code>sudo cpan Device::SMBus</code><br>
|
||||
</li>
|
||||
</ul>
|
||||
<a name="RPII2CDefine"></a><br>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> RPII2C <I2C Bus Number></code><br>
|
||||
Die <code><I2C Bus Number></code> ist die Nummer des I2C Bus an den die I2C IC's angeschlossen werden (0 oder 1)<br><br>
|
||||
</ul>
|
||||
|
||||
<a name="RPII2CSet"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<li>
|
||||
Schreibe ein Byte (oder auch mehrere nacheinander) direkt auf ein I2C device (manche I2C Module sind so einfach, das es nicht einmal mehrere Register gibt):<br>
|
||||
<code>set <name> writeByte <I2C Address> <value></code><br><br>
|
||||
</li>
|
||||
<li>
|
||||
Schreibe ein Byte (oder auch mehrere nacheinander) direkt auf ein Register des adressierten I2C device:<br>
|
||||
<code>set <name> writeByteReg <I2C Address> <Register Address> <value></code><br><br>
|
||||
</li>
|
||||
<li>
|
||||
Schreibe n-bytes auf einen Registerbereich, beginnend mit dem angegebenen Register:<br>
|
||||
<code>set <name> writeBlock <I2C Address> <Register Address> <value></code><br><br>
|
||||
</li>
|
||||
<li>
|
||||
Identisch zu writeBlock nur kann der Registerbereich sequentiell beschrieben werden. Die Anzahl der Byte muss ein vielfaches der <number of registers> sein.
|
||||
<code>set <name> writeNBlock <I2C Address> <Register Address> <number of registers> <value></code><br><br>
|
||||
</li><br>
|
||||
Beispiele:
|
||||
<ul>
|
||||
Schreibe 0xAA zu Modul mit I2C Addresse 0x60<br>
|
||||
<code>set test1 writeByte 60 AA</code><br>
|
||||
Schreibe 0xAA zu Register 0x01 des Moduls mit der I2C Adresse 0x6E<br>
|
||||
<code>set test1 writeByteReg 6E 01 AA</code><br>
|
||||
Schreibe 0xAA zu Register 0x01 des Moduls mit der I2C Adresse 0x6E, schreibe danach 0x55 in das Register 0x02 als einzelne Befehle<br>
|
||||
<code>set test1 writeByteReg 6E 01 AA 55</code><br>
|
||||
Schreibe 0xA4 zu Register 0x03, 0x00 zu Register 0x04 und 0xDA zu Register 0x05 des Moduls mit der I2C Adresse 0x60 zusammen als ein Blockbefehl<br>
|
||||
<code>set test1 writeBlock 60 03 A4 00 DA</code><br>
|
||||
</ul><br>
|
||||
</ul>
|
||||
|
||||
<a name="RPII2CGet"></a>
|
||||
<b>Get</b>
|
||||
<ul>
|
||||
<code>get <name> read <I2C Address> [<Register Address> [<number of registers>]] </code>
|
||||
<br>
|
||||
Auslesen der Registerinhalte des I2C Moduls<br><br>
|
||||
Examples:
|
||||
<ul>
|
||||
Lese Byte vom Modul mit der I2C Adresse 0x60<br>
|
||||
<code>get test1 writeByte 60</code><br>
|
||||
Lese den Inhalt des Registers 0x01 vom Modul mit der I2C Adresse 0x6E.<br>
|
||||
<code>get test1 read 6E 01 AA 55</code><br>
|
||||
Lese den Inhalt des Registerbereichs 0x03 bis 0x06 vom Modul mit der I2C Adresse 0x60.<br>
|
||||
<code>get test1 read 60 03 4</code><br>
|
||||
</ul><br>
|
||||
</ul><br>
|
||||
|
||||
<a name="RPII2CAttr"></a>
|
||||
<b>Attribute</b>
|
||||
<ul>
|
||||
<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
|
||||
|
||||
1;
|
488
fhem/FHEM/52_I2C_PCA9532.pm
Normal file
488
fhem/FHEM/52_I2C_PCA9532.pm
Normal file
@ -0,0 +1,488 @@
|
||||
##############################################################################
|
||||
#
|
||||
# 52_I2C_PCA9532.pm
|
||||
#
|
||||
##############################################################################
|
||||
# Modul for I2C PWM Driver PCA9532
|
||||
#
|
||||
# define <name> I2C_PCA9532 <I2C-Adresse>
|
||||
# set <name> <port> <value>
|
||||
#
|
||||
# contributed by Klaus Wittstock (2014) email: klauswittstock bei gmail punkt com
|
||||
#
|
||||
##############################################################################
|
||||
#zu tun:
|
||||
#bei Set sollten Input Values nicht aktualisiert werden
|
||||
#$sendpackage{data} als Array ?
|
||||
#$clientmsg->{received} als Array ?
|
||||
|
||||
#Inhalte des Hashes:
|
||||
#i2caddress 00-7F I2C-Adresse
|
||||
#direction i2cread|i2cwrite Richtung
|
||||
#reg 00-FF|"" Registeradresse (kann weggelassen werden für IC's ohne Registeradressierung)
|
||||
#nbyte Zahl Anzahl Register, die bearbeitet werden sollen (im mom 0-99)
|
||||
#data 00-FF ... Daten die an I2C geschickt werden sollen (müssen, wenn nbyte benutzt wird immer ein Vielfaches Desselben sein)
|
||||
#received 00-FF ... Daten die vom I2C empfangen wurden, durch Leerzeichen getrennt (bleibt leer wenn Daten geschrieben werden)
|
||||
#pname_SENDSTAT Ok|error zeigt Übertragungserfolg an
|
||||
|
||||
package main;
|
||||
use strict;
|
||||
use warnings;
|
||||
use SetExtensions;
|
||||
#use POSIX;
|
||||
use Scalar::Util qw(looks_like_number);
|
||||
#use Error qw(:try);
|
||||
|
||||
my $setdim = ":slider,0,1,255 ";
|
||||
|
||||
my %setsP = (
|
||||
'off' => 0,
|
||||
'on' => 1,
|
||||
'PWM0' => 2,
|
||||
'PWM1' => 3,
|
||||
);
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_Initialize($) {
|
||||
my ($hash) = @_;
|
||||
$hash->{DefFn} = "I2C_PCA9532_Define";
|
||||
$hash->{InitFn} = 'I2C_PCA9532_Init';
|
||||
$hash->{UndefFn} = "I2C_PCA9532_Undefine";
|
||||
$hash->{AttrFn} = "I2C_PCA9532_Attr";
|
||||
#$hash->{StateFn} = "I2C_PCA9532_SetState";
|
||||
$hash->{SetFn} = "I2C_PCA9532_Set";
|
||||
$hash->{GetFn} = "I2C_PCA9532_Get";
|
||||
$hash->{I2CRecFn} = "I2C_PCA9532_I2CRec";
|
||||
$hash->{AttrList} = "IODev do_not_notify:1,0 ignore:1,0 showtime:1,0".
|
||||
"poll_interval T0:slider,0,1,255 T1:slider,0,1,255 InputPorts ".
|
||||
"$readingFnAttributes";
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_SetState($$$$) {
|
||||
my ($hash, $tim, $vt, $val) = @_;
|
||||
|
||||
$val = $1 if($val =~ m/^(.*) \d+$/);
|
||||
#return "Undefined value $val" if(!defined($it_c2b{$val}));
|
||||
return undef;
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_Define($$) {
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split("[ \t]+", $def);
|
||||
$hash->{STATE} = 'defined';
|
||||
if ($main::init_done) {
|
||||
eval { I2C_PCA9532_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); };
|
||||
return I2C_PCA9532_Catch($@) if $@;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_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>";
|
||||
}
|
||||
#return "$name I2C Address not valid" unless ($a[0] =~ /^(0x|)([0-7]|)[0-9A-F]$/xi);
|
||||
|
||||
if (defined (my $address = shift @$args)) {
|
||||
$hash->{I2C_Address} = $address =~ /^0.*$/ ? oct($address) : $address;
|
||||
} else {
|
||||
return "$name I2C Address not valid";
|
||||
}
|
||||
|
||||
#$hash->{I2C_Address} = hex($a[0]);
|
||||
AssignIoPort($hash);
|
||||
$hash->{STATE} = 'Initialized';
|
||||
return;
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_Catch($) {
|
||||
my $exception = shift;
|
||||
if ($exception) {
|
||||
$exception =~ /^(.*)( at.*FHEM.*)$/;
|
||||
return $1;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_Undefine($$) {
|
||||
my ($hash, $arg) = @_;
|
||||
if ( defined (AttrVal($hash->{NAME}, "poll_interval", undef)) ) {
|
||||
RemoveInternalTimer($hash);
|
||||
}
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_Attr(@) {
|
||||
my (undef, $name, $attr, $val) = @_;
|
||||
my $hash = $defs{$name};
|
||||
my $msg = '';
|
||||
if ($attr && $attr eq 'poll_interval') {
|
||||
#my $pollInterval = (defined($val) && looks_like_number($val) && $val > 0) ? $val : 0;
|
||||
if (!defined($val) ) {
|
||||
RemoveInternalTimer($hash);
|
||||
} elsif ($val > 0) {
|
||||
RemoveInternalTimer($hash);
|
||||
InternalTimer(1, 'I2C_PCA9532_Poll', $hash, 0);
|
||||
} else {
|
||||
$msg = 'Wrong poll intervall defined. poll_interval must be a number > 0';
|
||||
}
|
||||
} elsif ($attr && $attr =~ m/^T[0-1]$/i) {
|
||||
return "wrong value: $val for \"set $name $attr\" use 0-255"
|
||||
unless(looks_like_number($val) && $val >= 0 && $val < 256);
|
||||
substr($attr,0,1,"");
|
||||
my $regaddr = ($attr == 0 ? 2 : 4);
|
||||
my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" );
|
||||
#$sendpackage{data} = sprintf("%.2X", $val);
|
||||
#$sendpackage{reg} = sprintf("%.2X", $regaddr);
|
||||
$sendpackage{data} = $val;
|
||||
$sendpackage{reg} = $regaddr;
|
||||
return "$name: no IO device defined" unless ($hash->{IODev});
|
||||
my $phash = $hash->{IODev};
|
||||
my $pname = $phash->{NAME};
|
||||
CallFn($pname, "I2CWrtFn", $phash, \%sendpackage);
|
||||
} elsif ($attr && $attr eq "InputPorts") {
|
||||
my @inp = split(" ", $val);
|
||||
foreach (@inp) {
|
||||
return "wrong value: $_ for \"set $name $attr\" use space separated numbers 0-15" unless ($_ >= 0 && $_ < 16);
|
||||
}
|
||||
}
|
||||
return ($msg) ? $msg : undef;
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_Poll($) {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
# Read values
|
||||
I2C_PCA9532_Get($hash, $name);
|
||||
my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
|
||||
if ($pollInterval > 0) {
|
||||
InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_PCA9532_Poll', $hash, 0);
|
||||
}
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_Set($@) {
|
||||
my ($hash, @a) = @_;
|
||||
my $name =$a[0];
|
||||
my $cmd = $a[1];
|
||||
my $val = $a[2];
|
||||
my @inports = sort(split( " ",AttrVal($name, "InputPorts", "")));
|
||||
unless (@a == 3) {
|
||||
|
||||
}
|
||||
my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" );
|
||||
if ( $cmd && $cmd =~ m/^Port((0|)[0-9]|1[0-5])$/i) {
|
||||
return "wrong value: $val for \"set $name $cmd\" use one of: " .
|
||||
join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) )
|
||||
unless(exists($setsP{$val}));
|
||||
substr($cmd,0,4,"");
|
||||
return "$name error: Port$cmd is defined as input" if ( $cmd ~~ @inports ); #Prüfen ob entsprechender Port Input ist
|
||||
my $LSreg = int($cmd / 4); #Nummer des entspechenden LS Registers
|
||||
my $regaddr = $LSreg + 6; #Adresse für Controlregister berechnen (LS0 = 0x06)
|
||||
my $n = $LSreg * 4; #Erster Port in LSx
|
||||
my $sbyte = 0;
|
||||
foreach (reverse 0..3) { #ensprechendes Controlregister füllen
|
||||
my $portn = $_ + $n;
|
||||
#hier noch alle inputs auf rezessiv setzen
|
||||
if (( $portn) == $cmd ) { #->wenn aktueller Port dann neuer Wert
|
||||
$sbyte += $setsP{$val} << (2 * $_);
|
||||
next;
|
||||
}
|
||||
$sbyte += $setsP{ReadingsVal($name,'Port'.$portn,"off")} << (2 * $_); #->sonst aus dem Reading holen
|
||||
}
|
||||
#$sendpackage{data} = sprintf("%.2X",$sbyte);
|
||||
$sendpackage{data} = $sbyte;
|
||||
#$sendpackage{reg} = sprintf("%.2X", $regaddr);
|
||||
$sendpackage{reg} = $regaddr;
|
||||
|
||||
} elsif ($cmd && $cmd =~ m/^PWM[0-1]$/i) {
|
||||
return "wrong value: $val for \"set $name $cmd\" use 0-255"
|
||||
unless(looks_like_number($val) && $val >= 0 && $val < 256);
|
||||
substr($cmd,0,3,"");
|
||||
my $regaddr = ($cmd == 0 ? 3 : 5);
|
||||
#$sendpackage{data} = sprintf("%.2X", $val);
|
||||
$sendpackage{data} = $val;
|
||||
$sendpackage{reg} = sprintf("%.2X", $regaddr);
|
||||
|
||||
} else {
|
||||
my $list = undef;
|
||||
foreach (0..15) {
|
||||
next if ( $_ ~~ @inports ); #Inputs überspringen
|
||||
#$list .= "Port" . $_ . ":" . join(',', sort keys %setsP) . " ";
|
||||
$list .= "Port" . $_ . ":" . join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) . " ";
|
||||
}
|
||||
$list .= join($setdim, ("PWM0", "PWM1")) . $setdim;
|
||||
return "Unknown argument $a[1], choose one of " . $list;
|
||||
}
|
||||
return "$name: no IO device defined" unless ($hash->{IODev});
|
||||
my $phash = $hash->{IODev};
|
||||
my $pname = $phash->{NAME};
|
||||
CallFn($pname, "I2CWrtFn", $phash, \%sendpackage);
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_Get($@) {
|
||||
my ($hash, @a) = @_;
|
||||
my $name =$a[0];
|
||||
|
||||
my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cread" );
|
||||
$sendpackage{reg} = 0; #startadresse zum lesen
|
||||
$sendpackage{nbyte} = 10;
|
||||
return "$name: no IO device defined" unless ($hash->{IODev});
|
||||
my $phash = $hash->{IODev};
|
||||
my $pname = $phash->{NAME};
|
||||
CallFn($pname, "I2CWrtFn", $phash, \%sendpackage);
|
||||
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_I2CRec($@) { # 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 für alle Keys in $clientmsg die mit dem physical Namen beginnen
|
||||
$hash->{$k} = $v if $k =~ /^$pname/ ;
|
||||
}
|
||||
#hier noch überprüfen, ob Register und Daten ok
|
||||
if ($clientmsg->{direction} && defined($clientmsg->{reg}) && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok") {
|
||||
if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) { # =~ m/^[a-f0-9]{2}$/i) {
|
||||
#my @rec = @{$clientmsg->{received}}; #bei übergabe im hash als array
|
||||
my @rec = split(" ",$clientmsg->{received}); #bei übergabe im als skalar
|
||||
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_PCA9532_UpdReadings($hash, $_ + $clientmsg->{reg} , $rec[$_]);
|
||||
}
|
||||
readingsSingleUpdate($hash,"state", "Ok", 1);
|
||||
} elsif ( $clientmsg->{direction} eq "i2cwrite" && defined($clientmsg->{data}) ) { # =~ m/^[a-f0-9]{2}$/i) {#readings aktualisieren wenn Übertragung ok
|
||||
I2C_PCA9532_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: $clientmsg->{i2caddress} " : "I2Caddress: undef ").
|
||||
(defined($clientmsg->{reg}) ? "Register: $clientmsg->{reg} " : "Register: undef ").
|
||||
(defined($clientmsg->{data}) ? "Data: $clientmsg->{data} " : "Data: undef ").
|
||||
(defined($clientmsg->{received}) ? "received: $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: $clientmsg->{i2caddress} " : "I2Caddress: undef ").
|
||||
(defined($clientmsg->{reg}) ? "Register: $clientmsg->{reg} " : "Register: undef ").
|
||||
(defined($clientmsg->{data}) ? "Data: $clientmsg->{data} " : "Data: undef ").
|
||||
(defined($clientmsg->{received}) ? "received: $clientmsg->{received} " : "received: undef ");
|
||||
#my $cmsg = undef;
|
||||
#foreach my $av (keys %{$clientmsg}) { $cmsg .= "|" . $av . ": " . $clientmsg->{$av}; }
|
||||
#Log3 $hash, 3, $cmsg;
|
||||
}
|
||||
#undef $clientmsg;
|
||||
}
|
||||
###############################################################################
|
||||
sub I2C_PCA9532_UpdReadings($$$) {
|
||||
my ($hash, $reg, $inh) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
#$inh = hex($inh);
|
||||
Log3 $hash, 5, "$name UpdReadings Register: $reg, Inhalt: $inh";
|
||||
readingsBeginUpdate($hash);
|
||||
if ( $reg < 10 && $reg > 5) { #Wenn PortRegister
|
||||
my %rsetsP = reverse %setsP;
|
||||
my $LSreg = $reg - 6; #Nummer des entspechenden LS Registers
|
||||
my $n = $LSreg * 4; #Erster Port in LSx
|
||||
foreach (reverse 0..3) { #Ports aus Controlregister abarbeiten
|
||||
my $pval = 3 & ( $inh >> ($_ * 2) );
|
||||
my $port = $_ + $n;
|
||||
readingsBulkUpdate($hash, 'Port'.$port , $rsetsP{$pval})
|
||||
if (ReadingsVal($name, 'Port'.$port,"nix") ne $rsetsP{$pval}); #nur wenn Wert geändert
|
||||
}
|
||||
} elsif ( $reg == 3) { #wenn PWM0 Register
|
||||
readingsBulkUpdate($hash, 'PWM0' , $inh);
|
||||
} elsif ( $reg == 5) { #wenn PWM1 Register
|
||||
readingsBulkUpdate($hash, 'PWM1' , $inh);
|
||||
} elsif ( $reg == 2) { #wenn Frequenz0 Register
|
||||
$hash->{Frequency_0} = sprintf( "%.3f", ( 152 / ($inh + 1) ) ) . " Hz";
|
||||
} elsif ( $reg == 4) { #wenn Frequenz0 Register
|
||||
$hash->{Frequency_1} = sprintf( "%.3f", ( 152 / ($inh + 1) ) ) . " Hz";
|
||||
} elsif ( $reg >= 0 && $reg < 2 ) { #Input Register
|
||||
my $j = 8 * $reg;
|
||||
Log3 $hash, 5, "Register $reg Inh: $inh";
|
||||
my @inports = sort(split( " ",AttrVal($name, "InputPorts", "")));
|
||||
for (my $i = 0; $i < 8; $i++) {
|
||||
Log3 $hash, 5, "Register $reg Forschleife i: $i";
|
||||
if ( ($i + $j) ~~ @inports ) { #nur als Input definierte Ports aktualisieren
|
||||
my $sval = $inh & (1 << $i);
|
||||
$sval = $sval == 0 ? "0" :"1";
|
||||
readingsBulkUpdate($hash, 'Port'.($i + $j) , $sval) if (ReadingsVal($name,'Port'.($i + $j),2) ne $sval);
|
||||
Log3 $hash, 5, "Register $reg wert: $sval";
|
||||
}
|
||||
}
|
||||
}
|
||||
readingsEndUpdate($hash, 1);
|
||||
return;
|
||||
}
|
||||
###############################################################################
|
||||
|
||||
1;
|
||||
|
||||
=pod
|
||||
=begin html
|
||||
|
||||
<a name="I2C_PCA9532"></a>
|
||||
<h3>I2C_PCA9532</h3>
|
||||
<ul>
|
||||
<a name="I2C_PCA9532"></a>
|
||||
Provides an interface to the PCA9532 I2C 16 channel PWM IC.
|
||||
The PCA9532 has 2 independent PWM stages and every channel can be attached to on of these stages or directly turned on or off.
|
||||
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_PCA9532Define"></a><br>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> I2C_PCA9532 <I2C Address></code><br>
|
||||
where <code><I2C Address></code> is an 2 digit hexadecimal value<br>
|
||||
</ul>
|
||||
|
||||
<a name="I2C_PCA9532Set"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<code>set <name> <port> <value></code><br><br>
|
||||
<ul>
|
||||
<li>if <code><port></code> is one of Port0 to Port15, then <code><value></code> will be one of:<br>
|
||||
<ul>
|
||||
<code>
|
||||
off<br>
|
||||
on<br>
|
||||
PWM0 (output is switched with PWM0 frequency and duty cycle)<br>
|
||||
PWM1 (output is switched with PWM1 frequency and duty cycle)<br>
|
||||
</code>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
if <code><port></code> is PWM0 or PWM1, then <code><value></code> is an value between 0 and 255 and stands for the duty cycle of the PWM stage.
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
Examples:
|
||||
<ul>
|
||||
<code>set mod1 Port4 PWM1</code><br>
|
||||
<code>set mod1 PWM1 128</code><br>
|
||||
</ul><br>
|
||||
</ul>
|
||||
|
||||
<a name="I2C_PCA9532Get"></a>
|
||||
<b>Get</b>
|
||||
<ul>
|
||||
<code>get <name></code>
|
||||
<br><br>
|
||||
refreshes all readings
|
||||
</ul><br>
|
||||
|
||||
<a name="I2C_PCA9532Attr"></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 .. 15<br><br>
|
||||
</li>
|
||||
<li>T0/T1<br>
|
||||
Sets PWM0/PWM1 to another Frequency. The Formula is: Fx = 152/(Tx + 1) The corresponding frequency value is shown under internals.<br>
|
||||
Default: 0 (152Hz), valid values: 0-255<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_PCA9532"></a>
|
||||
<h3>I2C_PCA9532</h3>
|
||||
<ul>
|
||||
<a name="I2C_PCA9532"></a>
|
||||
Ermöglicht die Verwendung eines PCA9532 I2C 16 Kanal PWM IC.
|
||||
Das PCA9532 hat 2 unabhängige PWM Stufen. Jeder Kanal kanne einer der Stufen zugeordnet werden oder direkt auf off/on gesetzt werden.
|
||||
I2C-Botschaften werden über ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</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_PCA9532Define"></a><br>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> I2C_PCA9532 <I2C Address></code><br>
|
||||
Der Wert <code><I2C Address></code> ist ein zweistelliger Hex-Wert<br>
|
||||
</ul>
|
||||
|
||||
<a name="I2C_PCA9532Set"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<code>set <name> <port> <value></code><br><br>
|
||||
<ul>
|
||||
<li>wenn als <code><port></code> Port0 bis Port15 verwendet wird, dann ist <code><value></code> einer dieser Werte:<br>
|
||||
<ul>
|
||||
<code>
|
||||
off<br>
|
||||
on<br>
|
||||
PWM0 (Port wird auf PWM0 Frequenz- und Pulsweiteneinstellung gesetzt)<br>
|
||||
PWM1 (Port wird auf PWM1 Frequenz- und Pulsweiteneinstellung gesetzt)<br>
|
||||
</code>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
wenn als <code><port></code> PWM0 oder PWM1 verwendet wird, ist <code><value></code> ein Wert zwischen 0 und 255 ensprechend der Pulsweite der PWM Stufe.
|
||||
</li>
|
||||
</ul>
|
||||
<br>
|
||||
Beispiele:
|
||||
<ul>
|
||||
<code>set mod1 Port4 PWM1</code><br>
|
||||
<code>set mod1 PWM1 128</code><br>
|
||||
</ul><br>
|
||||
</ul>
|
||||
|
||||
<a name="I2C_PCA9532Get"></a>
|
||||
<b>Get</b>
|
||||
<ul>
|
||||
<code>get <name></code>
|
||||
<br><br>
|
||||
Aktualisierung aller Werte
|
||||
</ul><br>
|
||||
|
||||
<a name="I2C_PCA9532Attr"></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 .. 15<br><br>
|
||||
</li>
|
||||
<li>T0/T1<br>
|
||||
Änderung der Frequenzwerte von PWM0/PWM1 nach der Formel: Fx = 152/(Tx + 1). Der entsprechende Frequenzwert wird unter Internals angezeigt.<br>
|
||||
Standard: 0 (152Hz), gültige Werte: 0-255<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
|
416
fhem/FHEM/52_I2C_PCF8574.pm
Normal file
416
fhem/FHEM/52_I2C_PCF8574.pm
Normal file
@ -0,0 +1,416 @@
|
||||
##############################################
|
||||
# $Id: 52_I2C_PCF8574.pm 3764 2014-01-22 07:09:38Z klausw $
|
||||
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->{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 InputPorts ".
|
||||
"$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/^Port((0|)[0-7])$/i) {
|
||||
return "wrong value: $val for \"set $name $cmd\" use one of: off, on"
|
||||
unless(exists($setsP{$val}));
|
||||
substr($cmd,0,4,""); #Nummer aus String extrahieren
|
||||
return "$name error: Port$cmd is defined as input" if ( $cmd ~~ @inports ); #Prüfen ob entsprechender Port Input ist
|
||||
my $sbyte = 0;
|
||||
foreach (0..7) {
|
||||
if ($_ == $cmd) { #Port der geändert werden soll
|
||||
$sbyte += $setsP{$val} << (1 * $_);
|
||||
} elsif ($_ ~~ @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 * $_); #->sonst aus dem Reading holen
|
||||
}
|
||||
}
|
||||
#$sendpackage{data} = sprintf("%.2X",$sbyte);
|
||||
$sendpackage{data} = $sbyte;
|
||||
} else {
|
||||
my $list = undef;
|
||||
foreach (0..7) {
|
||||
next if ( $_ ~~ @inports ); #Inputs überspringen
|
||||
$list .= "Port" . $_ . ":" . join(',', (sort { $setsP{ $a } <=> $setsP{ $b } } keys %setsP) ) . " ";
|
||||
}
|
||||
return "Unknown argument $a[1], choose one of " . $list;
|
||||
}
|
||||
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: über CallFn auf eigene Funktion
|
||||
my $phash = $hash->{IODev};
|
||||
my $pname = $phash->{NAME};
|
||||
CallFn($pname, "I2CWrtFn", $phash, \%sendpackage);
|
||||
#alt: für IOWrite
|
||||
#IOWrite($hash, \%sendpackage);
|
||||
}
|
||||
###################################
|
||||
sub I2C_PCF8574_Attr(@) { #
|
||||
my (undef, $name, $attr, $val) = @_;
|
||||
my $hash = $defs{$name};
|
||||
my $msg = '';
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
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";
|
||||
}
|
||||
|
||||
#für 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';
|
||||
return;
|
||||
}
|
||||
###################################
|
||||
sub I2C_PCF8574_Catch($) {
|
||||
my $exception = shift;
|
||||
if ($exception) {
|
||||
$exception =~ /^(.*)( at.*FHEM.*)$/;
|
||||
return $1;
|
||||
}
|
||||
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($@) { # über 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 für 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 über 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
|
||||
=begin html
|
||||
|
||||
<a name="I2C_PCF8574"></a>
|
||||
<h3>I2C_PCF8574</h3>
|
||||
<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 an 2 digit hexadecimal value<br>
|
||||
</ul>
|
||||
|
||||
<a name="I2C_PCF8574Set"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<code>set <name> <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>
|
||||
</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><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>
|
||||
<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>
|
||||
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 ein zweistelliger Hex-Wert<br>
|
||||
</ul>
|
||||
|
||||
<a name="I2C_PCF8574Set"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<code>set <name> <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>
|
||||
</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 .. 15<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
|
358
fhem/FHEM/52_I2C_SHT21.pm
Normal file
358
fhem/FHEM/52_I2C_SHT21.pm
Normal file
@ -0,0 +1,358 @@
|
||||
##############################################
|
||||
# $Id: 52_I2C_SHT21.pm 3764 2014-01-22 07:09:38Z klausw $
|
||||
|
||||
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',
|
||||
};
|
||||
|
||||
##################################################
|
||||
# Forward declarations
|
||||
#
|
||||
sub I2C_SHT21_Initialize($);
|
||||
sub I2C_SHT21_Define($$);
|
||||
sub I2C_SHT21_Attr(@);
|
||||
sub I2C_SHT21_Poll($);
|
||||
sub I2C_SHT21_Set($@);
|
||||
sub I2C_SHT21_Undef($$);
|
||||
|
||||
|
||||
my %sets = (
|
||||
'readValues' => 1,
|
||||
);
|
||||
|
||||
sub I2C_SHT21_Initialize($) {
|
||||
my ($hash) = @_;
|
||||
|
||||
$hash->{DefFn} = 'I2C_SHT21_Define';
|
||||
$hash->{InitFn} = 'I2C_SHT21_Init';
|
||||
$hash->{AttrFn} = 'I2C_SHT21_Attr';
|
||||
$hash->{SetFn} = 'I2C_SHT21_Set';
|
||||
$hash->{UndefFn} = 'I2C_SHT21_Undef';
|
||||
$hash->{I2CRecFn} = 'I2C_SHT21_I2CRec';
|
||||
|
||||
$hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 poll_interval:1,2,5,10,20,30 ' .
|
||||
'roundHumidityDecimal:0,1,2 roundTemperatureDecimal:0,1,2 ' .
|
||||
$readingFnAttributes;
|
||||
}
|
||||
|
||||
sub I2C_SHT21_Define($$) {
|
||||
my ($hash, $def) = @_;
|
||||
my @a = split('[ \t][ \t]*', $def);
|
||||
|
||||
$hash->{STATE} = "defined";
|
||||
|
||||
if ($main::init_done) {
|
||||
eval { I2C_SHT21_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); };
|
||||
return I2C_SHT21_Catch($@) if $@;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub I2C_SHT21_Init($$) {
|
||||
my ( $hash, $args ) = @_;
|
||||
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
if (defined $args && int(@$args) > 1)
|
||||
{
|
||||
return "Define: Wrong syntax. Usage:\n" .
|
||||
"define <name> I2C_SHT21 [<i2caddress>]";
|
||||
}
|
||||
|
||||
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);
|
||||
} else {
|
||||
$hash->{I2C_Address} = hex(SHT21_I2C_ADDRESS);
|
||||
}
|
||||
|
||||
|
||||
my $msg = '';
|
||||
# create default attributes
|
||||
$msg = CommandAttr(undef, $name . ' poll_interval 5');
|
||||
if ($msg) {
|
||||
Log3 ($hash, 1, $msg);
|
||||
return $msg;
|
||||
}
|
||||
AssignIoPort($hash);
|
||||
$hash->{STATE} = 'Initialized';
|
||||
|
||||
# my %sendpackage = ( i2caddress => $hash->{I2C_Address}, direction => "i2cread" );
|
||||
# $sendpackage{reg} = hex("AA");
|
||||
# $sendpackage{nbyte} = 22;
|
||||
# return "$name: no IO device defined" unless ($hash->{IODev});
|
||||
# my $phash = $hash->{IODev};
|
||||
# my $pname = $phash->{NAME};
|
||||
# CallFn($pname, "I2CWrtFn", $phash, \%sendpackage);
|
||||
|
||||
return undef;
|
||||
}
|
||||
|
||||
sub I2C_SHT21_Catch($) {
|
||||
my $exception = shift;
|
||||
if ($exception) {
|
||||
$exception =~ /^(.*)( at.*FHEM.*)$/;
|
||||
return $1;
|
||||
}
|
||||
return undef;
|
||||
}
|
||||
|
||||
|
||||
sub I2C_SHT21_Attr (@) {# hier noch Werteüberprüfung einfügen
|
||||
my (undef, $name, $attr, $val) = @_;
|
||||
my $hash = $defs{$name};
|
||||
my $msg = '';
|
||||
|
||||
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);
|
||||
} else {
|
||||
$msg = 'Wrong poll intervall defined. poll_interval must be a number > 0';
|
||||
}
|
||||
} elsif ($attr eq 'roundHumidityDecimal') {
|
||||
$msg = 'Wrong $attr defined. Use one of 0, 1, 2' if defined($val) && $val >= 0 && $val <= 2 ;
|
||||
} elsif ($attr eq 'roundTemperatureDecimal') {
|
||||
$msg = 'Wrong $attr defined. Use one of 0, 1, 2' if defined($val) && $val >= 0 && $val <= 2 ;
|
||||
}
|
||||
return ($msg) ? $msg : undef;
|
||||
}
|
||||
|
||||
sub I2C_SHT21_Poll($) {
|
||||
my ($hash) = @_;
|
||||
my $name = $hash->{NAME};
|
||||
|
||||
# Read values
|
||||
I2C_SHT21_Set($hash, ($name, 'readValues'));
|
||||
|
||||
my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0);
|
||||
if ($pollInterval > 0) {
|
||||
InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_SHT21_Poll', $hash, 0);
|
||||
}
|
||||
}
|
||||
|
||||
sub I2C_SHT21_Set($@) {
|
||||
my ($hash, @a) = @_;
|
||||
my $name = $a[0];
|
||||
my $cmd = $a[1];
|
||||
|
||||
if(!defined($sets{$cmd})) {
|
||||
return 'Unknown argument ' . $cmd . ', choose one of ' . join(' ', keys %sets)
|
||||
}
|
||||
|
||||
if ($cmd eq 'readValues') {
|
||||
I2C_SHT21_readTemperature($hash);
|
||||
I2C_SHT21_readHumidity($hash);
|
||||
}
|
||||
}
|
||||
|
||||
sub I2C_SHT21_Undef($$) {
|
||||
my ($hash, $arg) = @_;
|
||||
|
||||
RemoveInternalTimer($hash);
|
||||
return 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 für alle Keys in $clientmsg die mit dem physical Namen beginnen
|
||||
$hash->{$k} = $v if $k =~ /^$pname/ ;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sub I2C_SHT21_GetTemp ($$) {
|
||||
my ($hash, $rawdata) = @_;
|
||||
my @raw = split(" ",$rawdata);
|
||||
my $temperature = $raw[0] << 8 | $raw[1];
|
||||
$temperature = ( 175.72 * $temperature / 2**16 ) - 46.85;
|
||||
$temperature = sprintf(
|
||||
'%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f',
|
||||
$temperature
|
||||
);
|
||||
readingsSingleUpdate($hash,"temperature", $temperature, 1);
|
||||
}
|
||||
|
||||
sub I2C_SHT21_GetHum ($$) {
|
||||
my ($hash, $rawdata) = @_;
|
||||
my @raw = split(" ",$rawdata);
|
||||
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(
|
||||
'%.' . AttrVal($hash->{NAME}, 'roundHumidityDecimal', 1) . 'f',
|
||||
$humidity
|
||||
);
|
||||
readingsBeginUpdate($hash);
|
||||
readingsBulkUpdate(
|
||||
$hash,
|
||||
'state',
|
||||
'T: ' . $temperature . ' H: ' . $humidity
|
||||
);
|
||||
#readingsBulkUpdate($hash, 'temperature', $temperature);
|
||||
readingsBulkUpdate($hash, 'humidity', $humidity);
|
||||
readingsEndUpdate($hash, 1);
|
||||
}
|
||||
|
||||
|
||||
sub I2C_SHT21_readTemperature($) {
|
||||
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
|
||||
my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" };
|
||||
$i2creq->{data} = hex("F3");
|
||||
CallFn($pname, "I2CWrtFn", $phash, $i2creq);
|
||||
usleep(85000); #für 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);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
sub I2C_SHT21_readHumidity($) {
|
||||
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
|
||||
my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" };
|
||||
$i2creq->{data} = hex("F5");
|
||||
CallFn($pname, "I2CWrtFn", $phash, $i2creq);
|
||||
usleep(39000); #für 12bit
|
||||
|
||||
# Read the two byte result from device
|
||||
my $i2cread = { i2caddress => $hash->{I2C_Address}, direction => "i2cread" };
|
||||
$i2cread->{nbyte} = 2;
|
||||
$i2cread->{type} = "hum";
|
||||
CallFn($pname, "I2CWrtFn", $phash, $i2cread);
|
||||
|
||||
return; # $retVal;
|
||||
}
|
||||
|
||||
|
||||
1;
|
||||
|
||||
=pod
|
||||
=begin html
|
||||
|
||||
<a name="I2C_SHT21"></a>
|
||||
<h3>I2C_SHT21</h3>
|
||||
<ul>
|
||||
<a name="I2C_SHT21"></a>
|
||||
Provides an interface to the SHT21 I2C Humidity sensor from <a href="www.sensirion.com">Sensirion</a>.
|
||||
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_SHT21Define"></a><br>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> I2C_SHT21 [<I2C Address>]</code><br>
|
||||
where <code><I2C Address></code> is an 2 digit hexadecimal value<br>
|
||||
</ul>
|
||||
<a name="I2C_SHT21Set"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<code>set <name> readValues</code><br>
|
||||
Reads the current temperature and humidity values from sensor.<br><br>
|
||||
</ul>
|
||||
<a name="I2C_SHT21Attr"></a>
|
||||
<b>Attributes</b>
|
||||
<ul>
|
||||
<li>poll_interval<br>
|
||||
Set the polling interval in minutes to query data from sensor<br>
|
||||
Default: 5, valid values: 1,2,5,10,20,30<br><br>
|
||||
</li>
|
||||
<li>roundHumidityDecimal<br>
|
||||
Number of decimal places for humidity value<br>
|
||||
Default: 1, valid values: 0 1 2<br><br>
|
||||
</li>
|
||||
<li>roundTemperatureDecimal<br>
|
||||
Number of decimal places for temperature value<br>
|
||||
Default: 1, valid values: 0,1,2<br><br>
|
||||
</li>
|
||||
<li><a href="#IODev">IODev</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_SHT21"></a>
|
||||
<h3>I2C_SHT21</h3>
|
||||
<ul>
|
||||
<a name="I2C_SHT21"></a>
|
||||
Ermöglicht die Verwendung eines SHT21 I2C Feuchtesensors von <a href="www.sensirion.com">Sensirion</a>.
|
||||
I2C-Botschaften werden über ein I2C Interface Modul wie beispielsweise das <a href="#RPII2C">RPII2C</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_SHT21Define"></a><br>
|
||||
<b>Define</b>
|
||||
<ul>
|
||||
<code>define <name> I2C_SHT21 [<I2C Address>]</code><br>
|
||||
Der Wert <code><I2C Address></code> ist ein zweistelliger Hex-Wert<br>
|
||||
</ul>
|
||||
<a name="I2C_SHT21Set"></a>
|
||||
<b>Set</b>
|
||||
<ul>
|
||||
<code>set <name> readValues</code><br>
|
||||
Aktuelle Temperatur und Feuchte Werte vom Sensor lesen.<br><br>
|
||||
</ul>
|
||||
<a name="I2C_SHT21Attr"></a>
|
||||
<b>Attribute</b>
|
||||
<ul>
|
||||
<li>poll_interval<br>
|
||||
Aktualisierungsintervall aller Werte in Minuten.<br>
|
||||
Standard: 5, gültige Werte: 1,2,5,10,20,30<br><br>
|
||||
</li>
|
||||
<li>roundHumidityDecimal<br>
|
||||
Anzahl Dezimalstellen für den Feuchtewert<br>
|
||||
Standard: 1, gültige Werte: 0 1 2<br><br>
|
||||
</li>
|
||||
<li>roundTemperatureDecimal<br>
|
||||
Anzahl Dezimalstellen für den Temperaturwert<br>
|
||||
Standard: 1, gültige Werte: 0,1,2<br><br>
|
||||
</li>
|
||||
<li><a href="#IODev">IODev</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
|
Loading…
Reference in New Issue
Block a user