################################################################ # # Copyright notice # # (c) 2009 Copyright: Martin Fischer (m_fischer at gmx dot de) # All rights reserved # # This script 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 Time::HiRes qw(gettimeofday); use OW; my %gets = ( "address" => "", "alias" => "", "crc8" => "", "family" => "10", "id" => "", "locator" => "", "power" => "", "present" => "", # "r_address" => "", # "r_id" => "", # "r_locator" => "", "temperature" => "", "temphigh" => "", "templow" => "", "type" => "", ); my %sets = ( "alias" => "", "temphigh" => "", "templow" => "", "interval" => "", "alarminterval" => "", ); my %updates = ( "present" => "", "temperature" => "", "templow" => "", "temphigh" => "", ); my %dummy = ( "crc8" => "4D", "alias" => "dummy", "locator" => "FFFFFFFFFFFFFFFF", "power" => "0", "present" => "1", "temphigh" => "75", "templow" => "10", "type" => "DS18S20", "warnings" => "none", ); ##################################### sub OWTEMP_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "OWTEMP_Define"; $hash->{UndefFn} = "OWTEMP_Undef"; $hash->{GetFn} = "OWTEMP_Get"; $hash->{SetFn} = "OWTEMP_Set"; $hash->{AttrList}= "IODev do_not_notify:0,1 showtime:0,1 model:DS18S20 loglevel:0,1,2,3,4,5"; } ##################################### sub OWTEMP_UpdateReading($$$$) { my ($hash,$reading,$now,$value) = @_; # define vars my $temp; # exit if empty value return 0 if(!defined($value) || $value eq ""); # trim value $value =~ s/\s//g if($reading ne "warnings"); if($reading eq "temperature") { $value = sprintf("%.4f",$value); $temp = $value; $value = $value . " (".$hash->{OW_SCALE}.")"; } # update readings $hash->{READINGS}{$reading}{TIME} = $now; $hash->{READINGS}{$reading}{VAL} = $value; Log 4, "OWTEMP $hash->{NAME} $reading: $value"; return $value; } ##################################### sub OWTEMP_GetUpdate($$) { my ($hash, $a) = @_; # define vars my $name = $hash->{NAME}; my $now = TimeNow(); my $value = ""; my $temp = ""; my $ret = ""; my $count = 0; # define warnings my $warn = "none"; $hash->{ALARM} = "0"; # check for real sensor if($hash->{OW_ID} ne "none") { # real sensor if(!$hash->{LOCAL} || $a eq "") { ##################### # OW::Get is too slow: do it in the background by fork. After receiving # the data from the OW module, the child contacts the parent, and calls # "set childupdate ", which in turn will call this function # again with a filled CHILDDATA if(!$hash->{CHILDDATA}) { if($hash->{CHILDPID}) { Log 2, "OWTEMP: Child already forked: timeout too short?"; return; } return if(($hash->{CHILDPID} = fork)); my @ret; foreach my $r (sort keys %updates) { my $ret = OW::get("/uncached/".$hash->{OW_PATH}."/".$r); $ret = "" if(!defined($ret)); push(@ret, $ret); last if($ret eq ""); } my @port = split(" ", $attr{global}{port}); my $server = IO::Socket::INET->new(PeerAddr => "localhost:$port[0]"); Log 0, "OWTEMP: Can't connect to parent\n" if(!$server); syswrite($server, "set $hash->{NAME} childupdate ".join(":",@ret)."\n"); exit(0); } else { ##################### # Digest the data sent by the CHILD. my @ret = split(":", $hash->{CHILDDATA}); delete($hash->{CHILDPID}); delete($hash->{CHILDDATA}); foreach my $r (sort keys %updates) { $ret = shift(@ret); if($ret eq "") { # $hash->{PRESENT} = "0"; $r = "present"; $value = "0"; $ret = OWTEMP_UpdateReading($hash,$r,$now,$value); $hash->{CHANGED}[$count] = "present: ".$value } else { $hash->{PRESENT} = "1"; $value = $ret; if($r eq "temperature") { $temp = sprintf("%.4f",$value); $temp =~ s/\s//g; } $ret = OWTEMP_UpdateReading($hash,$r,$now,$value); } last if($hash->{PRESENT} eq "0"); } } } else { $ret = ""; $ret = OW::get("/uncached/".$hash->{OW_PATH}."/".$a); if(!defined($ret)) { $hash->{PRESENT} = "0"; $a = "present"; $value = "0"; $ret = OWTEMP_UpdateReading($hash,$a,$now,$value); } else { $hash->{PRESENT} = "1"; $value = $ret; if($a eq "temperature") { $temp = sprintf("%.4f",$value); $temp =~ s/\s//g; $value = $temp; } $ret = OWTEMP_UpdateReading($hash,$a,$now,$value); } } } else { # dummy sensor $temp = sprintf("%.4f",rand(85)); $dummy{temperature} = $temp; $dummy{present} = "1"; $hash->{PRESENT} = $dummy{present}; if(!$hash->{LOCAL} || $a eq "") { foreach my $r (sort keys %updates) { $ret = OWTEMP_UpdateReading($hash,$r,$now,$dummy{$r}); } } else { $ret = ""; $ret = $dummy{$a}; if($ret ne "") { $value = $ret; if($a eq "temperature") { $temp = sprintf("%.4f",$value); $temp =~ s/\s//g; } $ret = OWTEMP_UpdateReading($hash,$a,$now,$value); } } } return 1 if($hash->{LOCAL} && $a eq "" && $hash->{PRESENT} eq "0"); # check for warnings my $templow = $hash->{READINGS}{templow}{VAL}; my $temphigh = $hash->{READINGS}{temphigh}{VAL}; if($hash->{PRESENT} eq "1") { if($temp <= $templow) { # low temperature $hash->{ALARM} = "1"; $warn = "templow"; } elsif($temp >= $temphigh) { # high temperature $hash->{ALARM} = "1"; $warn = "temphigh"; } } else { # set old state $temp = $hash->{READINGS}{temperature}{VAL}; ($temp,undef) = split(" ",$temp); # sensor is missing $hash->{ALARM} = "1"; $warn = "not present"; } if(!$hash->{LOCAL} || $a eq "") { $ret = OWTEMP_UpdateReading($hash,"warnings",$now,$warn); } $hash->{STATE} = "T: ".$temp." ". "L: ".$templow." ". "H: ".$temphigh." ". "P: ".$hash->{PRESENT}." ". "A: ".$hash->{ALARM}." ". "W: ".$warn; # inform changes # state $hash->{CHANGED}[$count++] = $hash->{STATE}; # present $hash->{CHANGED}[$count++] = "present: ".$hash->{PRESENT} if(defined($hash->{PRESENT}) && $hash->{PRESENT} ne ""); # temperature $hash->{CHANGED}[$count++] = "temperature: ".$temp." (".$hash->{OW_SCALE}.")" if(defined($temp) && $temp ne ""); # temperature raw $hash->{CHANGED}[$count++] = "tempraw: ".$temp if(defined($temp) && $temp ne ""); # low temperature $hash->{CHANGED}[$count++] = "templow: ".$templow if(defined($templow) && $templow ne ""); # high temperature $hash->{CHANGED}[$count++] = "temphigh: ".$temphigh if(defined($temphigh) && $temphigh ne ""); # warnings $hash->{CHANGED}[$count++] = "warnings: ".$warn if(defined($warn) && $warn ne ""); if(!$hash->{LOCAL}) { # update timer RemoveInternalTimer($hash); # check alarm if($hash->{ALARM} eq "0") { $hash->{INTERVAL} = $hash->{INTV_CHECK}; } else { $hash->{INTERVAL} = $hash->{INTV_ALARM}; } InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 1); } else { return $value; } if(!$hash->{LOCAL}) { DoTrigger($name, undef) if($init_done); } return $hash->{STATE}; } ##################################### sub OWTEMP_Get($@) { my ($hash, @a) = @_; # check syntax return "argument is missing @a" if(int(@a) != 2); # check argument return "Unknown argument $a[1], choose one of ".join(",", sort keys %gets) if(!defined($gets{$a[1]})); # define vars my $value; # get value $hash->{LOCAL} = 1; $value = OWTEMP_GetUpdate($hash,$a[1]); delete $hash->{LOCAL}; my $reading = $a[1]; if(defined($hash->{READINGS}{$reading})) { $value = $hash->{READINGS}{$reading}{VAL}; } return "$a[0] $reading => $value"; } ##################################### sub OWTEMP_Set($@) { my ($hash, @a) = @_; # check syntax return "set needs one parameter" if(int(@a) != 3); # check arguments return "Unknown argument $a[1], choose one of ".join(",", sort keys %sets) if(!defined($sets{$a[1]}) && $a[1] ne "childupdate"); # define vars my $key = $a[1]; my $value = $a[2]; my $ret; if($key eq "childupdate") { $hash->{CHILDDATA} = $value; OWTEMP_GetUpdate($hash,undef); return undef; } # set new timer if($key eq "interval" || $key eq "alarminterval") { $key = "INTV_CHECK" if($key eq "interval"); $key = "INTV_ALARM" if($key eq "alarminterval"); # update timer $hash->{$key} = $value; RemoveInternalTimer($hash); # check alarm if($hash->{ALARM} eq "0") { $hash->{INTERVAL} = $hash->{INTV_CHECK}; } else { $hash->{INTERVAL} = $hash->{INTV_ALARM}; } InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 1); } # set warnings if($key eq "templow" || $key eq "temphigh") { # check range return "wrong value: range -55°C - 125°C" if(int($value) < -55 || int($value) > 125); } # set value Log 4, "OWTEMP set $hash->{NAME} $key $value"; # check for real sensor if($hash->{OW_ID} ne "none") { # real senson $ret = OW::put($hash->{OW_PATH}."/$key",$value); } else { # dummy sensor $dummy{$key} = $value; } # update readings if($key ne "interval" || $key ne "alarminterval") { $hash->{LOCAL} = 1; $ret = OWTEMP_GetUpdate($hash,$key); delete $hash->{LOCAL}; } return undef; } ##################################### sub OWTEMP_Define($$) { my ($hash, $def) = @_; # define OWTEMP [interval] [alarminterval] # e.g.: define flow OWTEMP 332670010800 300 my @a = split("[ \t][ \t]*", $def); # check syntax return "wrong syntax: define OWTEMP [interval] [alarminterval]" if(int(@a) < 2 && int(@a) > 5); # check ID format return "Define $a[0]: missing ID or wrong ID format: specify a 12 digit value or set it to none for demo mode" if(lc($a[2]) ne "none" && lc($a[2]) !~ m/^[0-9|a-f]{12}$/); # define vars my $name = $a[0]; my $id = $a[2]; my $interval = 300; my $alarminterval = 300; my $scale = ""; my $ret = ""; # overwrite default intervals if set by define if(int(@a)==4) { $interval = $a[3]; } if(int(@a)==5) { $interval = $a[3]; $alarminterval = $a[4] } # define device internals $hash->{ALARM} = 0; $hash->{INTERVAL} = $interval; $hash->{INTV_CHECK} = $interval; $hash->{INTV_ALARM} = $alarminterval; $hash->{OW_ID} = $id; $hash->{OW_FAMILY} = $gets{family}; $hash->{OW_PATH} = $hash->{OW_FAMILY}.".".$hash->{OW_ID}; $hash->{PRESENT} = 0; $modules{OWTEMP}{defptr}{$a[2]} = $hash; # assign IO port AssignIoPort($hash); return "No I/O device found. Please define a OWFS device first." if(!defined($hash->{IODev}->{NAME})); # get scale from I/O device $scale = $attr{$hash->{IODev}->{NAME}}{"temp-scale"}; # define scale for temperature values $scale = "Celsius" if ($scale eq "C"); $scale = "Fahrenheit" if ($scale eq "F"); $scale = "Kelvin" if ($scale eq "K"); $scale = "Rankine" if ($scale eq "R"); $hash->{OW_SCALE} = $scale; $hash->{STATE} = "Defined"; # define dummy values for testing if($hash->{OW_ID} eq "none") { my $now = TimeNow(); $dummy{address} = $hash->{OW_FAMILY}.$hash->{OW_ID}.$dummy{crc8}; $dummy{family} = $hash->{OW_FAMILY}; $dummy{id} = $hash->{OW_ID}; $dummy{temperature} = "80.0000 (".$hash->{OW_SCALE}.")"; foreach my $r (sort keys %gets) { $hash->{READINGS}{$r}{TIME} = $now; $hash->{READINGS}{$r}{VAL} = $dummy{$r}; Log 4, "OWTEMP $hash->{NAME} $r: ".$dummy{$r}; } } $hash->{STATE} = "Initialized"; # initalize $hash->{LOCAL} = 1; $ret = OWTEMP_GetUpdate($hash,""); delete $hash->{LOCAL}; # exit if sensor is not present return "Define $hash->{NAME}: Sensor is not reachable. Check first your 1-wire connection." if(defined($ret) && $ret eq 1); if(!$hash->{LOCAL}) { if($hash->{ALARM} eq "0") { $hash->{INTERVAL} = $hash->{INTV_CHECK}; } else { $hash->{INTERVAL} = $hash->{INTV_ALARM}; } InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 0); } return undef; } ##################################### sub OWTEMP_Undef($$) { my ($hash, $name) = @_; delete($modules{OWTEMP}{defptr}{$hash->{NAME}}); RemoveInternalTimer($hash); return undef; } 1;