From f9a8e57936dba39f19e771543ea7ada8940e5934 Mon Sep 17 00:00:00 2001 From: pahenning <> Date: Tue, 21 Feb 2012 09:56:28 +0000 Subject: [PATCH] =?UTF-8?q?1-Wire=20Module=20f=C3=BCr=20direkte=20Benutzun?= =?UTF-8?q?g=20des=201-Wire=20Bus=20(incl.=20FritzBox).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit git-svn-id: https://svn.fhem.de/fhem/trunk@1272 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/contrib/1-Wire/00_OWX.pm | 1459 +++++++++++++++++ fhem/contrib/1-Wire/21_OWTEMP.pm | 751 +++++++++ .../1-Wire/Schaltplan_1-Wire_Interface.png | Bin 0 -> 41324 bytes 3 files changed, 2210 insertions(+) create mode 100644 fhem/contrib/1-Wire/00_OWX.pm create mode 100644 fhem/contrib/1-Wire/21_OWTEMP.pm create mode 100644 fhem/contrib/1-Wire/Schaltplan_1-Wire_Interface.png diff --git a/fhem/contrib/1-Wire/00_OWX.pm b/fhem/contrib/1-Wire/00_OWX.pm new file mode 100644 index 000000000..c9aba2842 --- /dev/null +++ b/fhem/contrib/1-Wire/00_OWX.pm @@ -0,0 +1,1459 @@ +######################################################################################## +# +# OWX.pm +# +# FHEM module to commmunicate directly with 1-Wire bus devices +# via an active DS2480/DS2490/DS9097U bus master interface or +# via a passive DS9097 interface +# +# Version 1.0 - February 21, 2012 +# +# Prof. Dr. Peter A. Henning, 2011 +# +# Setup bus master as: +# define OWX +# +# where may be replaced by any name string +# is a serial (USB) device +# +# get alarms => find alarmed 1-Wire devices +# get devices => find all 1-Wire devices +# +# set interval => set period for temperature conversion and alarm testing +# +# Ordering of subroutines in this module +# 1. Subroutines independent of bus interface type +# 2. Subroutines for a specific type of the interface +# +######################################################################################## +# +# 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($$); + +# Line counter +my $cline=0; + +# These we may get on request +my %gets = ( + "alarms" => "A", + "devices" => "D" +); + +# These occur in a pulldown menu as settable values for the bus master +my %sets = ( + "interval" => "T" +); + +# These are attributes +my %attrs = ( +); + +#-- some globals needed for the 1-Wire module +#-- baud rate serial interface +my $owx_baud=9600; +#-- Debugging +my $owx_debug=0; +#-- bus master mode +my $owx_mode="undef"; +#-- bus interface +my $owx_interface=""; +#-- 8 byte 1-Wire device address +my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); +#-- List of addresses found on the bus +my @owx_devs=(); +my @owx_fams=(); +my @owx_alarm_devs=(); +#-- 16 byte search string +my @owx_search=(0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0); +#-- search state for 1-Wire bus search +my $owx_LastDiscrepancy = 0; +my $owx_LastFamilyDiscrepancy = 0; +my $owx_LastDeviceFlag = 0; + + +######################################################################################## +# +# The following subroutines in alphabetical order are independent of the bus interface +# +######################################################################################## +# +# OWX_Alarms - Find devices on the 1-Wire bus, +# which have the alarm flag set +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : no device present +# +######################################################################################## + +sub OWX_Alarms ($) { + my ($hash) = @_; + my @owx_alarm_names=(); + + #-- Discover all alarmed devices on the 1-Wire bus + @owx_alarm_devs=(); + my $res = OWX_First($hash,"alarm"); + while( $owx_LastDeviceFlag==0 && $res != 0){ + $res = $res & OWX_Next($hash,"alarm"); + } + if( @owx_alarm_devs == 0){ + return "OWX: No alarmed 1-Wire devices found "; + } + + #-- walk through all the devices to get their proper fhem names + foreach my $fhem_dev (sort keys %main::defs) { + + #-- all OW types start with OW + next if( substr($main::defs{$fhem_dev}{TYPE},0,2) ne "OW"); + next if($main::defs{$fhem_dev}{ROM_ID} eq "FF"); + foreach my $owx_dev (@owx_alarm_devs) { + #-- two pieces of the ROM ID found on the bus + my $owx_rnf = substr($owx_dev,3,12); + my $owx_f = substr($owx_dev,0,2); + my $id_owx = $owx_f.".".$owx_rnf; + + #-- skip if not in alarm list + if( $owx_dev eq $main::defs{$fhem_dev}{ROM_ID} ){ + $main::defs{$fhem_dev}{STATE} = "Alarmed"; + push(@owx_alarm_names,$main::defs{$fhem_dev}{NAME}); + } + } + } + #-- so far, so good - what do we want to do with this ? + return "OWX: Alarmed 1-Wire devices found (".join(",",@owx_alarm_names).")"; +} + +######################################################################################## +# +# OWX_Block - Send data block +# +# Parameter hash = hash of bus master, data = string to send +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub OWX_Block ($$) { + my ($hash,$data) =@_; + + if( $owx_interface eq "DS2480" ){ + return OWX_Block_2480($hash,$data); + }elsif( $owx_interface eq "DS9097" ){ + return OWX_Block_9097($hash,$data); + }else{ + Log 1,"OWX: Block called with unknown interface"; + return 0; + } +} + +######################################################################################## +# +# OWX_Convert - Initiate temperature conversion in all devices +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : Not OK +# +######################################################################################## + +sub OWX_Convert($) { + + my($hash) = @_; + + #-- Call us in n seconds again. + InternalTimer(gettimeofday()+ $hash->{interval}, "OWX_Convert", $hash,1); + + OWX_Reset($hash); + + #-- issue the skip ROM command \xCC followed by start conversion command \x44 + if( OWX_Block($hash,"\xCC\x44") eq 0) { + Log 3, "OWX: Failure in temperature conversion\n"; + return 0; + } + return 1; +} + +######################################################################################## +# +# OWX_CRC - Check the CRC8 code of a device address in @owx_ROM_ID +# +######################################################################################## + +sub OWX_CRC () { + my @crc8_table = ( + 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65, + 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220, + 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98, + 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255, + 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7, + 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154, + 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36, + 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185, + 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205, + 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80, + 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238, + 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115, + 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139, + 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22, + 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168, + 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53); + + my $crc8=0; + + for(my $i=0; $i<8; $i++){ + $crc8 = $crc8_table[ $crc8 ^ $owx_ROM_ID[$i] ]; + } + return $crc8; +} + + +######################################################################################## +# +# OWX_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################## + +sub OWX_Define ($$) { + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + if(@a == 3){ + #-- If this line contains 3 parameters, it is the bus master definition + my $dev = $a[2]; + $hash->{DeviceName} = $dev; + #-- Dummy 1-Wire ROM identifier + $hash->{ROM_ID} = "FF"; + + #-- First step: open the serial device to test it + #Log 3, "OWX opening device $dev"; + my $owx_serport = new Device::SerialPort ($dev); + return "OWX: Can't open $dev: $!" if(!$owx_serport); + Log 3, "OWX: opened device $dev"; + $owx_serport->reset_error(); + $owx_serport->baudrate(9600) || die "failed setting baudrate"; + $owx_serport->databits(8) || die "failed setting databits"; + $owx_serport->parity('none') || die "failed setting parity"; + $owx_serport->stopbits(1) || die "failed setting stopbits"; + $owx_serport->handshake('none') || die "failed setting handshake"; + $owx_serport->write_settings || die "no settings"; + sleep(1); + $owx_serport->close(); + + #-- Second step: see, if a bus interface is detected + if (!OWX_Detect($hash)){ + $hash->{STATE} = "Failed"; + $init_done = 1; + return undef; + } + + #-- default temperature-scale: C + # C: Celsius, F: Fahrenheit, K: Kelvin, R: Rankine + $attr{$hash->{NAME}}{"temp-scale"} = "C"; + + #-- In 10 seconds discover all devices on the 1-Wire bus + InternalTimer(gettimeofday()+5, "OWX_Discover", $hash,0); + $hash->{interval} = 60; # call every minute + + #-- InternalTimer blocks if init_done is not true + my $oid = $init_done; + $hash->{STATE} = "Initialized"; + $hash->{INTERFACE} = $owx_interface; + $init_done = 1; + + #-- Intiate the first temp conversion only after the proper subroutines have been defined + InternalTimer(gettimeofday() + 5, "OWX_Convert", $hash,1); + $init_done = $oid; + $hash->{STATE} = "Active"; + return undef; + } +} + +######################################################################################## +# +# OWX_Detect - Detect 1-Wire interface +# +# Method rather crude - treated as an 2480, and see whatis returned +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_Detect ($) { + my ($hash) = @_; + + my ($i,$j,$k); + #-- timing byte for DS2480 + OWX_Query_2480($hash,"\xC1"); + #-- write 1-Wire bus (Fig. 2 of Maxim AN192) + my $res = OWX_Query_2480($hash,"\x17\x45\x5B\x0F\x91"); + + #-- process 4/5-byte string for detection + if( $res eq "\x16\x44\x5A\x00\x93"){ + Log 1, "OWX: 1-Wire bus master DS2480 detected for the first time"; + $owx_interface="DS2480"; + return 1; + } elsif( $res eq "\x17\x45\x5B\x0F\x91"){ + Log 1, "OWX: 1-Wire bus master DS2480 re-detected"; + $owx_interface="DS2480"; + return 1; + } elsif( ($res eq "\x17\x0A\x5B\x0F\x02") || ($res eq "\x00\x17\x0A\x5B\x0F\x02") ){ + Log 1, "OWX: Passive 1-Wire bus interface DS9097 detected"; + $owx_interface="DS9097"; + return 1; + } else { + my $ress = "OWX: No 1-Wire bus interface detected, answer was "; + for($i=0;$i{NAME}"); + CommandAttr (undef,"$name room OWX"); + push(@owx_names,$name); + } else { + Log 1, "OWX: Undefined device family $owx_f"; + } + #-- replace the ROM ID by the proper value + $main::defs{$name}{ROM_ID}=$owx_dev; + } + } + Log 1, "OWX: 1-Wire devices found (".join(",",@owx_names).")"; + return "OWX: 1-Wire devices found (".join(",",@owx_names).")"; +} + +######################################################################################## +# +# OWX_First - Find the 'first' devices on the 1-Wire bus +# +# Parameter hash = hash of bus master, mode +# +# Return 1 : device found, ROM number pushed to list +# 0 : no device present +# +######################################################################################## + +sub OWX_First ($$) { + my ($hash,$mode) = @_; + + #-- clear 16 byte of search data + @owx_search=(0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0); + #-- reset the search state + $owx_LastDiscrepancy = 0; + $owx_LastDeviceFlag = 0; + $owx_LastFamilyDiscrepancy = 0; + #-- now do the search + return OWX_Search($hash,$mode); +} + +######################################################################################## +# +# OWX_Get - Implements GetFn function +# +# Parameter hash = hash of the bus master a = argument array +# +######################################################################################## + +sub OWX_Get($@) { + my ($hash, @a) = @_; + return "OWX: Get needs exactly one parameter" if(@a != 2); + + my $name = $hash->{NAME}; + my $owx_dev = $hash->{ROM_ID}; + + if( $a[1] eq "alarms") { + my $res = OWX_Alarms($hash); + #-- process result + return $res + + } elsif( $a[1] eq "devices") { + my $res = OWX_Discover($hash); + #-- process result + return $res + + } else { + return "OWX: Get with unknown argument $a[1], choose one of ". + join(",", sort keys %gets); + } +} + +######################################################################################### +# +# OWX_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWX_Initialize ($) { + my ($hash) = @_; + #-- Provider + #$hash->{Clients} = ":OWCOUNT:OWHUB:OWLCD:OWMULTI:OWSWITCH:OWTEMP:"; + $hash->{Clients} = ":OWTEMP:"; + + #-- Normal Devices + $hash->{DefFn} = "OWX_Define"; + $hash->{UndefFn} = "OWX_Undef"; + $hash->{GetFn} = "OWX_Get"; + $hash->{SetFn} = "OWX_Set"; + $hash->{AttrList}= "temp-scale:C,F,K,R loglevel:0,1,2,3,4,5,6"; +} + +######################################################################################## +# +# OWX_Next - Find the 'next' devices on the 1-Wire bus +# +# Parameter hash = hash of bus master, mode +# +# Return 1 : device found, ROM number in owx_ROM_ID and pushed to list (LastDeviceFlag=0) +# or only in owx_ROM_ID (LastDeviceFlag=1) +# 0 : device not found, or ot searched at all +# +######################################################################################## + +sub OWX_Next ($$) { + my ($hash,$mode) = @_; + #-- now do the search + return OWX_Search($hash,$mode); +} + +######################################################################################## +# +# OWX_Reset - Reset the 1-Wire bus +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_Reset ($) { + + my ($hash)=@_; + if( $owx_interface eq "DS2480" ){ + return OWX_Reset_2480($hash); + }elsif( $owx_interface eq "DS9097" ){ + return OWX_Reset_9097($hash); + }else{ + Log 1,"OWX: Reset called with unknown interface"; + return 0; + } +} + +######################################################################################## +# +# OWX_Search - Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +# search state. +# +# Parameter hash = hash of bus master, mode=alarm,discover or verify +# +# Return 1 : device found, ROM number in owx_ROM_ID and pushed to list (LastDeviceFlag=0) +# or only in owx_ROM_ID (LastDeviceFlag=1) +# 0 : device not found, or ot searched at all +# +######################################################################################## + +sub OWX_Search ($$) { + my ($hash,$mode)=@_; + + #-- if the last call was the last one, no search + if ($owx_LastDeviceFlag==1){ + return 0; + } + #-- 1-Wire reset + if (OWX_Reset($hash)==0){ + #-- reset the search + Log 1, "OWX: Search reset failed"; + $owx_LastDiscrepancy = 0; + $owx_LastDeviceFlag = 0; + $owx_LastFamilyDiscrepancy = 0; + return 0; + } + #print "Reset ok when starting search \n"; + + #-- Here we call the device dependent part + if( $owx_interface eq "DS2480" ){ + OWX_Search_2480($hash,$mode); + }elsif( $owx_interface eq "DS9097" ){ + OWX_Search_9097($hash,$mode); + }else{ + Log 1,"OWX: Search called with unknown interface"; + return 0; + } + + #--check if we really found a device + if( OWX_CRC()!= 0){ + #-- reset the search + Log 1, "OWX: Search CRC failed "; + $owx_LastDiscrepancy = 0; + $owx_LastDeviceFlag = 0; + $owx_LastFamilyDiscrepancy = 0; + return 0; + } + + #-- character version of device ROM_ID, first byte = family + my $dev=sprintf("%02X.%02X%02X%02X%02X%02X%02X.%02X",@owx_ROM_ID); + + #-- for some reason this does not work - replaced by another test, see below + #if( $owx_LastDiscrepancy==0 ){ + # $owx_LastDeviceFlag=1; + #} + #-- + if( $owx_LastDiscrepancy==$owx_LastFamilyDiscrepancy ){ + $owx_LastFamilyDiscrepancy=0; + } + + #-- mode was to verify presence of a device + if ($mode eq "verify") { + Log 5, "OWX: Device verified $dev"; + return 1; + #-- mode was to discover devices + } elsif( $mode eq "discover" ){ + #-- check families + my $famfnd=0; + foreach (@owx_fams){ + if( substr($dev,0,2) eq $_ ){ + #-- if present, set the fam found flag + $famfnd=1; + last; + } + } + push(@owx_fams,substr($dev,0,2)) if( !$famfnd ); + foreach (@owx_devs){ + if( $dev eq $_ ){ + #-- if present, set the last device found flag + $owx_LastDeviceFlag=1; + last; + } + } + if( $owx_LastDeviceFlag!=1 ){ + #-- push to list + push(@owx_devs,$dev); + Log 5, "OWX: New device found $dev"; + } + return 1; + #-- mode was to discover alarm devices + } else { + for(my $i=0;$i<@owx_alarm_devs;$i++){ + if( $dev eq $owx_alarm_devs[$i] ){ + #-- if present, set the last device found flag + $owx_LastDeviceFlag=1; + last; + } + } + if( $owx_LastDeviceFlag!=1 ){ + #--push to list + push(@owx_alarm_devs,$dev); + Log 5, "OWX: New alarm device found $dev"; + } + return 1; + } +} + +######################################################################################## +# +# OWX_Set - Implements SetFn function +# +# Parameter hash , a = argument array +# +######################################################################################## + +sub OWX_Set($@) { + my ($hash, @a) = @_; + my $name = shift @a; + my $res; + + #-- First we need to find the ROM ID corresponding to the device name + my $owx_romid = $hash->{ROM_ID}; + Log 5, "OWX_Set request $name $owx_romid ".join(" ",@a); + + #-- for the selector: which values are possible + return join(" ", sort keys %sets) if(@a != 2); + return "OWX_Set: With unknown argument $a[0], choose one of " . join(" ", sort keys %sets) + if(!defined($sets{$a[0]})); + + #-- Set timer value + if( $a[0] eq "interval" ){ + #-- only values >= 15 secs allowed + if( $a[1] >= 15){ + $hash->{interval} = $a[1]; + $res = 1; + } else { + $res = 0; + } + Log GetLogLevel($name,3), "OWX_Set $name ".join(" ",@a)." => $res"; + DoTrigger($name, undef) if($init_done); + return "OWX_Set => $name ".join(" ",@a)." => $res"; + } +} + +######################################################################################## +# +# OWX_Undef - Implements UndefFn function +# +# Parameter hash = hash of the bus master, name +# +######################################################################################## + +sub OWX_Undef ($$) { + my ($hash, $name) = @_; + delete($modules{OWX}{defptr}{$hash->{CODE}}); + return undef; +} + +######################################################################################## +# +# OBSOLETE subroutine OWX_Values - stub function for OWX_yy +# Necessary, because at time of first scheduling the OWX_yy subs are not yet knwon +# +# Parameter hash = hash of device addressed +# +######################################################################################## + + sub OWX_Values ($) { + my ($hash) = @_; + + my $fam; + foreach $fam (@owx_fams) { + } + my $res=OWXTEMP_Values($hash); + return $res; +} + +######################################################################################## +# +# OWX_Verify - Verify a particular device on the 1-Wire bus +# +# Parameter hash = hash of bus master, dev = 8 Byte ROM ID of device to be tested +# +# Return 1 : device found +# 0 : device not +# +######################################################################################## + +sub OWX_Verify ($$) { + my ($hash,$dev) = @_; + my $i; + #-- from search string to byte id + my $devs=$dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + #-- reset the search state + $owx_LastDiscrepancy = 64; + $owx_LastDeviceFlag = 0; + #-- now do the search + my $res=OWX_Search($hash,"verify"); + my $dev2=sprintf("%02X.%02X%02X%02X%02X%02X%02X.%02X",@owx_ROM_ID); + #-- reset the search state + $owx_LastDiscrepancy = 0; + $owx_LastDeviceFlag = 0; + #-- check result + if ($dev eq $dev2){ + return 1; + }else{ + # print "Searching for $dev, but found $dev2\n"; + return 0; + } +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a DS2480 bus interface +# +######################################################################################### +# +# OWX_Block_2480 - Send data block (Fig. 6 of Maxim AN192) +# +# Parameter hash = hash of bus master, data = string to send +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub OWX_Block_2480 ($$) { + my ($hash,$data) =@_; + + my $data2=""; + #-- if necessary, prepend E1 character for data mode + if( ($owx_mode ne "data") && (substr($data,0,1) ne '\xE1')) { + $data2 = "\xE1"; + } + #-- all E3 characters have to be duplicated + for(my $i=0;$i{DeviceName}; + + if( $dev ne "emulator") { + #Log 3, "OWX opening device $dev"; + my $owx_serport = new Device::SerialPort ($dev); + return "OWX: Can't open $dev: $!" if(!$owx_serport); + Log 4, "OWX: Opened device $dev"; + + $owx_serport->reset_error(); + $owx_serport->baudrate($owx_baud) || die "failed setting baudrate"; + $owx_serport->databits(8) || die "failed setting databits"; + $owx_serport->parity('none') || die "failed setting parity"; + $owx_serport->stopbits(1) || die "failed setting stopbits"; + $owx_serport->handshake('none') || die "failed setting handshake"; + $owx_serport->write_settings || die "no settings"; + + if( $owx_debug > 1){ + my $res = "OWX: Sending out "; + for($i=0;$iwrite($cmd); + + Log 1, "OWX: Write incomplete $count_out ne ".(length($cmd))."" if ( $count_out != length($cmd) ); + #-- sleeping 0.03 seconds + select(undef,undef,undef,0.04); + + #-- read the data + my ($count_in, $string_in) = $owx_serport->read(32); + + if( $owx_debug > 1){ + my $res = "OWX: 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; + $res.=sprintf "0x%1x%1x ",$j,$k; + } + Log 3, $res; + } + + #-- sleeping 0.03 seconds + select(undef,undef,undef,0.04); + + $owx_serport->close(); + return($string_in); + } +} + +######################################################################################## +# +# OWX_Reset_2480 - Reset the 1-Wire bus (Fig. 4 of Maxim AN192) +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_Reset_2480 ($) { + + my ($hash)=@_; + my $cmd=""; + + #-- if necessary, prepend \xE3 character for command mode + if( $owx_mode ne "command" ) { + $cmd = "\xE3"; + } + #-- Reset command \xC5 + $cmd = $cmd."\xC5"; + #-- write 1-Wire bus + my $res =OWX_Query_2480($hash,$cmd); + #-- process result + my $r1 = ord(substr($res,0,1)) & 192; + if( $r1 != 192){ + Log 3, "OWX: Reset failure"; + return 0; + } + + my $r2 = ord(substr($res,0,1)) & 3; + + if( $r2 == 3 ){ + Log 3, "OWX: No presence detected"; + return 0; + }elsif( $r2 ==2 ){ + Log 1, "OWX: Alarm presence detected"; + } + return 1; +} + +######################################################################################## +# +# OWX_Search_2480 - Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +# search state. +# +# Parameter hash = hash of bus master, mode=alarm,discover or verify +# +# Return 1 : device found, ROM number in owx_ROM_ID and pushed to list (LastDeviceFlag=0) +# or only in owx_ROM_ID (LastDeviceFlag=1) +# 0 : device not found, or ot searched at all +# +######################################################################################## + +sub OWX_Search_2480 ($$) { + my ($hash,$mode)=@_; + + my ($sp1,$sp2,$response,$search_direction,$id_bit_number); + + #-- Response search data parsing operates bytewise + $id_bit_number = 1; + + select(undef,undef,undef,0.5); + + #-- clear 16 byte of search data + @owx_search=(0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0); + #-- Output search data construction (Fig. 9 of Maxim AN192) + # operates on a 16 byte search response = 64 pairs of two bits + while ( $id_bit_number <= 64) { + #-- address single bits in a 16 byte search string + my $newcpos = int(($id_bit_number-1)/4); + my $newimsk = ($id_bit_number-1)%4; + #-- address single bits in a 8 byte id string + my $newcpos2 = int(($id_bit_number-1)/8); + my $newimsk2 = ($id_bit_number-1)%8; + + if( $id_bit_number <= $owx_LastDiscrepancy){ + #-- first use the ROM ID bit to set the search direction + if( $id_bit_number < $owx_LastDiscrepancy ) { + $search_direction = ($owx_ROM_ID[$newcpos2]>>$newimsk2) & 1; + #-- at the last discrepancy search into 1 direction anyhow + } else { + $search_direction = 1; + } + #-- fill into search data; + $owx_search[$newcpos]+=$search_direction<<(2*$newimsk+1); + } + #--increment number + $id_bit_number++; + } + #-- issue data mode \xE1, the normal search command \xF0 or the alarm search command \xEC + # and the command mode \xE3 / start accelerator \xB5 + if( $mode ne "alarm" ){ + $sp1 = "\xE1\xF0\xE3\xB5"; + } else { + $sp1 = "\xE1\xEC\xE3\xB5"; + } + #-- issue data mode \xE1, device ID, command mode \xE3 / end accelerator \xA5 + $sp2=sprintf("\xE1%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\xE3\xA5",@owx_search); + $response = OWX_Query_2480($hash,$sp1); + $response = OWX_Query_2480($hash,$sp2); + + #-- interpret the return data + if( length($response)!=16 ) { + Log 3, "OWX: Search 2nd return has wrong parameter with length = ".length($response).""; + return 0; + } + #-- Response search data parsing (Fig. 11 of Maxim AN192) + # operates on a 16 byte search response = 64 pairs of two bits + $id_bit_number = 1; + #-- clear 8 byte of device id for current search + @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + + while ( $id_bit_number <= 64) { + #-- adress single bits in a 16 byte string + my $newcpos = int(($id_bit_number-1)/4); + my $newimsk = ($id_bit_number-1)%4; + + #-- retrieve the new ROM_ID bit + my $newchar = substr($response,$newcpos,1); + + #-- these are the new bits + my $newibit = (( ord($newchar) >> (2*$newimsk) ) & 2) / 2; + my $newdbit = ( ord($newchar) >> (2*$newimsk) ) & 1; + + #-- output for test purpose + #print "id_bit_number=$id_bit_number => newcpos=$newcpos, newchar=0x".int(ord($newchar)/16). + # ".".int(ord($newchar)%16)." r$id_bit_number=$newibit d$id_bit_number=$newdbit\n"; + + #-- discrepancy=1 and ROM_ID=0 + if( ($newdbit==1) and ($newibit==0) ){ + $owx_LastDiscrepancy=$id_bit_number; + if( $id_bit_number < 9 ){ + $owx_LastFamilyDiscrepancy=$id_bit_number; + } + } + #-- fill into device data; one char per 8 bits + $owx_ROM_ID[int(($id_bit_number-1)/8)]+=$newibit<<(($id_bit_number-1)%8); + + #-- increment number + $id_bit_number++; + } + return 1; +} + +######################################################################################## +# +# OWX_WriteBytePower_2480 - Send byte to bus with power increase (Fig. 16 of Maxim AN192) +# +# Parameter hash = hash of bus master, dbyte = byte to send +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_WriteBytePower_2480 ($$) { + + my ($hash,$dbyte) =@_; + my $cmd="\x3F"; + my $ret="\x3E"; + #-- if necessary, prepend \xE3 character for command mode + if( $owx_mode ne "command") { + $cmd = "\xE3".$cmd; + } + #-- distribute the bits of data byte over several command bytes + for (my $i=0;$i<8;$i++){ + my $newbit = (ord($dbyte) >> $i) & 1; + my $newchar = 133 | ($newbit << 4); + my $newchar2 = 132 | ($newbit << 4) | ($newbit << 1) | $newbit; + #-- last command byte still different + if( $i == 7){ + $newchar = $newchar | 2; + } + $cmd = $cmd.chr($newchar); + $ret = $ret.chr($newchar2); + } + #-- write 1-Wire bus + my $res = OWX_Query($hash,$cmd); + #-- process result + if( $res eq $ret ){ + Log 5, "OWX: WriteBytePower OK"; + return 1; + } else { + Log 3, "OWX: WriteBytePower failure"; + return 0; + } +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a DS9097 bus interface +# +######################################################################################## +# +# OWX_Block_9097 - Send data block ( +# +# Parameter hash = hash of bus master, data = string to send +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub OWX_Block_9097 ($$) { + my ($hash,$data) =@_; + + my $data2=""; + for (my $i=0; $i{DeviceName}; + + if( $dev ne "emulator") { + #Log 3, "OWX opening device $dev"; + my $owx_serport = new Device::SerialPort ($dev); + return "OWX: Can't open $dev: $!" if(!$owx_serport); + Log 4, "OWX: Opened device $dev"; + + #$owx_serport->reset_error(); + $owx_serport->baudrate($owx_baud) || die "failed setting baudrate"; + $owx_serport->databits(8) || die "failed setting databits"; + $owx_serport->parity('none') || die "failed setting parity"; + $owx_serport->stopbits(1) || die "failed setting stopbits"; + $owx_serport->handshake('none') || die "failed setting handshake"; + $owx_serport->write_settings || die "no settings"; + + if( $owx_debug > 1){ + my $res = "OWX: Sending out "; + for($i=0;$iwrite($cmd); + + Log 1, "OWX: Write incomplete $count_out ne ".(length($cmd))."" if ( $count_out != length($cmd) ); + #-- sleeping 0.03 seconds + #select(undef,undef,undef,0.03); + #select(undef,undef,undef,0.05); + + #-- read the data + my ($count_in, $string_in) = $owx_serport->read(32); + + if( $owx_debug > 1){ + my $res = "OWX: 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; + $res.=sprintf "0x%1x%1x ",$j,$k; + } + Log 3, $res; + } + + #-- sleeping 0.03 seconds + #select(undef,undef,undef,0.03); + select(undef,undef,undef,0.05); + + $owx_serport->close(); + return($string_in); + } +} + +######################################################################################## +# +# OWX_ReadBit_9097 - Read 1 bit from 1-wire bus (Fig. 5/6 from Maxim AN214) +# +# Parameter hash = hash of bus master +# +# Return bit value +# +######################################################################################## + +sub OWX_ReadBit_9097 ($) { + my ($hash) = @_; + + #-- set baud rate to 115200 and query!!! + my $sp1="\xFF"; + $owx_baud=115200; + my $res=OWX_Query_9097($hash,$sp1); + $owx_baud=9600; + #-- process result + if( substr($res,0,1) eq "\xFF" ){ + return 1; + } else { + return 0; + } +} + +######################################################################################## +# +# OWX_Reset_9097 - Reset the 1-Wire bus (Fig. 4 of Maxim AN192) +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_Reset_9097 ($) { + + my ($hash)=@_; + my $cmd=""; + + #-- Reset command \xF0 + $cmd="\xF0"; + #-- write 1-Wire bus + my $res =OWX_Query_9097($hash,$cmd); + #-- TODO: process result + #-- may vary between 0x10, 0x90, 0xe0 + return 1; +} + +######################################################################################## +# +# OWX_Search_9097 - Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +# search state. +# +# Parameter hash = hash of bus master, mode=alarm,discover or verify +# +# Return 1 : device found, ROM number in owx_ROM_ID and pushed to list (LastDeviceFlag=0) +# or only in owx_ROM_ID (LastDeviceFlag=1) +# 0 : device not found, or ot searched at all +# +######################################################################################## + +sub OWX_Search_9097 ($$) { + + my ($hash,$mode)=@_; + + my ($sp1,$sp2,$response,$search_direction,$id_bit_number); + + #-- Response search data parsing operates bitwise + $id_bit_number = 1; + my $rom_byte_number = 0; + my $rom_byte_mask = 1; + my $last_zero = 0; + + #-- issue search command + $owx_baud=115200; + $sp2="\x00\x00\x00\x00\xFF\xFF\xFF\xFF"; + $response = OWX_Query_9097($hash,$sp2); + $owx_baud=9600; + #-- issue the normal search command \xF0 or the alarm search command \xEC + #if( $mode ne "alarm" ){ + # $sp1 = 0xF0; + #} else { + # $sp1 = 0xEC; + #} + + #$response = OWX_TouchByte($hash,$sp1); + + #-- clear 8 byte of device id for current search + @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + + while ( $id_bit_number <= 64) { + #loop until through all ROM bytes 0-7 + my $id_bit = OWX_TouchBit_9097($hash,1); + my $cmp_id_bit = OWX_TouchBit_9097($hash,1); + + #print "id_bit = $id_bit, cmp_id_bit = $cmp_id_bit\n"; + + if( ($id_bit == 1) && ($cmp_id_bit == 1) ){ + #print "no devices present at id_bit_number=$id_bit_number \n"; + next; + } + if ( $id_bit != $cmp_id_bit ){ + $search_direction = $id_bit; + } else { + # hä ? if this discrepancy if before the Last Discrepancy + # on a previous next then pick the same as last time + if ( $id_bit_number < $owx_LastDiscrepancy ){ + if (($owx_ROM_ID[$rom_byte_number] & $rom_byte_mask) > 0){ + $search_direction = 1; + } else { + $search_direction = 0; + } + } else { + # if equal to last pick 1, if not then pick 0 + if ($id_bit_number == $owx_LastDiscrepancy){ + $search_direction = 1; + } else { + $search_direction = 0; + } + } + # if 0 was picked then record its position in LastZero + if ($search_direction == 0){ + $last_zero = $id_bit_number; + # check for Last discrepancy in family + if ($last_zero < 9) { + $owx_LastFamilyDiscrepancy = $last_zero; + } + } + } + # print "search_direction = $search_direction, last_zero=$last_zero\n"; + # set or clear the bit in the ROM byte rom_byte_number + # with mask rom_byte_mask + #print "ROM byte mask = $rom_byte_mask, search_direction = $search_direction\n"; + if ( $search_direction == 1){ + $owx_ROM_ID[$rom_byte_number] |= $rom_byte_mask; + } else { + $owx_ROM_ID[$rom_byte_number] &= ~$rom_byte_mask; + } + # serial number search direction write bit + $response = OWX_WriteBit_9097($hash,$search_direction); + # increment the byte counter id_bit_number + # and shift the mask rom_byte_mask + $id_bit_number++; + $rom_byte_mask <<= 1; + #-- if the mask is 0 then go to new rom_byte_number and + if ($rom_byte_mask == 256){ + $rom_byte_number++; + $rom_byte_mask = 1; + } + $owx_LastDiscrepancy = $last_zero; + } + return 1; +} + +######################################################################################## +# +# OWX_TouchBit_9097 - Write/Read 1 bit from 1-wire bus (Fig. 5-8 from Maxim AN 214) +# +# Parameter hash = hash of bus master +# +# Return bit value +# +######################################################################################## + +sub OWX_TouchBit_9097 ($$) { + my ($hash,$bit) = @_; + + my $sp1; + #-- set baud rate to 115200 and query!!! + if( $bit == 1 ){ + $sp1="\xFF"; + } else { + $sp1="\x00"; + } + $owx_baud=115200; + my $res=OWX_Query_9097($hash,$sp1); + $owx_baud=9600; + #-- process result + my $sp2=substr($res,0,1); + if( $sp1 eq $sp2 ){ + return 1; + }else { + return 0; + } +} + +######################################################################################## +# +# OWX_TouchByte_9097 - Write/Read 8 bit from 1-wire bus +# +# Parameter hash = hash of bus master +# +# Return bit value +# +######################################################################################## + +sub OWX_TouchByte_9097 ($$) { + my ($hash,$byte) = @_; + + my $loop; + my $result=0; + + for( $loop=0; $loop < 8; $loop++ ){ + #-- shift result to get ready for the next bit + $result >>=1; + #-- if sending a 1 then read a bit else write a 0 + if( $byte & 0x01 ){ + if( OWX_ReadBit_9097($hash) ){ + $result |= 0x80; + } + } else { + OWX_WriteBit_9097($hash,0); + } + $byte >>= 1; + } + return $result; +} + +######################################################################################## +# +# OWX_WriteBit_9097 - Write 1 bit to 1-wire bus (Fig. 7/8 from Maxim AN 214) +# +# Parameter hash = hash of bus master +# +# Return bit value +# +######################################################################################## + +sub OWX_WriteBit_9097 ($$) { + my ($hash,$bit) = @_; + + my $sp1; + #-- set baud rate to 115200 and query!!! + if( $bit ==1 ){ + $sp1="\xFF"; + } else { + $sp1="\x00"; + } + $owx_baud=115200; + my $res=OWX_Query_9097($hash,$sp1); + $owx_baud=9600; + #-- process result + if( substr($res,0,1) eq $sp1 ){ + return 1; + } else { + return 0; + } +} + +1; diff --git a/fhem/contrib/1-Wire/21_OWTEMP.pm b/fhem/contrib/1-Wire/21_OWTEMP.pm new file mode 100644 index 000000000..f488b7d84 --- /dev/null +++ b/fhem/contrib/1-Wire/21_OWTEMP.pm @@ -0,0 +1,751 @@ +######################################################################################## +# +# OWTEMP.pm +# +# FHEM module to commmunicate with 1-Wire temperature sensors +# +# Attention: This module works as a replacement for the standard 21_OWTEMP.pm, +# therefore may communicate with the 1-Wire File System OWFS, +# but also with the newer and more direct OWX module +# +# Prefixes for subroutines of this module: +# OW = General 1-Wire routines (Martin Fischer, Peter Henning) +# OWFS = 1-Wire file system (Martin Fischer) +# OWX = 1-Wire bus master interface (Peter Henning) +# +# Martin Fischer, 2011 +# Prof. Dr. Peter A. Henning, 2012 +# +# Version 1.0 - February 21, 2012 +# +# Setup bus device in fhem.cfg as +# define OWTEMP [] [interval] [alarminterval] +# +# where may be replaced by any name string +# +# is a 1-Wire device type. If omitted, we assume this to be an +# DS1820 temperature sensor +# is a 12 character (6 byte) 1-Wire ROM ID +# without Family ID, e.g. A2D90D000800 +# [interval] is an optional query interval in seconds +# [alarminterval] as an additional parameter is ignored so far ! +# +# Additional attributes are defined in fhem.cfg as +# +# attr offset = a temperature offset added to the temperature reading +# +######################################################################################## +# +# 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; + +#-- Prototypes to make komodo happy +use vars qw{%attr %defs}; +use strict; +use warnings; +sub Log($$); + +#-- declare variables +my $ownet; +my %gets = (); +my %sets = (); +my %updates = (); + +#-- temperature globals +my $owg_temp=0; +my $owg_th=0; +my $owg_tl=0; + +# temperature scale from IODev +my $scale; + +%gets = ( + #"address" => "", + #"alias" => "", + #"crc8" => "", + #"family" => "10", + #"id" => "", + #"locator" => "", + #"power" => "", + "present" => "", + "temperature" => "", + "temphigh" => "", + "templow" => "" + #, + #"type" => "", +); + +%sets = ( + #-- obsolete "alias" => "", + "temphigh" => "", + "templow" => "", + "interval" => "" + #-- taken out into I/O-Device parameters, + #"alarminterval" => "", +); + +%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", +); + +######################################################################################## +# +# The following subroutines in alphabetical order are independent of the bus interface +# +# Prefix = OWTEMP +# +######################################################################################### +# +# OWTEMP_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################## + +sub OWTEMP_Define ($$) { + my ($hash, $def) = @_; + + # define OWTEMP [] [interval] [alarminterval] + # e.g.: define flow OWTEMP 525715020000 300 + my @a = split("[ \t][ \t]*", $def); + + my ($name,$model,$id,$interval,$alarminterval,$scale,$ret); + + #-- default + $name = $a[0]; + $interval = 300; + $scale = ""; + $ret = ""; + + #-- check syntax + return "OWTEMP: Wrong syntax, must be define OWTEMP [] [interval] [alarminterval]" + if(int(@a) < 2 || int(@a) > 6); + + #-- check if this is an old style definition, e.g. is missing + my $a2 = lc($a[2]); + my $a3 = defined($a[3]) ? lc($a[3]) : ""; + if( ($a2 eq "none") || ($a2 =~ m/^[0-9|a-f]{12}$/) ) { + $model = "DS1820"; + $id = $a[2]; + if(int(@a)>=4) { $interval = $a[3]; } + Log 1, "OWTEMP: Parameter [alarminterval] is obsolete now - must be set with I/O-Device" + if(int(@a) == 5); + } elsif( ($a3 eq "none") || ($a3 =~ m/^[0-9|a-f]{12}$/) ) { + $model = $a[2]; + return "OWTEMP: Wrong 1-Wire device model $model" + if( $model ne "DS1820"); + $id = $a[3]; + if(int(@a)>=5) { $interval = $a[4]; } + Log 1, "OWTEMP: Parameter [alarminterval] is obsolete now - must be set with I/O-Device" + if(int(@a) == 6); + } else { + return "OWTEMP: $a[0] hat missing ID or wrong ID format $a[2], specify a 12 digit value or set it to none for demo mode"; + } + + #-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY" + # + # Careful: For now this is effectively only 14 characters = 7 byte long and + # must not be passed back to the 1-Wire bus. The 8th byte is the CRC code + # and will be appended in the first call of GetTemperatures + # TODO: should be replaced by a calculation of the CRC value ! + #-- define device internals + $hash->{ALARM} = 0; + $hash->{INTERVAL} = $interval; + $hash->{ROM_ID} = "10.".$id."00"; + $hash->{OW_ID} = $id; + $hash->{OW_FAMILY} = 10; + $hash->{PRESENT} = 0; + + $modules{OWTEMP}{defptr}{$id} = $hash; + + AssignIoPort($hash); + Log 3, "OWTEMP: Warning, no 1-Wire I/O device found for $name." + 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; + + #-- 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}; + } + #-- Initial readings temperature sensor + } else { + $hash->{READINGS}{temp}{VAL} = 0.0; + $hash->{READINGS}{templow}{VAL} = 0.0; + $hash->{READINGS}{temphigh}{VAL} = 0.0; + $hash->{READINGS}{temp}{TIME} = ""; + $hash->{READINGS}{templow}{TIME} = ""; + $hash->{READINGS}{temphigh}{TIME} = ""; + $hash->{STATE} = "Defined"; + Log 3, "OWTEMP: Device $name defined."; + } + # start timer for updates + InternalTimer(time()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 0); + + # initialize device + #$ret = OWFS_InitializeDevice($hash); + #return "OWFS Can't initialize Device $name" if (!defined($ret)); + #$hash->{STATE} = "Initialized"; + + #-- InternalTimer blocks if init_done is not true + #my $oid = $init_done; + $hash->{STATE} = "Initialized"; + return undef; +} + +######################################################################################### +# +# OWTEMP_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWTEMP_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "OWTEMP_Define"; + $hash->{UndefFn} = "OWTEMP_Undef"; + $hash->{GetFn} = "OWTEMP_Get"; + $hash->{SetFn} = "OWTEMP_Set"; + #offset = a temperature offset added to the temperature reading + $hash->{AttrList}= "IODev do_not_notify:0,1 showtime:0,1 model:DS18S20 loglevel:0,1,2,3,4,5 ". + "offset "; + } + +######################################################################################## +# +# OWTEMP_Get - Implements GetFn function +# +# Parameter hash = hash of device addressed, a = argument array +# +######################################################################################## + +sub OWTEMP_Get($@) { + my ($hash, @a) = @_; + my $name = $hash->{NAME}; + my $ret; + + #-- check argument + return "OWTEMP: Get with unknown argument $a[1], choose one of ".join(",", sort keys %gets) + if(!defined($gets{$a[1]})); + #-- check syntax + return "OWTEMP: Get argument is missing @a" + if(int(@a) != 2); + + # define vars + my $reading = $a[1]; + my $value = undef; + + # get interval + if($a[1] eq "interval") { + $value = $hash->{INTERVAL}; + return "$a[0] $reading => $value"; + } + + #-- Get other values according to interface type + my $interface= $hash->{IODev}->{TYPE}; + #-- OWX interface + if( $interface eq "OWX" ){ + #-- not different from getting all values .. + $ret = OWXTEMP_GetValues($hash); + #-- OWFS interface + }elsif( $interface eq "OWFS" ){ + $ret = OWFSTEMP_GetValues($hash); + #-- Unknown interface + }else{ + return "OWTEMP: Get with wrong IODev type $interface"; + } + + #-- process results + my $tn = TimeNow(); + + #-- correct for proper offset + $owg_temp += $attr{$name}{offset} if ($attr{$name}{offset} ); + #-- Test for alarm condition + if( ($owg_temp <= $owg_tl) | ($owg_temp >= $owg_th) ){ + $hash->{STATE} = "Alarmed"; + } else { + $hash->{STATE} = "Normal"; + } + + #-- put into READINGS + $hash->{READINGS}{temp}{VAL} = $owg_temp; + $hash->{READINGS}{temp}{TIME} = $tn; + $hash->{READINGS}{templow}{VAL} = $owg_tl; + $hash->{READINGS}{templow}{TIME} = $tn; + $hash->{READINGS}{temphigh}{VAL} = $owg_th; + $hash->{READINGS}{temphigh}{TIME} = $tn; + + #-- return the special reading + if(defined($hash->{READINGS}{$reading})) { + $value = $hash->{READINGS}{$reading}{VAL}; + } + if(!defined($value)) { + Log GetLogLevel($name,4), "OWTEMP: Can't get value for $name.$reading"; + return "OWTEMP: Can't get value for $name.$reading"; + } + return "OWTEMP: $name.$reading => $value"; +} + +####################################################################################### +# +# OWTEMP_GetUpdate - Updates the reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWTEMP_GetUpdate($@) { + my $hash = shift; + + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my $owx_dev = $hash->{ROM_ID}; + my $now = TimeNow(); + my $value = ""; + my $temp = ""; + my $ret = ""; + my $count = 0; + + #-- define warnings + my $warn = "none"; + $hash->{ALARM} = "0"; + + #-- restart timer for updates + #if(!$hash->{LOCAL}) { + RemoveInternalTimer($hash); + InternalTimer(time()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 1); + #} + + # set new state + #($temp,undef) = split(" ",$hash->{READINGS}{temperature}{VAL}); + #$warn = $hash->{READINGS}{warnings}{VAL}; + #$hash->{STATE} = "T: $temp W: $warn"; + + my $interface= $hash->{IODev}->{TYPE}; + #-- real sensor + if($hash->{OW_ID} ne "none") { + #-- Get values according to interface type + my $interface= $hash->{IODev}->{TYPE}; + if( $interface eq "OWX" ){ + $ret = OWXTEMP_GetValues($hash); + }elsif( $interface eq "OWFS" ){ + $ret = OWFSTEMP_GetValues($hash); + }else{ + return "OWTEMP: GetUpdate with wrong IODev type $interface"; + } + #-- dummy sensor + } else { + $owg_temp = sprintf("%.4f",rand(85)); + $dummy{temperature} = $owg_temp; + $dummy{present} = "1"; + $hash->{PRESENT} = 1; + } + + #-- process results + my $tn = TimeNow(); + + #-- correct for proper offset + $owg_temp += $attr{$name}{offset} if ($attr{$name}{offset} ); + #-- Test for alarm condition + if( ($owg_temp <= $owg_tl) | ($owg_temp >= $owg_th) ){ + $hash->{STATE} = "Alarmed"; + } else { + $hash->{STATE} = "Normal"; + } + + #-- put into READINGS + $hash->{READINGS}{temp}{VAL} = $owg_temp; + $hash->{READINGS}{temp}{TIME} = $tn; + $hash->{READINGS}{templow}{VAL} = $owg_tl; + $hash->{READINGS}{templow}{TIME} = $tn; + $hash->{READINGS}{temphigh}{VAL} = $owg_th; + $hash->{READINGS}{temphigh}{TIME} = $tn; + + #--logging + my $rv = sprintf "temp: %3.1f templow: %3.0f temphigh: %3.0f",$owg_temp,$owg_tl,$owg_th; + Log 5, $rv; + $hash->{CHANGED}[0] = $rv; + DoTrigger($name, undef); + + return undef; +} + +####################################################################################### +# +# OWTEMP_Set - Set values for one device +# +# Parameter hash = hash of device addressed +# a = argument string +# +######################################################################################## + +sub OWTEMP_Set($@) { + my ($hash, @a) = @_; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my $path = "10.".$hash->{OW_ID}; + + #-- for the selector: which values are possible + return join(" ", sort keys %sets) if(@a == 2); + #-- check syntax + return "OWTEMP: Set needs one parameter" + if(int(@a) != 3); + #-- check argument + return "OWTEMP: Set with unknown argument $a[1], choose one of ".join(",", sort keys %sets) + if(!defined($sets{$a[1]})); + + #-- define vars + my $key = $a[1]; + my $value = $a[2]; + my $ret = undef; + + #-- set warnings + if($key eq "templow" || $key eq "temphigh") { + # check range + return "OWTEMP: Set with wrong temperature value, range is -55°C - 125°C" + if(int($value) < -55 || int($value) > 125); + } + + #-- set new timer interval + if($key eq "interval") { + # check value + return "OWTEMP: Set with too short time value, interval must be > 10" + if(int($value) < 10); + # update timer + $hash->{INTERVAL} = $value; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 1); + return undef; + } + + #-- set other values depending on interface type + Log 4, "OWTEMP: Set $hash->{NAME} $key $value"; + + my $interface= $hash->{IODev}->{TYPE}; + #-- real sensor + if($hash->{OW_ID} ne "none") { + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXTEMP_SetValues($hash,@a); + return $ret + if(defined($ret)); + #-- OWFS interface + }elsif( $interface eq "OWFS" ){ + $ret = OWFSTEMP_SetValues($hash,@a); + return $ret + if(defined($ret)); + } else { + return "OWTEMP: Set with wrong IODev type $interface"; + } + #-- dummy sensor + } else { + $dummy{$key} = $value; + } + + #-- process results + my $tn = TimeNow(); + + #-- correct for proper offset + $owg_temp += $attr{$name}{offset} if ($attr{$name}{offset} ); + #-- Test for alarm condition + if( ($owg_temp <= $owg_tl) | ($owg_temp >= $owg_th) ){ + $hash->{STATE} = "Alarmed"; + } else { + $hash->{STATE} = "Normal"; + } + + #-- put into READINGS + $hash->{READINGS}{temp}{VAL} = $owg_temp; + $hash->{READINGS}{temp}{TIME} = $tn; + $hash->{READINGS}{templow}{VAL} = $owg_tl; + $hash->{READINGS}{templow}{TIME} = $tn; + $hash->{READINGS}{temphigh}{VAL} = $owg_th; + $hash->{READINGS}{temphigh}{TIME} = $tn; + + return undef; +} + +######################################################################################## +# +# OWTEMP_Undef - Implements UndefFn function +# +# Parameter hash = hash of device addressed, name +# +######################################################################################## + +sub OWTEMP_Undef ($$) { + my ($hash, $name) = @_; + delete($modules{OWTEMP}{defptr}{$hash->{CODE}}); + RemoveInternalTimer($hash); + return undef; +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a 1-Wire bus connected +# via OWFS +# +# Prefix = OWFSTEMP +# +######################################################################################## +# +# OWFSTEMP_GetValues - Get reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWFSTEMP_GetValues($) +{ + my ($hash) = @_; + + my $ret = OW::get("/uncached/10.".$hash->{OW_ID}."/temperature"); + if( defined($ret) ) { + $hash->{PRESENT} = 1; + $owg_temp = $ret; + $owg_th = OW::get("/uncached/10.".$hash->{OW_ID}."/temphigh"); + $owg_tl = OW::get("/uncached/10.".$hash->{OW_ID}."/templow"); + } else { + $hash->{PRESENT} = 0; + $owg_temp = 0.0; + $owg_th = 0.0; + $owg_tl = 0.0; + } + + return undef; + +} + +####################################################################################### +# +# OWFSTEMP_SetValues - Implements SetFn function +# +# Parameter hash = hash of the device addressed here, a = argument array +# +######################################################################################## + +sub OWFSTEMP_SetValues($@) { + my ($hash, @a) = @_; + + #-- define vars + my $key = $a[1]; + my $value = $a[2]; + + return OW::put("10.".$hash->{OW_ID}."/$key",$value); +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a 1-Wire bus connected +# directly to the FHEM server +# +# Prefix = OWXTEMP +# +######################################################################################## +# +# OWXTEMP_GetValues - Get reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWXTEMP_GetValues($) { + + my ($hash) = @_; + + #-- For now, switch of temperature conversion command + my $con=0; + + #-- ID of the device + my $owx_dev = $hash->{ROM_ID}; + my $owx_rnf = substr($owx_dev,3,12); + my $owx_f = substr($owx_dev,0,2); + + #-- hash of the busmaster + my $master = $hash->{IODev}; + + my ($i,$j,$k); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #-- if the conversion has not been called before + if( $con==1 ){ + OWX_Reset($master); + #-- issue the match ROM command \x55 and the start conversion command + my $select=sprintf("\x55%c%c%c%c%c%c%c%c\x44",@owx_ROM_ID); + if( OWX_Block($master,$select) eq 0 ){ + return "OWXTEMP: Device $owx_dev not accessible"; + } + #-- conversion needs some 950 ms + sleep(1); + } + + #-- NOW ask the specific device + OWX_Reset($master); + #-- issue the match ROM command \x55 and the read scratchpad command \xBE + my $select=sprintf("\x55%c%c%c%c%c%c%c%c\xBE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + @owx_ROM_ID); + + my $res=OWX_Block($master,$select); + #-- process results + if( $res eq 0 ){ + return "OWXTEMP: Device $owx_dev not accessible in 2nd step"; + } + #-- process results + my @data=split(//,$res); + if (@data == 19){ + my $count_remain = ord($data[16]); + my $count_perc = ord($data[17]); + my $delta = -0.25 + ($count_perc - $count_remain)/$count_perc; + + #-- 2's complement form = signed bytes + if( $data[11] eq "\x00" ){ + $owg_temp = int(ord($data[10])/2) + $delta; + } else { + $owg_temp = 128-(int(ord($data[10])/2) + $delta); + } + $owg_th = ord($data[12]) > 127 ? 128-ord($data[12]) : ord($data[12]); + $owg_tl = ord($data[13]) > 127 ? 128-ord($data[13]) : ord($data[13]); + + return undef; + } else { + return "OWXTEMP: Device $owx_dev returns wrong number @data of bytes"; + } +} + +####################################################################################### +# +# OWXTEMP_SetValues - Implements SetFn function +# +# Parameter hash = hash of the device addressed here, a = argument array +# +######################################################################################## + +sub OWXTEMP_SetValues($@) { + my ($hash, @a) = @_; + + my $name = $hash->{NAME}; + + #-- ID of the device + my $owx_dev = $hash->{ROM_ID}; + my $owx_rnf = substr($owx_dev,3,12); + my $owx_f = substr($owx_dev,0,2); + + #-- hash of the busmaster + my $master = $hash->{IODev}; + + my ($i,$j,$k); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + # define vars + my $key = $a[1]; + my $value = $a[2]; + + #-- get the old values + $owg_temp = $hash->{READINGS}{temp}{VAL}; + $owg_tl = $hash->{READINGS}{templow}{VAL}; + $owg_th = $hash->{READINGS}{temphigh}{VAL}; + + $owg_tl = int($value) if( $key eq "templow" ); + $owg_th = int($value) if( $key eq "temphigh" ); + + #-- put into 2's complement formed (signed byte) + my $tlp = $owg_tl < 0 ? 128 - $owg_tl : $owg_tl; + my $thp = $owg_th < 0 ? 128 - $owg_th : $owg_th; + + OWX_Reset($master); + + #-- issue the match ROM command \x55 and the write scratchpad command \x4E, + # followed by the write EEPROM command \x48 + # + # so far writing the EEPROM does not work properly. + # 1. \x48 directly appended to the write scratchpad command => command ok, no effect on EEPROM + # 2. \x48 appended to match ROM => command not ok. + # 3. \x48 sent by WriteBytePower after match ROM => command ok, no effect on EEPROM + + my $select=sprintf("\x55%c%c%c%c%c%c%c%c\x4E%c%c\x48",@owx_ROM_ID,$thp,$tlp); + my $res=OWX_Block($master,$select); + + if( $res eq 0 ){ + return "OWXTEMP: Device $owx_dev not accessible"; + } + + #-- issue the match ROM command \x55 and the copy scratchpad command \x48 + #$select=sprintf("\x55%c%c%c%c%c%c%c%c",@owx_ROM_ID); + #$res=OWX_Block($hash,$select); + #$res=OWX_WriteBytePower($hash,"\x48"); + + #if( $res eq 0 ){ + # Log 3, "OWXTEMP_SetTemp: Device $romid not accessible in the second step"; + # return 0; + #} + + DoTrigger($name, undef) if($init_done); + return undef; +} + + + +1; diff --git a/fhem/contrib/1-Wire/Schaltplan_1-Wire_Interface.png b/fhem/contrib/1-Wire/Schaltplan_1-Wire_Interface.png new file mode 100644 index 0000000000000000000000000000000000000000..4c5f08cefbf7a782fc917adb9d8f7a9091dc6478 GIT binary patch literal 41324 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 LaI!C@dHw4@*a%yN literal 0 HcmV?d00001