############################################## # $Id$ package main; use strict; use warnings; use Time::HiRes qw(usleep); use Scalar::Util qw(looks_like_number); #use Error qw(:try); use constant { MCP3422_I2C_ADDRESS => '0x68', }; ################################################## # Forward declarations # sub I2C_MCP342x_Initialize($); sub I2C_MCP342x_Define($$); sub I2C_MCP342x_Attr(@); sub I2C_MCP342x_Poll($); sub I2C_MCP342x_Set($@); sub I2C_MCP342x_Undef($$); my %resols = ( '12' => { code => 0b00000000, delay => 5690, lsb => 1000, }, '14' => { code => 0b00000100, delay => 22730, lsb => 250, }, '16' => { code => 0b00001000, delay => 90910, lsb => 62.5, }, '18' => { code => 0b00001100, delay => 363640, lsb => 15.625, }, ); my %gains = ( '1' => 0b00000000, '2' => 0b00000001, '4' => 0b00000010, '8' => 0b00000011, ); sub I2C_MCP342x_Initialize($) { my ($hash) = @_; $hash->{DefFn} = 'I2C_MCP342x_Define'; $hash->{InitFn} = 'I2C_MCP342x_Init'; $hash->{AttrFn} = 'I2C_MCP342x_Attr'; $hash->{GetFn} = 'I2C_MCP342x_Get'; $hash->{UndefFn} = 'I2C_MCP342x_Undef'; $hash->{I2CRecFn} = 'I2C_MCP342x_I2CRec'; $hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 poll_interval:1,2,5,10,20,30 ' . 'ch1roundDecimal:0,1,2,3 ch1gain:1,2,4,8 ch1resolution:12,14,16,18 ch1factor '. 'ch2roundDecimal:0,1,2,3 ch2gain:1,2,4,8 ch2resolution:12,14,16,18 ch2factor '. 'ch3roundDecimal:0,1,2,3 ch3gain:1,2,4,8 ch3resolution:12,14,16,18 ch3factor '. 'ch4roundDecimal:0,1,2,3 ch4gain:1,2,4,8 ch4resolution:12,14,16,18 ch4factor '. $readingFnAttributes; } sub I2C_MCP342x_Define($$) { my ($hash, $def) = @_; my @a = split('[ \t][ \t]*', $def); $hash->{STATE} = "defined"; if ($main::init_done) { eval { I2C_MCP342x_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); }; return I2C_MCP342x_Catch($@) if $@; } return undef; } sub I2C_MCP342x_Init($$) { my ( $hash, $args ) = @_; my $name = $hash->{NAME}; Log3 $hash, 5, "$hash->{NAME}: Init Argumente1: $args"; if (defined $args && int(@$args) < 1) { Log3 $hash, 0, "Define: Wrong syntax. Usage:\n" . "define MCP342x [] []"; } if (defined (my $address = shift @$args)) { $hash->{I2C_Address} = $address =~ /^0x.*$/ ? oct($address) : $address; Log3 $hash, 0, "$name: I2C Address not valid" unless ($hash->{I2C_Address} < 128 && $hash->{I2C_Address} > 3); } else { $hash->{I2C_Address} = hex(MCP3422_I2C_ADDRESS); } if (defined (my $channels = shift @$args)) { $hash->{channels} = ($channels == 4 ? 4 : 2); } else { $hash->{channels} = 2; } my $msg = ''; # create default attributes if (AttrVal($name, 'poll_interval', '?') eq '?') { $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_MCP342x_Catch($) { my $exception = shift; if ($exception) { $exception =~ /^(.*)( at.*FHEM.*)$/; return $1; } return undef; } sub I2C_MCP342x_Attr (@) {# hier noch Werteueberpruefung einfuegen 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_MCP342x_Init($hash,\@def) if (defined ($hash->{IODev})); } } 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_MCP342x_Poll', $hash, 0); } else { $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0'; } } elsif ($attr eq 'roundDecimal') { $msg = 'Wrong $attr defined. Use one of 0, 1, 2' if defined($val) && $val <= 0 && $val >= 3 ; } elsif ($attr eq 'gain') { foreach (split (/,/,$val)) { my @pair = split (/=/,$_); $msg = "wrong value: $_ for \"attr $hash->{NAME} $attr\" use comma separated =1|2|4|8 where = 1-$hash->{channels}" unless ( ( scalar(@pair) == 2 && $pair[0] =~ m/^[1-4]$/i && $pair[0] <= $hash->{channels} && $pair[1] =~ m/^(1|2|4|8)$/i ) || $val =~ m/^(1|2|4|8)$/i); } } elsif ($attr eq 'resolution') { foreach (split (/,/,$val)) { my @pair = split (/=/,$_); $msg = "wrong value: $_ for \"attr $hash->{NAME} $attr\" use comma separated =12|14|16|18 where = 1-$hash->{channels}" unless ( ( scalar(@pair) == 2 && $pair[0] =~ m/^[1-4]$/i && $pair[1] =~ m/^1(2|4|6|8)$/i ) && $val =~ m/^1(2|4|6|8)$/i ); } } return ($msg) ? $msg : undef; } sub I2C_MCP342x_Poll($) { my ($hash) = @_; my $name = $hash->{NAME}; # Read values I2C_MCP342x_Get($hash, $name); my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); if ($pollInterval > 0) { InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_MCP342x_Poll', $hash, 0); } } sub I2C_MCP342x_Get($@) { my ($hash, @a) = @_; my $name = $a[0]; my $cmd = $a[1]; my $rex = "^[1-" . $hash->{channels} . "]\$"; if (defined $cmd && $cmd =~ m/$rex/i) { my $resol = defined $a[2] ? $a[2] : AttrVal($hash->{NAME},("ch" . $cmd . "resolution"),"12"); return "Wrong resolution, use 12, 14, 16 or 18" unless $resol =~ m/^1(2|4|6|8)$/i; my $gain = defined $a[3] ? $a[3] : AttrVal($hash->{NAME},("ch" . $cmd . "gain"),"1"); return "Wrong gain, use 1, 2, 4 or 8" unless $gain =~ m/^(1|2|4|8)$/i; my $ts = ReadingsTimestamp($hash->{NAME},("Channel".$cmd),0); I2C_MCP342x_readvoltage($hash,$cmd,$resol,$gain); foreach (1..400) { #max 2s warten usleep 5000; return ReadingsVal($hash->{NAME},("Channel".$cmd),undef) if $ts ne ReadingsTimestamp($hash->{NAME},("Channel".$cmd),0); } } else { foreach (1..$hash->{channels}) { my $resol = defined $a[3] ? $a[3] : AttrVal($hash->{NAME},("ch" . $_ . "resolution"),"12"); return "Wrong resolution, use 12, 14, 16 or 18" unless $resol =~ m/^1(2|4|6|8)$/i; my $gain = defined $a[4] ? $a[4] : AttrVal($hash->{NAME},("ch" . $_ . "gain"),"1"); return "Wrong gain, use 1, 2, 4 or 8" unless $gain =~ m/^(1|2|4|8)$/i; I2C_MCP342x_readvoltage($hash,$_,$resol,$gain); } my @gets = ('1', '2'); push(@gets,('3', '4')) if $hash->{channels} == 4; return 'Unknown argument' . (defined $cmd ? (" " . $cmd) : "" ) . ', choose one of ' . join(' ', @gets) } } sub I2C_MCP342x_Undef($$) { my ($hash, $arg) = @_; RemoveInternalTimer($hash); return undef; } sub I2C_MCP342x_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/ ; } #my $ankommen = "$hash->{NAME}: vom physical empfangen"; # foreach my $av (keys %{$clientmsg}) { $ankommen .= "|" . $av . ": " . $clientmsg->{$av}; } #Log3 $hash, 1, $ankommen; if ($clientmsg->{direction} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok") { readingsSingleUpdate($hash,"state", "Ok", 1); if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) {###hier noch normal read rein,wenn alles wieder ok #Log3 $hash, 1, "empfangen: $clientmsg->{received}"; I2C_MCP342x_GetVoltage ($hash, $clientmsg->{received}); # if $clientmsg->{type} eq "temp" && $clientmsg->{nbyte} == 2; } } else { readingsSingleUpdate($hash,"state", "transmission error", 1); Log3 $hash, 3, "$name: failurei 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_MCP342x_GetVoltage ($$) { my ($hash, $rawdata) = @_; my @raw = split(" ",$rawdata); if ( defined($raw[4]) ) { if ( ($raw[4] & 0b10000000) == 0 ) { my $channel = 1 + (($raw[4] & 0b01100000) >> 5 ); my $resol = 2 * (($raw[4] & 0b00001100) >> 2 ) + 12; my $gain = 2 ** ($raw[4] & 0b00000011); my $rawvolt; if ($resol == 18) { $rawvolt = ($raw[0] & 0b00000011) << 16 | $raw[1] << 8 | $raw[2]; } elsif ($resol == 14) { $rawvolt = ($raw[0] & 0b00111111) << 8 | $raw[1]; } elsif ($resol == 12) { $rawvolt = ($raw[0] & 0b00001111) << 8 | $raw[1]; } else { $rawvolt = $raw[0] << 8 | $raw[1]; } Log3 $hash, 4, "Kanal: $channel, rawvolt: $rawvolt, Aufloesung: $resol, Gain: $gain, LSB: $resols{$resol}{lsb}"; $rawvolt -= (1 << $resol) if $rawvolt >= (1 << ($resol - 1)); Log3 $hash, 4, "Kanal: $channel, Signedrawvolt: $rawvolt"; my $voltage = ( $rawvolt * $resols{$resol}{lsb} ) / $gain ; $voltage /= 1000000; # LSB Werte in µV $voltage *= AttrVal($hash->{NAME},("ch" . $channel . "factor"),"1"); $voltage = sprintf( '%.' . AttrVal($hash->{NAME}, ('ch' . $channel . 'roundDecimal'), 3) . 'f', $voltage ); $voltage .= " overflow" if ( $rawvolt == ( (1<<($resol-1)) - 1) || $rawvolt == (1<<($resol-1)) ); readingsSingleUpdate($hash,"Channel$channel", $voltage, 1); } else { Log3 $hash, 3, $hash->{NAME} . " error, output conversion not finished"; } } } sub I2C_MCP342x_readvoltage($@) { my ($hash, $channel, $resol, $gain) = @_; my $name = $hash->{NAME}; return "$name: no IO device defined" unless ($hash->{IODev}); my $phash = $hash->{IODev}; my $pname = $phash->{NAME}; #0b10010000 my $confreg = 1 << 7; # 1|| |||| Initiate a new conversion $confreg |= ($channel - 1) << 5; # 11 |||| Channel Selection Bits $confreg |= $resols{$resol}{code}; # 11|| Sample Rate Selection Bit $confreg |= $gains{$gain}; # 11 PGA Gain Selection Bits #Log3 $hash, 1, "confinhalt: " . sprintf ('0b%08b', $confreg); # Write CONFIGURATION REGISTER to device. This requests a conversion process my $i2creq = { i2caddress => $hash->{I2C_Address}, direction => "i2cwrite" }; $i2creq->{data} = $confreg; CallFn($pname, "I2CWrtFn", $phash, $i2creq); usleep($resols{$resol}{delay}); #Verzoegerung # Read the result from device my $i2cread = { i2caddress => $hash->{I2C_Address}, direction => "i2cread" }; $i2cread->{nbyte} = 5; #$i2cread->{type} = "temp"; CallFn($pname, "I2CWrtFn", $phash, $i2cread); return; } 1; =pod =item device =item summary reads the analog inputs from an via I2C connected MCP342x =item summary_DE lesen der Analogeingänge eines über I2C angeschlossenen MCP342x =begin html

I2C_MCP342x

(en | de)
    Provides an interface to the MCP3422/3/4 A/D converter. 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_MCP342x [[<I2C Address>] <n channels>]
      <I2C Address> may be an 2 digit hexadecimal value (0xnn) or an decimal value
      For example 0x40 (hexadecimal) = 64 (decimal). An I2C address are 7 MSB, the LSB is the R/W bit.
      <n channels> is the number of A/D channels

    Get
      get <name> [[[<channel>] <resolution> ] <gain>]
      Returns the level on specific <channel>. <resolution> and <gain> will override attibutes for actual operation. Without attributes only the readings will be refreshed.

    Attributes
    • poll_interval
      Set the polling interval in minutes to query data from sensor
      Default: 5, valid values: 1,2,5,10,20,30

    • Following attributes are separate for all channels.

    • ch1resolution
      resolution settings
      the bigger the resolution the longer the conversion time.
      Default: 12, valid values: 12,14,16,18

    • ch1gain
      gain setting
      Important: the gain setting will reduce the measurement range an may produce an overflow. In this case "overflow" will be added to reading
      Default: 1, valid values: 1,2,4,8

    • ch1factor
      correction factor (will be mutiplied to channel value)
      Default: 1, valid values: number

    • ch1roundDecimal
      Number of decimal places for value
      Default: 3, valid values: 0,1,2,3

    • IODev
    • do_not_notify
    • showtime

=end html =begin html_DE

I2C_MCP342x

(en | de)
    Ermöglicht die Verwendung eines MCP3422/3/4 I2C A/D Wandler. 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_MCP342x [[<I2C Address>] <n channels>]
      <I2C Address> kann ein zweistelliger Hex-Wert (0xnn) oder ein Dezimalwert sein
      Beispielsweise 0x40 (hexadezimal) = 64 (dezimal). Als I2C Adresse verstehen sich die 7 MSB, das LSB ist das R/W Bit.
      <n channels> ist die Anzahl der A/D Kanäle.
    Get
      get <name> [[[<channel>] <resolution> ] <gain>]
      Aktuelle Werte vom entstrechenden <channel> lesen. <resolution> und <gain> überschreiben die entsprechenden Attribute für diesen Lesevorgang

    Attribute
    • poll_interval
      Aktualisierungsintervall aller Werte in Minuten.
      Standard: 5, gültige Werte: 1,2,5,10,20,30

    • Folgende Attribute existieren separat für alle Kanäle.

    • ch1resolution
      Auflösung des Kanals
      Je größer die Auflösung desto länger die Lesezeit.
      Standard: 12, gültige Werte: 12,14,16,18

    • ch1gain
      Verstärkungsfaktor
      Wichtig: Der Verstärkungsfaktor verringert den Messbereich entsprechend und kann zu einem Überlauf führen. In diesem Fall wird "overflow" an das reading angehängt.
      Standard: 1, gültige Werte: 1,2,4,8

    • ch1factor
      Korrekturfaktor (Wird zum Kanalwert multipliziert.)
      Standard: 1, gültige Werte: Zahl

    • ch1roundDecimal
      Anzahl Dezimalstellen für den Messwert
      Standard: 3, gültige Werte: 0,1,2,3

    • IODev
    • do_not_notify
    • showtime

=end html_DE =cut