From 9fdaac2c90a87ddb17b1264d03b0340dbce3ac4c Mon Sep 17 00:00:00 2001 From: eisler Date: Sun, 4 Feb 2018 14:49:06 +0000 Subject: [PATCH] 52_I2C_EMC1001.pm: initial check in git-svn-id: https://svn.fhem.de/fhem/trunk/fhem@16082 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- CHANGED | 1 + FHEM/52_I2C_EMC1001.pm | 473 +++++++++++++++++++++++++++++++++++++++++ MAINTAINER.txt | 1 + 3 files changed, 475 insertions(+) create mode 100644 FHEM/52_I2C_EMC1001.pm diff --git a/CHANGED b/CHANGED index d7efe55b5..6b52b2313 100644 --- a/CHANGED +++ b/CHANGED @@ -1,5 +1,6 @@ # Add changes at the top of the list. Keep it in ASCII, and 80-char wide. # Do not insert empty lines here, update check depends on it. + - new: 52_I2C_EMC1001.pm: initial check in - feature: 30_HUEBridge, 31_HUEDevice: added createGroupReadings attribute - feature: f18: implement dragging / dashboard - change: 93_DbLog: V3.8.3, configCheck improved, execute cache only every diff --git a/FHEM/52_I2C_EMC1001.pm b/FHEM/52_I2C_EMC1001.pm new file mode 100644 index 000000000..d90719223 --- /dev/null +++ b/FHEM/52_I2C_EMC1001.pm @@ -0,0 +1,473 @@ +############################################## +# +# Modul for reading a EMC1001 digital temperature sensor via I2C +# (see http://ww1.microchip.com/downloads/en/DeviceDoc/20005411A.pdf) +# +# Copyright (C) 2018 Stephan Eisler +# +# This file is part of fhem. +# +# Fhem is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Fhem is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with fhem. If not, see . +# +# $Id$ +# +############################################## + +package main; + +use strict; +use warnings; + +use constant { + EMC1001_I2C_ADDRESS => 0x48, # EMC1001 I2C ADDRESS + Reg_TMP_HB => 0x00, # R temperature value high byte + Reg_STATUS => 0x01, # RC Status + Reg_TMP_LB => 0x02, # R low byte containing 1/4 deg fraction + Reg_Config => 0x03, # R/W Configuration + Reg_Cnv_Rate => 0x04, # R/W Conversion Rate + Reg_THL_HB => 0x05, # R/W Temperature High Limit High Byte + Reg_THL_LB => 0x06, # R/W Temperature High Limit Low Byte + Reg_TLL_HB => 0x07, # R/W Temperature Low Limit High Byte + Reg_TLL_LB => 0x08, # R/W Temperature Low Limit Low Byte + Reg_One_Sht => 0x0f, # R One-Shot + Reg_THM_LMT => 0x20, # R/W THERM Limit + Reg_THM_HYS => 0x21, # R/W THERM Hysteresis + Reg_SMB_TO => 0x22, # R/W SMBus Timeout Enable + Reg_Prd_ID => 0xfd, # R Product ID Register + Reg_Mnf_ID => 0xfe, # R Manufacture ID + Reg_Rev_No => 0xff # R Revision Number +}; + +################################################## +# Forward declarations +# +sub I2C_EMC1001_I2CRec ($$); +sub I2C_EMC1001_GetReadings ($$); +sub I2C_EMC1001_GetTemp ($@); +sub I2C_EMC1001_calcTrueTemperature($$); + + +my %sets = ( + 'readValues' => 1, +); + +sub I2C_EMC1001_Initialize($) { + my ($hash) = @_; + + $hash->{DefFn} = 'I2C_EMC1001_Define'; + $hash->{InitFn} = 'I2C_EMC1001_Init'; + $hash->{AttrFn} = 'I2C_EMC1001_Attr'; + $hash->{SetFn} = 'I2C_EMC1001_Set'; + #$hash->{GetFn} = 'I2C_EMC1001_Get'; + $hash->{UndefFn} = 'I2C_EMC1001_Undef'; + $hash->{I2CRecFn} = 'I2C_EMC1001_I2CRec'; + $hash->{AttrList} = 'IODev do_not_notify:0,1 showtime:0,1 poll_interval:1,2,5,10,20,30 ' . + 'roundTemperatureDecimal:0,1,2 ' . + $readingFnAttributes; + $hash->{DbLog_splitFn} = "I2C_EMC1001_DbLog_splitFn"; +} + +sub I2C_EMC1001_Define($$) { + my ($hash, $def) = @_; + my @a = split('[ \t][ \t]*', $def); + $hash->{STATE} = 'defined'; + + my $name = $a[0]; + + my $msg = ''; + if((@a < 2)) { + $msg = 'wrong syntax: define I2C_EMC1001 [I2C-Address]'; + } + if ($msg) { + Log3 ($hash, 1, $msg); + return $msg; + } + if ($main::init_done) { + eval { I2C_EMC1001_Init( $hash, [ @a[ 2 .. scalar(@a) - 1 ] ] ); }; + return I2C_EMC1001_Catch($@) if $@; + } +} + +sub I2C_EMC1001_Init($$) { # wird bei FHEM start Define oder wieder + my ( $hash, $args ) = @_; + my $name = $hash->{NAME}; + + if (defined (my $address = shift @$args)) { + $hash->{I2C_Address} = $address =~ /^0x.*$/ ? oct($address) : $address; + return "$name: I2C Address not valid" unless ($hash->{I2C_Address} < 128 && $hash->{I2C_Address} > 3); + } else { + $hash->{I2C_Address} = EMC1001_I2C_ADDRESS; + } + 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; + # } + #} + eval { + AssignIoPort($hash, AttrVal($hash->{NAME},"IODev",undef)); + I2C_EMC1001_i2cread($hash, Reg_Prd_ID, 1); #get Prd Id + I2C_EMC1001_i2cread($hash, Reg_Mnf_ID, 1); #get Mnf Id + I2C_EMC1001_i2cread($hash, Reg_Rev_No, 1); #get Reg Rev No + }; + return I2C_EMC1001_Catch($@) if $@; +} + +sub I2C_EMC1001_Catch($) { # Fehlermeldungen formattieren + my $exception = shift; + if ($exception) { + $exception =~ /^(.*)( at.*FHEM.*)$/; + return $1; + } + return undef; +} + +sub I2C_EMC1001_Attr (@) { # Wird beim Attribut anlegen/aendern aufgerufen + my ($command, $name, $attr, $val) = @_; + my $hash = $defs{$name}; + my $msg = ''; + + if (defined $command && $command eq "set" && $attr eq "IODev") { + eval { + if ($main::init_done and (!defined ($hash->{IODev}) or $hash->{IODev}->{NAME} ne $val)) { + main::AssignIoPort($hash,$val); + my @def = split (' ',$hash->{DEF}); + I2C_EMC1001_Init($hash,\@def) if (defined ($hash->{IODev})); + } + }; + $msg = I2C_EMC1001_Catch($@) if $@; + } elsif ($attr eq 'poll_interval') { + if (defined($val)) { + if ($val =~ m/^(0*[1-9][0-9]*)$/) { + RemoveInternalTimer($hash); + I2C_EMC1001_Poll($hash) if ($main::init_done); + #InternalTimer(gettimeofday() + 5, 'I2C_EMC1001_Poll', $hash, 0) if ($main::init_done); + } else { + $msg = 'Wrong poll intervall defined. poll_interval must be a number > 0'; + } + } else { + RemoveInternalTimer($hash); + } + } elsif ($attr =~ m/^round(Temperature)Decimal$/ && defined($val)) { + $msg = 'Wrong value: $val for $attr defined. value must be a one of 0,1,2' unless ($val =~ m/^(0*[0-2])$/); + } + return ($msg) ? $msg : undef; +} + +sub I2C_EMC1001_Poll($) { # Messwerte regelmaessig anfordern + my ($hash) = @_; + my $name = $hash->{NAME}; + + I2C_EMC1001_Set($hash, ($name, 'readValues')); # Read values + my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); + if ($pollInterval > 0) { + InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_EMC1001_Poll', $hash, 0); + } +} + +sub I2C_EMC1001_Set($@) { # Messwerte manuell anfordern + 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) . ":noArg" + } + + if ($cmd eq 'readValues') { + I2C_EMC1001_i2cread($hash, Reg_TMP_HB, 1); + I2C_EMC1001_i2cread($hash, Reg_TMP_LB, 1); + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_EMC1001_UpdateReadings', $hash, 0); + } + return undef +} + +sub I2C_EMC1001_Get($@) { # Messwerte manuell anfordern + my ($hash, @a) = @_; + my $name = $a[0]; + my $cmd = $a[1]; + + if (defined($cmd) && $cmd eq 'readValues') { + I2C_EMC1001_i2cread($hash, Reg_TMP_HB, 1); + I2C_EMC1001_i2cread($hash, Reg_TMP_LB, 1); + RemoveInternalTimer($hash); + InternalTimer(gettimeofday() + 1, 'I2C_EMC1001_UpdateReadings', $hash, 0); + } else { + return 'Unknown argument ' . $cmd . ', choose one of readValues:noArg'; + } + return undef +} + +sub I2C_EMC1001_UpdateReadings($) { # Messwerte auslesen + my ($hash) = @_; + I2C_EMC1001_i2cread($hash, Reg_TMP_HB, 1); + I2C_EMC1001_i2cread($hash, Reg_TMP_LB, 1); + my $pollInterval = AttrVal($hash->{NAME}, 'poll_interval', 0); #poll_interval Timer wiederherstellen + InternalTimer(gettimeofday() + ($pollInterval * 60), 'I2C_EMC1001_Poll', $hash, 0) if ($pollInterval > 0); +} + +sub I2C_EMC1001_Undef($$) { # Device loeschen + my ($hash, $arg) = @_; + RemoveInternalTimer($hash); + return undef; +} + +sub I2C_EMC1001_I2CRec ($$) { # wird vom IODev aus aufgerufen wenn I2C Daten vorliegen + my ($hash, $clientmsg) = @_; + my $name = $hash->{NAME}; + my $pname = undef; + my $phash = $hash->{IODev}; + $pname = $phash->{NAME}; + while ( my ( $k, $v ) = each %$clientmsg ) { #erzeugen von Internals fuer alle Keys in $clientmsg die mit dem physical Namen beginnen + $hash->{$k} = $v if $k =~ /^$pname/ ; + } + + if ( $clientmsg->{direction} && $clientmsg->{$pname . "_SENDSTAT"} && $clientmsg->{$pname . "_SENDSTAT"} eq "Ok" ) { + if ( $clientmsg->{direction} eq "i2cread" && defined($clientmsg->{received}) ) { + Log3 $hash, 5, "$name Rx, Reg: $clientmsg->{reg}, Data: $clientmsg->{received}"; + I2C_EMC1001_GetProdId ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_Prd_ID && $clientmsg->{nbyte} == 1; + I2C_EMC1001_GetMnfId ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_Mnf_ID && $clientmsg->{nbyte} == 1; + I2C_EMC1001_GetRevN ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_Rev_No && $clientmsg->{nbyte} == 1; + I2C_EMC1001_GetReadingsTemperatureValueHighByte ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_TMP_HB && $clientmsg->{nbyte} == 1; + I2C_EMC1001_GetReadingsTemperatureValueLowByte ($hash, $clientmsg->{received}) if $clientmsg->{reg} == Reg_TMP_LB && $clientmsg->{nbyte} == 1; + } + } + + return undef +} + +sub I2C_EMC1001_GetProdId ($$) { + my ($hash, $rawdata) = @_; + if ($rawdata == hex("00")) { + $hash->{DeviceType} = "EMC1001"; + } elsif ($rawdata == hex("01")) { + $hash->{DeviceType} = "EMC1001-1"; + } + readingsSingleUpdate($hash, 'DeviceType', $hash->{DeviceType}, 1); + $hash->{STATE} = 'Initialized'; + I2C_EMC1001_Poll($hash) if defined(AttrVal($hash->{NAME}, 'poll_interval', undef)); # wenn poll_interval definiert -> timer starten +} + +sub I2C_EMC1001_GetMnfId ($$) { + my ($hash, $rawdata) = @_; + readingsSingleUpdate($hash, 'DeviceManufactureId', sprintf("0x%X", $rawdata), 1); +} + +sub I2C_EMC1001_GetRevN ($$) { + my ($hash, $rawdata) = @_; + readingsSingleUpdate($hash, 'DeviceRevisionNumber', sprintf("%d", $rawdata), 1); +} + +sub I2C_EMC1001_GetReadingsTemperatureValueHighByte ($$) { # empfangenes Temperature High Byte verarbeiten + my ($hash, $rawdata) = @_; + Log3 $hash, 5, "ReadingsTemperatureValueHighByte: $rawdata"; + $hash->{TemperatureValueHighByte} = $rawdata; +} + +sub I2C_EMC1001_GetReadingsTemperatureValueLowByte ($$) { # empfangenes Temperature Low Byte verarbeiten + my ($hash, $rawdata) = @_; + Log3 $hash, 5, "ReadingsTemperatureValueLowByte: $rawdata"; + $hash->{TemperatureValueLowByte} = $rawdata; + I2C_EMC1001_GetTemp($hash, $rawdata); + + my $tem = ReadingsVal($hash->{NAME},"temperature", undef); + readingsSingleUpdate( + $hash, + 'state', + (defined $tem ? "T: $tem " : ""), + 1 + ); +} + +sub I2C_EMC1001_GetTemp($@) { # Temperatur Messwerte verarbeiten + my ($hash, @raw) = @_; + + my $temp= $hash->{TemperatureValueHighByte}; + my $templo= $hash->{TemperatureValueLowByte}; + + $templo = $templo >> 6; + + if ($temp < 0) { + $templo = 3-$templo; + } + my $temperature = sprintf( + '%.' . AttrVal($hash->{NAME}, 'roundTemperatureDecimal', 1) . 'f', + sprintf("%d.%d", $temp, $templo*25) + ); + readingsSingleUpdate($hash, 'temperature', $temperature, 1); +} + +sub I2C_EMC1001_i2cread($$$) { # Lesebefehl an Hardware absetzen (antwort kommt in I2C_*****_I2CRec an) + my ($hash, $reg, $nbyte) = @_; + if (defined (my $iodev = $hash->{IODev})) { + Log3 $hash, 5, "$hash->{NAME}: $hash->{I2C_Address} read $nbyte Byte from Register $reg"; + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cread", + i2caddress => $hash->{I2C_Address}, + reg => $reg, + nbyte => $nbyte + }); + } else { + return "no IODev assigned to '$hash->{NAME}'"; + } +} + +sub I2C_EMC1001_i2cwrite($$$) { # Schreibbefehl an Hardware absetzen + my ($hash, $reg, @data) = @_; + if (defined (my $iodev = $hash->{IODev})) { + Log3 $hash, 5, "$hash->{NAME}: $hash->{I2C_Address} write " . join (' ',@data) . " to Register $reg"; + CallFn($iodev->{NAME}, "I2CWrtFn", $iodev, { + direction => "i2cwrite", + i2caddress => $hash->{I2C_Address}, + reg => $reg, + data => join (' ',@data), + }); + } else { + return "no IODev assigned to '$hash->{NAME}'"; + } +} + +sub I2C_EMC1001_DbLog_splitFn($) { # Einheiten + my ($event) = @_; + Log3 undef, 5, "in DbLog_splitFn empfangen: $event"; + my ($reading, $value, $unit) = ""; + + my @parts = split(/ /,$event); + $reading = shift @parts; + $reading =~ tr/://d; + $value = $parts[0]; + $unit = "\xB0C" if(lc($reading) =~ m/temp/); + return ($reading, $value, $unit); +} + +1; + +=pod +=item device +=item summary reads temperature from an via I2C connected EMC1001 +=item summary_DE lese Temperatur eines über I2C angeschlossenen EMC1001 +=begin html + + +

I2C_EMC1001

+(en | de) +
    + + Provides an interface to the digital temperature sensor EMC1001 + 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 EMC1001 I2C_EMC1001 [<I2C Address>]

      + without defined <I2C Address> 0x48 will be used as address
      +
      + Examples: +
      +      define EMC1001 I2C_EMC1001 0x48
      +      attr EMC1001 poll_interval 5
      +			attr roundTemperatureDecimal 2
      +    
      +
    + + + Set +
      + set EMC1001 <readValues> +

      + Reads current temperature values from the sensor.
      + Normaly this execute automaticly at each poll intervall. You can execute + this manually if you want query the current values. +

      +
    + + + Attributes +
      +
    • poll_interval
      + Set the polling interval in minutes to query the sensor for new measured + values.
      + Default: 5, valid values: any whole number

      +
    • +
    • roundTemperatureDecimal
      + Round temperature values to given decimal places.
      + Default: 1, valid values: 0, 1, 2

      +
    • +
    • IODev
    • +
    • do_not_notify
    • +
    • showtime
    • +

    +
+ +=end html + +=begin html_DE + + +

I2C_EMC1001

+(en | de) +
    + + Ermöglicht die Verwendung eines digitalen Temperatur EMC1001 über den I2C Bus des Raspberry Pi.

    + 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 EMC1001 <EMC1001_name> [<I2C Addresse>]

      + Fehlt <I2C Address> wird 0x48 verwendet
      +
      + Beispiel: +
      +			define EMC1001 I2C_EMC1001 0x48
      +			attr EMC1001 poll_interval 5
      +			attr roundTemperatureDecimal 2
      +		
      +
    + + + Set +
      + set EMC1001 readValues +

      + set <name> readValues
      + Aktuelle Temperatur Werte vom Sensor lesen.

      +
    + + + Attribute +
      +
    • poll_interval
      + Definiert das Poll Intervall in Minuten für das Auslesen einer neuen Messung.
      + Default: 5, gültige Werte: 1, 2, 5, 10, 20, 30

      +
    • +
    • roundTemperatureDecimal
      + Rundet jeweils den Temperaturwert mit den angegebenen Nachkommastellen.
      + Standard: 1, gültige Werte: 0, 1, 2

      +
    • +
    • IODev
    • +
    • do_not_notify
    • +
    • showtime
    • +

    +
+ +=end html_DE +=cut + diff --git a/MAINTAINER.txt b/MAINTAINER.txt index 4c398f7a3..59f4036a3 100644 --- a/MAINTAINER.txt +++ b/MAINTAINER.txt @@ -249,6 +249,7 @@ FHEM/51_Netzer.pm klausw Sonstige Systeme FHEM/51_RPI_GPIO.pm klausw Einplatinencomputer FHEM/52_I2C_DS1307 ntruchsess Sonstige Systeme FHEM/52_I2C_EEPROM.pm klausw Sonstige Systeme +FHEM/52_I2C_EMC1001.pm eisler Sonstige Systeme FHEM/52_I2C_LCD ntruchsess Sonstige Systeme FHEM/52_I2C_BME280 klausw Sonstige Systeme FHEM/52_I2C_K30 yoda_gh Sonstige Systeme