diff --git a/fhem/contrib/70_PT8005.pm b/fhem/contrib/70_PT8005.pm new file mode 100644 index 000000000..fe3b22889 --- /dev/null +++ b/fhem/contrib/70_PT8005.pm @@ -0,0 +1,583 @@ +######################################################################################## +# +# PT8005.pm +# +# FHEM module to read the data from a PeakTech PT8005 sound level meter +# +# Prof. Dr. Peter A. Henning, 2013 +# +# Version 0.1 - November 2013 +# +# Setup as: +# define PT8005 +# +# where may be replaced by any name string and +# is a serial (USB) device or the keyword "emulator". +# In the latter case, a 4.5 kWP solar installation is simulated +# +# get present => 1 if device present, 0 if not +# get reading => measurement for all channels +# +# Additional attributes are defined in fhem.cfg as +# attr pt8005 room Noise +# Monthly and yearly log file +# attr pt8005 LogM NoiseLogM +# attr pt8005 LogY NoiseLogY +# +######################################################################################## +# +# This programm 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. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script 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. +# +######################################################################################## +package main; + +use strict; +use warnings; +use Device::SerialPort; + +#-- Prototypes to make komodo happy +use vars qw{%attr %defs}; +sub Log($$); + +#-- globals +my $freq ="db(A)"; # dB(A) or dB(C) +my $speed="fast"; # response speed fast or slow +my $mode ="normal"; # min/max/... +my $range="50-100 dB"; # measurement range +my $over =""; # over/underflow + +#-- These we may get on request +my %gets = ( + "present" => "", + "reading" => "R", +); + +#-- These occur in a pulldown menu as settable values +my %sets = ( + "Min/Max"=> "", + "off" => "O", + "rec" => "", + "speed" => "", + "range" => "", # toggle the measurement range + "auto" => "", # set the measurement range to auto + "dBA/C" => "", # toggle the frequency curve + "freq" => "" # set the frequency curve to a value db(A) or db(C) +); + +#-- Single key commands to the PT8005 +my %SKC = ("Min/Max","\x11", "off","\x33", "rec","\x55", "speed","\x77", "range","\x88", "dBA/C","\x99"); + + +######################################################################################## +# +# PT8005_Initialize +# +# Parameter hash +# +######################################################################################## + +sub PT8005_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "PT8005_Define"; + $hash->{GetFn} = "PT8005_Get"; + $hash->{SetFn} = "PT8005_Set"; + # LogM, LogY = name of the monthly and yearly log file + $hash->{AttrList}= "LogM LogY ". + "loglevel ". + $readingFnAttributes; +} + +######################################################################################## +# +# PT8005_Define - Implements DefFn function +# +# Parameter hash, definition string +# +######################################################################################## + +sub PT8005_Define($$) { + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "Define the serial device as a parameter" + if(@a != 3); + + my $dev = $a[2]; + + Log 1, "PT8005 opening device $dev"; + my $pt8005_serport = new Device::SerialPort ($dev); + return "PT8005 Can't open $dev: $!" if(!$pt8005_serport); + Log 1, "PT8005 opened device $dev"; + $hash->{USBDev} = $pt8005_serport; + sleep(1); + $pt8005_serport->close(); + + $hash->{DeviceName} = $dev; + $hash->{INTERVAL} = 60; # call every 60 seconds + + $modules{PT8005}{defptr}{$a[0]} = $hash; + + #-- InternalTimer blocks if init_done is not true + my $oid = $init_done; + $init_done = 1; + readingsSingleUpdate($hash,"state","initialized",1); + + PT8005_GetStatus($hash); + $init_done = $oid; + return undef; +} + +######################################################################################## +# +# PT8005_Cmd - Write command to meter +# +# Parameter hash, cmd = command +# +######################################################################################## + + sub PT8005_Cmd ($$) { + my ($hash, $cmd) = @_; + + my $res; + my $dev= $hash->{DeviceName}; + my $name = $hash->{NAME}; + my $serport = new Device::SerialPort ($dev); + + if(!$serport) { + Log GetLogLevel($name,1), "PT8005: Can't open $dev: $!"; + return undef; + } + $serport->reset_error(); + $serport->baudrate(9600); + $serport->databits(8); + $serport->parity('none'); + $serport->stopbits(1); + $serport->handshake('none'); + $serport->write_settings; + + #-- calculate checksum and send + #my $cmd="\x33"; + my $count_out = $serport->write($cmd); + Log GetLogLevel($name,3), "PT8005 write failed\n" unless ($count_out); + #-- sleeping 0.05 seconds + select(undef,undef,undef,0.05); + my ($count_in, $string_in) = $serport->read(4); + #-- control + #my ($i,$j,$k); + #my $ans="receiving:"; + #for($i=0;$i<$count_in;$i++){ + # $j=int(ord(substr($string_in,$i,1))/16); + # $k=ord(substr($string_in,$i,1))%16; + # $ans.="byte $i = 0x$j$k\n"; + #} + #Log 1, $ans; + #-- sleeping 0.05 seconds + select(undef,undef,undef,0.05); + $serport->close(); +} + +######################################################################################## +# +# PT8005_Get - Implements GetFn function +# +# Parameter hash, argument array +# +######################################################################################## + +sub PT8005_Get ($@) { + my ($hash, @a) = @_; + + #-- check syntax + return "PT8005_Get needs exactly one parameter" if(@a != 2); + my $name = $hash->{NAME}; + my $v; + + #-- get present + if($a[1] eq "present") { + $v = ($hash->{READINGS}{"state"}{VAL} =~ m/.*dB.*/) ? 1 : 0; + return "$a[0] present => $v"; + } + + #-- current reading + if($a[1] eq "reading") { + $v = PT8005_GetStatus($hash); + if(!defined($v)) { + Log GetLogLevel($name,2), "PT8005_Get $a[1] error"; + return "$a[0] $a[1] => Error"; + } + $v =~ s/[\r\n]//g; # Delete the NewLine + } else { + return "PT8005_Get with unknown argument $a[1], choose one of " . join(",", sort keys %gets); + } + + Log GetLogLevel($name,3), "PT8005_Get $a[1] $v"; + return "$a[0] $a[1] => $v"; +} + +####################################################################################### +# +# PT8005 - GetStatus - Called in regular intervals to obtain current reading +# +# Parameter hash +# +######################################################################################## + +sub PT8005_GetStatus ($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + my ($bcd,$i,$j,$k); + my ($hour,$min,$sec,$time); + my $data=0.0; + my $nospeed=1; + my $norange=1; + my $nofreq=1; + my $nodata=1; + my $loop=0; + + #-- restart timer for updates + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+ $hash->{INTERVAL}, "PT8005_GetStatus", $hash,1); + #-- check if rec is really off + PT8005_Unrec($hash); + + #-- Obtain the current reading + my $res; + my $dev= $hash->{DeviceName}; + my $serport = new Device::SerialPort ($dev); + + if(!$serport) { + Log GetLogLevel($name,3), "PT8005_Read: Can't open $dev: $!"; + return undef; + } + $serport->reset_error(); + $serport->baudrate(9600); + $serport->databits(8); + $serport->parity('none'); + $serport->stopbits(1); + $serport->handshake('none'); + $serport->write_settings; + + #-- switch into recording mode + my $count_out = $serport->write($SKC{"rec"}); + Log GetLogLevel($name,3), "PT8005_GetStatus: Switch to REC failed" unless ($count_out); + #-- sleeping some time + select(undef,undef,undef,0.15); + + #-- loop for the data + while ( ($nospeed+$norange+$nofreq+$nodata > 0) and ($loop <3) ){ + #my $string_in=PT8005_Read($hash); + select(undef,undef,undef,0.02); + my ($count_in, $string_in) = $serport->read(64); + $loop++; + + #--find data items + if( index($string_in,"\xA5\x02") != -1){ + $nospeed=0; + $speed="fast"; + } elsif( index($string_in,"\xA5\x03") != -1){ + $nospeed=0; + $speed="slow"; + } + + if( index($string_in,"\xA5\x04") != -1){ + $mode="max"; + }elsif( index($string_in,"\xA5\x05") != -1){ + $mode="min"; + }else{ + $mode="normal"; + } + + if( index($string_in,"\xA5\x10") != -1){ + $norange=0; + $range="30-80 dB"; + }elsif( index($string_in,"\xA5\x20") != -1){ + $norange=0; + $range="50-100 dB"; + }elsif( index($string_in,"\xA5\x30") != -1){ + $norange=0; + $range="80-130 dB"; + }elsif( index($string_in,"\xA5\x40") != -1){ + $norange=0; + $range="30-130 dB"; + } + + if( index($string_in,"\xA5\x07") != -1){ + $over="over"; + }elsif( index($string_in,"\xA5\x08") != -1){ + $over="under"; + }else{ + $over=""; + } + + if( index($string_in,"\xA5\x1B") == -1){ + $nofreq=0; + $freq="dB(A)"; + } elsif ( index($string_in,"\xA5\x1C") != -1){ + $nofreq=0; + $freq="dB(C)"; + } + + #-- time not needed + #my $in_time = index($string_in,"\xA5\x06"); + #if( $in_time != -1 ){ + # $bcd=ord(substr($string_in,$in_time+2,1)); + # $hour=int($bcd/16)*10 + $bcd%16 - 20; + # $bcd=ord(substr($string_in,$in_time+3,1)); + # $min = int($bcd/16)*10 + $bcd%16; + # $bcd=ord(substr($string_in,$in_time+4,1)); + # $sec = int($bcd/16)*10 + $bcd%16; + # $time=sprintf("%02d:%02d:%02d",$hour,$min,$sec); + #} else { + # $time="undef"; + # Log GetLogLevel($name,3),"PT8005_GetStatus: no time value obtained" + #} + + #-- data value + my $in_data = index($string_in,"\xA5\x0D"); + if( $in_data != -1){ + $nodata=0; + $bcd=ord(substr($string_in,$in_data+2,1)); + $data=(int($bcd/16)*10 + $bcd%16)*10; + $bcd=ord(substr($string_in,$in_data+3,1)); + $data+=(int($bcd/16)*10 + $bcd%16)*0.1; + } + } + + #-- sleeping some time + select(undef,undef,undef,0.02); + #-- leave recording mode + $count_out = $serport->write($SKC{"rec"}); + #-- sleeping some time + select(undef,undef,undef,0.02); + #-- + $serport->close(); + + #-- could not find a value + if( $nofreq==1 ){ + Log GetLogLevel($name,4), "PT8005_GetStatus: no dBA/C frequency curve value obtained"; + }; + if( $norange==1 ){ + Log GetLogLevel($name,4), "PT8005_GetStatus: no range value obtained"; + }; + if( $nospeed==1 ){ + Log GetLogLevel($name,4), "PT8005_GetStatus: no speed value obtained"; + }; + if( $nodata==1 ){ + Log GetLogLevel($name,4), "PT8005_GetStatus: no data value obtained"; + }; + + #-- addnl. messages + if( $over eq "over"){ + Log GetLogLevel($name,4), "PT8005_GetStatus: Range overflow"; + }elsif( $over eq "under" ){ + Log GetLogLevel($name,4), "PT8005_GetStatus: Range underflow"; + } + + #-- put into readings + + $hash->{READINGS}{"soundlevel"}{UNIT} = $freq; + $hash->{READINGS}{"soundlevel"}{UNITABBR} = $freq; + + my $svalue = sprintf("%3.1f %s",$data,$freq); + + #-- put into READINGS + readingsBeginUpdate($hash); + readingsBulkUpdate($hash,"speed",$speed) + if( $nospeed ==0 ); + readingsBulkUpdate($hash,"mode",$mode); + readingsBulkUpdate($hash,"range",$range) + if( $norange ==0 ); + readingsBulkUpdate($hash,"overflow",$over); + readingsBulkUpdate($hash,"soundlevel",$data) + if( $nodata ==0 ); + #-- STATE + readingsBulkUpdate($hash,"state",$svalue) + if( $nodata ==0 ); + readingsEndUpdate($hash,1); +} + +######################################################################################## +# +# PT8005_Set - Implements SetFn function +# +# Parameter hash, a = argument array +# +######################################################################################## + +sub PT8005_Set ($@) { + my ($hash, @a) = @_; + my $name = shift @a; + my $res; + + #-- for the selector: which values are possible + #return join(" ", sort keys %sets) if(@a != 2); + return "PT8005_Set: With unknown argument $a[0], choose one of " . join(" ", sort keys %sets) + if(!defined($sets{$a[0]})); + + my $dev= $hash->{DeviceName}; + + #-- Set single key value + for (keys %SKC){ + if( $a[0] eq "$_" ){ + Log GetLogLevel($name,1),"PT8005_Set called with arg $_"; + PT8005_Cmd($hash,$SKC{$_}); + } + } + + #-- Set frequency curve to db(A) or db(C) + if( $a[0] eq "freq" ){ + my $freqn = $a[1]; + if ( (!defined($freqn)) || (($freqn ne "dB(A)") && ($freqn ne "dB(C)")) ){ + return "PT8005_Set $name ".join(" ",@a)." with missing parameter, must be dB(A) or dB(C) "; + } + if ( (($freq eq "dB(A)") && ($freqn eq "dB(C)")) || + (($freq eq "dB(C)") && ($freqn eq "dB(A)")) ){ + Log GetLogLevel($name,1),"PT8005_Set freq $freqn"; + $res=PT8005_Cmd($hash,$SKC{"dBA/C"}); + } + } + + #-- Set measurement range to auto + if( $a[0] eq "auto" ){ + if ($range eq "30-80 dB"){ + $res =PT8005_Cmd($hash,$SKC{"range"}); + select(undef,undef,undef,0.05); + $res.=PT8005_Cmd($hash,$SKC{"range"}); + select(undef,undef,undef,0.05); + $res.=PT8005_Cmd($hash,$SKC{"range"}); + }elsif ($range eq "50-100 dB"){ + $res =PT8005_Cmd($hash,$SKC{"range"}); + select(undef,undef,undef,0.05); + $res.=PT8005_Cmd($hash,$SKC{"range"}); + }elsif ($range eq "80-130 dB"){ + $res=PT8005_Cmd($hash,$SKC{"range"}); + } + + Log GetLogLevel($name,1),"PT8005_Set auto"; + } + + Log GetLogLevel($name,3), "PT8005_Set $name ".join(" ",@a)." => $res"; + return "PT8005_Set $name ".join(" ",@a)." => $res"; +} + +######################################################################################## +# +# PT8005_Unrec - switch recording mode off +# +# Parameter hash +# +######################################################################################## + + sub PT8005_Unrec ($) { + my ($hash) = @_; + + my $res; + my $dev= $hash->{DeviceName}; + my $name = $hash->{NAME}; + my $serport = new Device::SerialPort ($dev); + + if(!$serport) { + Log GetLogLevel($name,3), "PT8005_UnRec: Can't open $dev: $!"; + return undef; + } + $serport->reset_error(); + $serport->baudrate(9600); + $serport->databits(8); + $serport->parity('none'); + $serport->stopbits(1); + $serport->handshake('none'); + $serport->write_settings; + + for(my $i = 0; $i < 4; $i++) { + #-- read data and look if it is nonzero + my ($count_in, $string_in) = $serport->read(1); + if( $string_in eq "" ){ + $serport->close(); + Log GetLogLevel($name,4),"PT8005_UnRec: REC is off "; + return 1; + } else { + #-- leave recording mode + select(undef,undef,undef,0.01); + my $count_out = $serport->write($SKC{"rec"}); + #-- sleeping some time + select(undef,undef,undef,0.01); + } + } + $serport->close(); + Log GetLogLevel($name,4),"PT8005_UnRec: REC cannot be turned off "; + + return 0; +} + +1; + + +=pod +=begin html + + +

PT8005

+

FHEM module to commmunicate with a PeakTech PT8005 soundlevel meter
+

+

Example

+

+ define pt8005 PT8005 /dev/ttyUSB0 +


+ +

Define

+

+ define <name> PT8005 <device> +

Define a PT8005 soundlevel meter

+
    +
  • + <name> + Serial device port +
  • +
+ +

Set

+ +
+ +

Get

+ +
+ +

Attributes

+ + +=end html +=cut +