diff --git a/fhem/FHEM/00_OWX.pm b/fhem/FHEM/00_OWX.pm new file mode 100644 index 000000000..4e4b515cd --- /dev/null +++ b/fhem/FHEM/00_OWX.pm @@ -0,0 +1,2083 @@ +######################################################################################## +# +# OWX.pm +# +# TODO: Abfangen, wenn das Serial Device nach Öffnung nicht existiert ??? +# set init als rediscover ausführen. +# ungültige ID's von ungültigen devices => rauswerfen. +# +# FHEM module to commmunicate with 1-Wire bus devices +# * via an active DS2480/DS2482/DS2490/DS9097U bus master interface attached to an USB port +# * via a passive DS9097 interface attached to an USB port +# * via a network-attached CUNO +# Internally these interfaces are vastly different, read the corresponding Wiki pages +# http://fhemwiki.de/wiki/Interfaces_f%C3%BCr_1-Wire +# +# Version 2.24 - October, 2012 +# +# Prof. Dr. Peter A. Henning, 2012 +# +# define OWX for USB interfaces or +# define OWX for a CUNO or COC interface +# +# where may be replaced by any name string +# is a serial (USB) device +# is a CUNO or COC device +# +# get alarms => find alarmed 1-Wire devices (not with CUNO) +# get devices => find all 1-Wire devices +# +# set interval => set period for temperature conversion and alarm testing +# set followAlarms on/off => determine whether an alarm is followed by a search for +# alarmed devices +# +# attr buspower real/parasitic - whether the 1-Wire bus is really powered or +# the 1-Wire devices take their power from the data wire (parasitic is default !) +# +# 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; +use vars qw{%attr %defs}; + +require "$attr{global}{modpath}/FHEM/DevIo.pm"; + +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", + "followAlarms" => "F" +); + +# These are attributes +my %attrs = ( +); + +#-- some globals needed for the 1-Wire module +my $owx_hwdevice; +#-- baud rate serial interface +my $owx_baud=9600; +#-- Debugging 0,1,2,3 +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 are independent of the bus interface +# +######################################################################################## +# +# OWX_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWX_Initialize ($) { + my ($hash) = @_; + #-- Provider + $hash->{Clients} = ":OWAD:OWCOUNT:OWID:OWLCD:OWMULTI:OWSWITCH:OWTHERM:"; + + #-- Normal Devices + $hash->{DefFn} = "OWX_Define"; + $hash->{UndefFn} = "OWX_Undef"; + $hash->{GetFn} = "OWX_Get"; + $hash->{SetFn} = "OWX_Set"; + $hash->{AttrList}= "loglevel:0,1,2,3,4,5,6 buspower:real,parasitic"; +} + +######################################################################################## +# +# 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); + + #-- check syntax + if(int(@a) < 3){ + return "OWX: Syntax error - must be define OWX" + } + + #-- check syntax + Log 1,"OWX: Warning - Some parameter(s) ignored, must be define OWX |" + if(int(@a) > 3); + #-- If this line contains 3 parameters, it is the bus master definition + my $dev = $a[2]; + + #-- TODO: what should we do when the specified device name contains @ already ? + $hash->{DeviceName} = $dev."\@9600"; + #-- Dummy 1-Wire ROM identifier + $hash->{ROM_ID} = "FF"; + + #-- First step: check if we have a directly connected serial interface or a CUNO/COC attached + # (mod suggested by T.Faust) + if ( $dev =~ m/\/dev\/.*/ ){ + #-- TODO: what should we do when the specified device name contains @ already ? + $hash->{DeviceName} = $dev."\@9600"; + #-- Second step in case of serial device: open the serial device to test it + my $msg = "OWX: Serial device $dev"; + my $ret = DevIo_OpenDev($hash,0,undef); + $owx_hwdevice = $hash->{USBDev}; + if(!defined($owx_hwdevice)){ + Log 1, $msg." not defined"; + return "OWX: Can't open serial device $dev: $!" + } else { + Log 1,$msg." defined"; + } + $owx_hwdevice->reset_error(); + $owx_hwdevice->baudrate(9600); + $owx_hwdevice->databits(8); + $owx_hwdevice->parity('none'); + $owx_hwdevice->stopbits(1); + $owx_hwdevice->handshake('none'); + $owx_hwdevice->write_settings; + #-- store with OWX device + $hash->{INTERFACE} = "serial"; + $hash->{HWDEVICE} = $owx_hwdevice; + + #-- sleeping for some time + select(undef,undef,undef,0.1); + + } else { + $hash->{DeviceName} = $dev; + #-- Second step in case of CUNO: See if we can open it + my $msg = "OWX: CUNO/COC device $dev"; + $owx_hwdevice = $main::defs{$dev}; + if($owx_hwdevice){ + Log 1,$msg." defined"; + #-- store with OWX device + $hash->{INTERFACE} = "CUNO"; + $hash->{HWDEVICE} = $owx_hwdevice; + #-- reset the 1-Wire system in CUNO + CUL_SimpleWrite($owx_hwdevice, "Oi"); + }else{ + Log 1, $msg." not defined"; + return "OWX: Can't open CUNO/COC device $dev: $!" + } + } + #-- Third step: see, if a bus interface is detected + if (!OWX_Detect($hash)){ + $hash->{STATE} = "Failed"; + $hash->{PRESENT} = 0; + $init_done = 1; + return undef; + } + + #-- In 10 seconds discover all devices on the 1-Wire bus + InternalTimer(gettimeofday()+10, "OWX_Discover", $hash,0); + + #-- Default settings + $hash->{interval} = 300; # kick every minute + $hash->{followAlarms} = "off"; + $hash->{ALARMED} = "no"; + + #-- InternalTimer blocks if init_done is not true + my $oid = $init_done; + $hash->{PRESENT} = 1; + $hash->{STATE} = "Initialized"; + $init_done = 1; + #-- Intiate first alarm detection and eventually conversion in a minute or so + InternalTimer(gettimeofday() + $hash->{interval}, "OWX_Kick", $hash,1); + $init_done = $oid; + $hash->{STATE} = "Active"; + return undef; +} + +######################################################################################## +# +# 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 $name = $hash->{NAME}; + my @owx_alarm_names=(); + + #-- Discover all alarmed devices on the 1-Wire bus + @owx_alarm_devs=(); + my $res = OWX_First_SER($hash,"alarm"); + while( $owx_LastDeviceFlag==0 && $res != 0){ + $res = $res & OWX_Next_SER($hash,"alarm"); + } + if( @owx_alarm_devs == 0){ + return "OWX: No alarmed 1-Wire devices found on bus $name"; + } + + #-- walk through all the devices to get their proper fhem names + foreach my $fhem_dev (sort keys %main::defs) { + #-- skip if busmaster + next if( $name eq $main::defs{$fhem_dev}{NAME} ); + #-- all OW types start with OW + next if( substr($main::defs{$fhem_dev}{TYPE},0,2) ne "OW"); + 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 on bus $name (".join(",",@owx_alarm_names).")"; +} + +######################################################################################## +# +# OWX_Complex - Send match ROM, data block and receive bytes as response +# +# Parameter hash = hash of bus master, +# owx_dev = ROM ID of device +# data = string to send +# numread = number of bytes to receive +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub OWX_Complex ($$$$) { + my ($hash,$owx_dev,$data,$numread) =@_; + my $name = $hash->{NAME}; + + #-- get the interface + $owx_interface = $hash->{INTERFACE}; + $owx_hwdevice = $hash->{HWDEVICE}; + + #-- interface error + if( !(defined($owx_interface))){ + Log 3,"OWX: Complex called with unknown interface on bus $name"; + return 0; + #-- here we treat the directly connected serial interfaces + }elsif( ($owx_interface eq "DS2480") || ($owx_interface eq "DS9097") ){ + return OWX_Complex_SER($hash,$owx_dev,$data,$numread); + + #-- here we treat the network-connected CUNO + }elsif( $owx_interface eq "CUNO" ){ + return OWX_Complex_CUNO($hash,$owx_dev,$data,$numread); + + #-- interface error + }else{ + Log 3,"OWX: Complex called with unknown interface $owx_interface on bus $name"; + return 0; + } +} + +######################################################################################## +# +# OWX_CRC - Check the CRC8 code of a device address in @owx_ROM_ID +# +# Parameter romid = if not zero, return the CRC8 value instead of checking it +# +######################################################################################## + +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); + + +sub OWX_CRC ($) { + my ($romid) = @_; + my $crc8=0; + + if( $romid eq "0" ){ + for(my $i=0; $i<8; $i++){ + $crc8 = $crc8_table[ $crc8 ^ $owx_ROM_ID[$i] ]; + } + return $crc8; + } else { + #-- from search string to byte id + $romid=~s/\.//g; + for(my $i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($romid,2*$i,2)); + } + for(my $i=0; $i<7; $i++){ + $crc8 = $crc8_table[ $crc8 ^ $owx_ROM_ID[$i] ]; + } + return $crc8; + } +} + +######################################################################################## +# +# OWX_CRC - Check the CRC8 code of an a byte string +# +# Parameter string, crc. +# If crc is defined, make a comparison, otherwise output crc8 +# +######################################################################################## + +sub OWX_CRC8 ($$) { + my ($string,$crc) = @_; + my $crc0=ord($crc); + my $crc8=0; + my @strhex; + + for(my $i=0; $i<8; $i++){ + $strhex[$i]=ord(substr($string,$i,1)); + $crc8 = $crc8_table[ $crc8 ^ $strhex[$i] ]; + } + + if( defined($crc) ){ + if ( $crc0 == $crc8 ){ + return 1; + }else{ + return 0; + } + }else{ + return $crc8; + } +} + +######################################################################################## +# +# OWX_CRC16 - Calculate the CRC16 code of a string +# +# TODO UNFINISHED CODE +# +# Parameter crc - previous CRC code, c next character +# +######################################################################################## + +sub OWX_CRC16($) { + my ($data) = @_; + + my $crc=0; + for( my $i=0; $i New CRC value = %x",$crc; + } + return $crc; +} + + +sub OWX_DOCRC16($$) { + my ($crc,$c) = @_; + + #-- polynomial for x^16 + x^15 + x^2 + 1 + my $mask = 0xA001; + + my $i; + for($i=0;$i<8;$i++) { + if(($crc ^ ord($c)) & 1) { + $crc=($crc>>1)^$mask; + } else { + $crc>>=1; + } + $c>>=1; + } + return ($crc); +} + +######################################################################################## +# +# OWX_Detect - Detect 1-Wire interface +# TODO: HAS TO BE SPLIT INTO INTERFACE DEPENDENT AND INDEPENDENT PART +# +# 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,$l,$res,$ret,$ress); + my $name = $hash->{NAME}; + my $ress0 = "OWX: 1-Wire bus $name: interface "; + $ress = $ress0; + + #-- get the interface + $owx_interface = $hash->{INTERFACE}; + $owx_hwdevice = $hash->{HWDEVICE}; + + #-- here we treat the directly connected serial interfaces + if($owx_interface eq "serial"){ + #-- timing byte for DS2480 + OWX_Query_2480($hash,"\xC1"); + + #-- Max 4 tries to detect an interface + for($l=0;$l<4;$l++) { + #-- write 1-Wire bus (Fig. 2 of Maxim AN192) + $res = OWX_Query_2480($hash,"\x17\x45\x5B\x0F\x91"); + + #-- process 4/5-byte string for detection + if( !defined($res)){ + $res=""; + $ret=0; + }elsif( ($res eq "\x16\x44\x5A\x00\x90") || ($res eq "\x16\x44\x5A\x00\x93")){ + $ress .= "master DS2480 detected for the first time"; + $owx_interface="DS2480"; + $ret=1; + } elsif( $res eq "\x17\x45\x5B\x0F\x91"){ + $ress .= "master DS2480 re-detected"; + $owx_interface="DS2480"; + $ret=1; + } elsif( ($res eq "\x17\x0A\x5B\x0F\x02") || ($res eq "\x00\x17\x0A\x5B\x0F\x02") || ($res eq "\x30\xf8\x00") ){ + $ress .= "passive DS9097 detected"; + $owx_interface="DS9097"; + $ret=1; + } else { + $ret=0; + } + last + if( $ret==1 ); + $ress .= "not found, answer was "; + for($i=0;$i".$ob."<="; + if( !defined($ob)){ + $ob=""; + $ret=0; + }elsif( $ob =~ m/OK.*/){ + $owx_interface="CUNO"; + $ress .= "DS2482 detected in $owx_hwdevice->{NAME}"; + $ret=1; + } else { + $ret=0; + } + last + if( $ret==1 ); + $ress .= "not found, answer was ".$ob; + Log 1, $ress; + $ress = $ress0; + #-- sleeping for some time + select(undef,undef,undef,0.5); + } + if( $ret == 0 ){ + $owx_interface=undef; + $ress .= "in $owx_hwdevice->{NAME} could not be addressed"; + } + } + #-- store with OWX device + $hash->{INTERFACE} = $owx_interface; + Log 1, $ress; + return $ret; +} + +######################################################################################## +# +# OWX_Discover - Discover devices on the 1-Wire bus, +# autocreate devices if not already present +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : no device present +# +######################################################################################## + +sub OWX_Discover ($) { + my ($hash) = @_; + my $res; + my $name = $hash->{NAME}; + + #-- get the interface + $owx_interface = $hash->{INTERFACE}; + $owx_hwdevice = $hash->{HWDEVICE}; + + #-- Discover all devices on the 1-Wire bus + @owx_devs=(); + my @owx_names=(); + #-- directly connected interface + if( $owx_interface =~ m/DS.*/ ){ + $res = OWX_First_SER($hash,"discover"); + while( $owx_LastDeviceFlag==0 && $res!=0 ){ + $res = $res & OWX_Next_SER($hash,"discover"); + } + #-- Ask the cuno + }else { + CUL_SimpleWrite($owx_hwdevice, "OCf"); + #-- sleeping for some time + select(undef,undef,undef,3); + CUL_SimpleWrite($owx_hwdevice, "Oc"); + select(undef,undef,undef,0.5); + my $ob = OWX_SimpleRead($owx_hwdevice); + if( $ob ){ + foreach my $dx (split(/\n/,$ob)){ + $dx =~ s/\d+\://; + my $ddx = substr($dx,14,2)."."; + #-- reverse data from culfw + for( my $i=1;$i<7;$i++){ + $ddx .= substr($dx,14-2*$i,2); + } + $ddx .= ".".substr($dx,0,2); + push (@owx_devs,$ddx); + } + } + } + #-- Go through all devices found on this bus + foreach my $owx_dev (@owx_devs) { + #-- three 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 $owx_crc = substr($owx_dev,15,3); + my $id_owx = $owx_f.".".$owx_rnf; + + my $match = 0; + + #-- Check against all existing devices + foreach my $fhem_dev (sort keys %main::defs) { + #-- skip if busmaster + # next if( $hash->{NAME} eq $main::defs{$fhem_dev}{NAME} ); + #-- all OW types start with OW + next if( substr($main::defs{$fhem_dev}{TYPE},0,2) ne "OW"); + my $id_fhem = substr($main::defs{$fhem_dev}{ROM_ID},0,15); + #-- skip interface device + next if( length($id_fhem) != 15 ); + #-- testing if equal to the one found here + # even with improper family + # Log 1, " FHEM-Device = ".substr($id_fhem,3,12)." OWX discovered device ".substr($id_owx,3,12); + if( substr($id_fhem,3,12) eq substr($id_owx,3,12) ) { + #-- warn if improper family id + if( substr($id_fhem,0,2) ne substr($id_owx,0,2) ){ + Log 1, "OWX: Warning, $fhem_dev is defined with improper family id ".substr($id_fhem,0,2). + ", correcting to ".substr($id_owx,0,2); + $main::defs{$fhem_dev}{OW_FAMILY} = substr($id_owx,0,2); + } + push(@owx_names,$main::defs{$fhem_dev}{NAME}); + #-- replace the ROM ID by the proper value including CRC + $main::defs{$fhem_dev}{ROM_ID}=$owx_dev; + $main::defs{$fhem_dev}{PRESENT}=1; + $match = 1; + last; + } + # + } + + #-- autocreate the device + if( $match==0 ){ + #-- Default name OWX_FF_XXXXXXXXXXXX, default type = OWX_FF + my $name = sprintf "OWX_%s_%s",$owx_f,$owx_rnf; + #-- Family 10 = Temperature sensor, assume DS1820 as default + if( $owx_f eq "10" ){ + CommandDefine(undef,"$name OWTHERM DS1820 $owx_rnf"); + #-- Family 12 = Switch, assume DS2406 as default + }elsif( $owx_f eq "12" ){ + CommandDefine(undef,"$name OWSWITCH DS2406 $owx_rnf"); + #-- Family 1D = Counter/RAM, assume DS2423 as default + }elsif( $owx_f eq "1D" ){ + CommandDefine(undef,"$name OWCOUNT DS2423 $owx_rnf"); + #-- Family 20 = A/D converter, assume DS2450 as default + } elsif( $owx_f eq "20" ){ + CommandDefine(undef,"$name OWAD DS2450 $owx_rnf"); + #-- Family 22 = Temperature sensor, assume DS1822 as default + }elsif( $owx_f eq "22" ){ + CommandDefine(undef,"$name OWTHERM DS1822 $owx_rnf"); + #-- Family 26 = Multisensor, assume DS2438 as default + }elsif( $owx_f eq "26" ){ + CommandDefine(undef,"$name OWMULTI DS2438 $owx_rnf"); + #-- Family 28 = Temperature sensor, assume DS18B20 as default + }elsif( $owx_f eq "28" ){ + CommandDefine(undef,"$name OWTHERM DS18B20 $owx_rnf"); + #-- Family 29 = Switch, assume DS2408 as default + }elsif( $owx_f eq "29" ){ + CommandDefine(undef,"$name OWSWITCH DS2408 $owx_rnf"); + #-- Family 3A = Switch, assume DS2413 as default + }elsif( $owx_f eq "3A" ){ + CommandDefine(undef,"$name OWSWITCH DS2413 $owx_rnf"); + #-- Family FF = LCD display + }elsif( $owx_f eq "FF" ){ + CommandDefine(undef,"$name OWLCD $owx_rnf"); + #-- All unknown families are ID only (ID-Chips have family id 09) + } else { + CommandDefine(undef,"$name OWID $owx_f $owx_rnf"); + } + #-- yes, it is on the bus and therefore present + push(@owx_names,$name); + $main::defs{$name}{PRESENT}=1; + #-- THIS IODev, default room + CommandAttr (undef,"$name IODev $hash->{NAME}"); + CommandAttr (undef,"$name room OWX"); + #-- replace the ROM ID by the proper value + $main::defs{$name}{ROM_ID}=$owx_dev; + } + } + + #-- final step: Undefine all 1-Wire devices which are not on this bus but have this IODev + foreach my $fhem_dev (sort keys %main::defs) { + #-- skip if malformed device + #next if( !defined($main::defs{$fhem_dev}{NAME}) ); + #-- all OW types start with OW + next if( substr($main::defs{$fhem_dev}{TYPE},0,2) ne "OW"); + #-- skip if the device is present. + next if( $main::defs{$fhem_dev}{PRESENT} == 1); + #-- skip if different IODev + next if( $main::defs{$fhem_dev}{IODev}{NAME} ne $hash->{NAME} ); + Log 1, "OWX: Deleting unused 1-Wire device $main::defs{$fhem_dev}{NAME} of type $main::defs{$fhem_dev}{TYPE}"; + CommandDelete(undef,$main::defs{$fhem_dev}{NAME}); + } + Log 1, "OWX: 1-Wire devices found on bus $name: (".join(",",@owx_names).")"; + return "OWX: 1-Wire devices found on bus $name: (".join(",",@owx_names).")"; +} + +######################################################################################## +# +# 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_Kick - Initiate some processes in all devices +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : Not OK +# +######################################################################################## + +sub OWX_Kick($) { + my($hash) = @_; + my $ret; + + #-- Call us in n seconds again. + InternalTimer(gettimeofday()+ $hash->{interval}, "OWX_Kick", $hash,1); + #-- During reset we see if an alarmed device is present. + OWX_Reset($hash); + + #-- Only if we have real power on the bus + if( defined($attr{$hash->{NAME}}{buspower}) && ($attr{$hash->{NAME}}{buspower} eq "real") ){ + #-- issue the skip ROM command \xCC followed by start conversion command \x44 + $ret = OWX_Complex($hash,"","\xCC\x44",0); + if( $ret eq 0 ){ + Log 3, "OWX: Failure in temperature conversion\n"; + return 0; + } + #-- sleeping for some time + select(undef,undef,undef,0.5); + } + return 1; +} + +######################################################################################## +# +# OWX_Reset - Reset the 1-Wire bus +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_Reset ($) { + my ($hash)=@_; + + #-- get the interface + $owx_interface = $hash->{INTERFACE}; + $owx_hwdevice = $hash->{HWDEVICE}; + + #-- interface error + if( !(defined($owx_interface))){ + Log 3,"OWX: Reset called with undefined interface"; + return 0; + }elsif( $owx_interface eq "DS2480" ){ + return OWX_Reset_2480($hash); + }elsif( $owx_interface eq "DS9097" ){ + return OWX_Reset_9097($hash); + }elsif( $owx_interface eq "CUNO" ){ + return OWX_Reset_CUNO($hash); + }else{ + Log 3,"OWX: Reset called with unknown interface $owx_interface"; + return 0; + } +} + +######################################################################################## +# +# 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; + } + } + + #-- Set alarm behaviour + if( $a[0] eq "followAlarms" ){ + #-- only values >= 15 secs allowed + if( (lc($a[1]) eq "off") && ($hash->{followAlarms} eq "on") ){ + $hash->{interval} = "off"; + $res = 1; + }elsif( (lc($a[1]) eq "on") && ($hash->{followAlarms} eq "off") ){ + $hash->{interval} = "off"; + $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) = @_; + RemoveInternalTimer($hash); + DevIo_CloseDev($hash); + return undef; +} + +######################################################################################## +# +# 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; + + #-- get the interface + $owx_interface = $hash->{INTERFACE}; + $owx_hwdevice = $hash->{HWDEVICE}; + + #-- directly connected interface + if( ($owx_interface eq "DS2480") || ($owx_interface eq "DS9097") ){ + return OWX_Verify_SER($hash,$dev) + #-- Ask the cuno + }elsif( $owx_interface eq "CUNO" ) { + return OWX_Verify_CUNO($hash,$dev) + } else { + Log 1,"OWX: Verify called with unknown interface"; + return 0; + } +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for direct serial bus interface +# +######################################################################################## +# +# OWX_Complex_SER - Send match ROM, data block and receive bytes as response +# +# Parameter hash = hash of bus master, +# owx_dev = ROM ID of device +# data = string to send +# numread = number of bytes to receive +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub OWX_Complex_SER ($$$$) { + my ($hash,$owx_dev,$data,$numread) =@_; + + my $select; + my $res = ""; + my $res2 = ""; + my ($i,$j,$k); + + #-- has match ROM part + if( $owx_dev ){ + #-- ID of the device + my $owx_rnf = substr($owx_dev,3,12); + my $owx_f = substr($owx_dev,0,2); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + $owx_dev=~s/\.//g; + for(my $i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($owx_dev,2*$i,2)); + } + $select=sprintf("\x55%c%c%c%c%c%c%c%c",@owx_ROM_ID).$data; + #-- has no match ROM part + } else { + $select=$data; + } + #-- has receive data part + if( $numread >0 ){ + #$numread += length($data); + for( my $i=0;$i<$numread;$i++){ + $select .= "\xFF"; + }; + } + + #-- for debugging + if( $owx_debug > 1){ + $res2 = "OWX_Complex_SER: Sending out "; + for($i=0;$i 1){ + $res2 = "OWX_Complex_SER: Receiving "; + for($i=0;$i{HWDEVICE}; + $owx_hwdevice->baudrate($owx_baud); + $owx_hwdevice->write_settings; + + if( $owx_debug > 2){ + 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 for some time + select(undef,undef,undef,0.04); + + #-- read the data + my ($count_in, $string_in) = $owx_hwdevice->read(48); + + if( $owx_debug > 2){ + 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 for some time + select(undef,undef,undef,0.04); + 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=""; + + my ($res,$r1,$r2); + #-- 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 + $res =OWX_Query_2480($hash,$cmd); + + #-- if not ok, try for max. a second time + $r1 = ord(substr($res,0,1)) & 192; + if( $r1 != 192){ + #Log 1, "Trying second reset"; + $res =OWX_Query_2480($hash,$cmd); + } + + #-- process result + $r1 = ord(substr($res,0,1)) & 192; + if( $r1 != 192){ + Log 3, "OWX: Reset failure"; + return 0; + } + $hash->{ALARMED} = "no"; + + $r2 = ord(substr($res,0,1)) & 3; + + if( $r2 == 3 ){ + #Log 3, "OWX: No presence detected"; + return 1; + }elsif( $r2 ==2 ){ + Log 1, "OWX: Alarm presence detected"; + $hash->{ALARMED} = "yes"; + } + 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=""; + my $res=0; + for (my $i=0; $i{HWDEVICE}; + $owx_hwdevice->baudrate($owx_baud); + $owx_hwdevice->write_settings; + + if( $owx_debug > 2){ + 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 for some time + select(undef,undef,undef,0.01); + + #-- read the data + my ($count_in, $string_in) = $owx_hwdevice->read(48); + + if( $owx_debug > 2){ + 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 for some time + select(undef,undef,undef,0.01); + + 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; + my $bytein=$byte; + + 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 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; + } +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a CUNO interface +# +######################################################################################## +# +# OWX_Complex_CUNO - Send match ROM, data block and receive bytes as response +# +# Parameter hash = hash of bus master, +# owx_dev = ROM ID of device +# data = string to send +# numread = number of bytes to receive +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub OWX_Complex_CUNO ($$$$) { + my ($hash,$owx_dev,$data,$numread) =@_; + + my $select; + my $res = ""; + + #-- has match ROM part + if( $owx_dev ){ + #-- ID of the device + my $owx_rnf = substr($owx_dev,3,12); + my $owx_f = substr($owx_dev,0,2); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to reverse string id + $owx_dev=~s/\.//g; + for(my $i=0;$i<8;$i++){ + $owx_ROM_ID[7-$i]=substr($owx_dev,2*$i,2); + } + $select=sprintf("Om%s%s%s%s%s%s%s%s",@owx_ROM_ID); + Log 3,"OWX: Sending match ROM to CUNO ".$select + if( $owx_debug > 1); + CUL_SimpleWrite($owx_hwdevice, $select); + my $ob = OWX_SimpleRead($owx_hwdevice); + #-- padding first 9 bytes into result string, since we have this + # in the serial interfaces as well + $res .= "000000000"; + } + #-- has data part + if ( $data ){ + OWX_Send_CUNO($hash,$data); + $res .= $data; + } + #-- has receive part + if( $numread > 0 ){ + #$numread += length($data); + $res.=OWX_Receive_CUNO($hash,$numread); + } + Log 3,"OWX: returned from CUNO $res" + if( $owx_debug > 1); + return $res; +} + +######################################################################################## +# +# OWX_Receive_CUNO - Read from the CUNO +# +# Parameter: hash = hash of bus master, numread = number of bytes to read +# +# Return: string received from the CUNO +# +######################################################################################## + +sub OWX_Receive_CUNO ($$) { + my ($hash,$numread) = @_; + my $res=""; + my $res2=""; + + for( + my $i=0;$i<$numread;$i++){ + CUL_SimpleWrite($owx_hwdevice, "OrB"); + my $ob = OWX_SimpleRead($owx_hwdevice); + #-- process results + if( !(defined($ob)) ){ + return ""; + #-- four bytes received makes one byte of result + }elsif( length($ob) == 4 ){ + $res .= sprintf("%c",hex(substr($ob,0,2))); + $res2 .= "0x".substr($ob,0,2)." "; + #-- 20 bytes received = leftover from match + }elsif( length($ob) == 20 ){ + $numread++; + }else{ + Log 1,"OWX: Received unexpected number of ".length($ob)." bytes from CUNO/COC"; + } + } + Log 3, "OWX: Receive from CUNO/COC $numread bytes = $res2" + if( $owx_debug > 1); + + return($res); +} + +######################################################################################## +# +# OWX_Reset_CUNO - Reset the 1-Wire bus +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_Reset_CUNO ($) { + CUL_SimpleWrite($owx_hwdevice, "ORb"); + my $ob = OWX_SimpleRead($owx_hwdevice); + if( substr($ob,0,4) eq "OK:1" ){ + return 1; + }else{ + return 0 + } +} + +######################################################################################### +# +# OWX_Send_CUNO - Send data block +# +# Parameter hash = hash of bus master, data = string to send +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub OWX_Send_CUNO ($$) { + my ($hash,$data) =@_; + my ($i,$j,$k); + my $res = ""; + my $res2 = ""; + + for( $i=0;$i 1); +} + +######################################################################################### +# +# OWX_SimpleRead - Reading with retry. +# Suggested in this way by Dirk Tostmann +# +# Parameter hash = hash of device +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub OWX_SimpleRead($) +{ + my ($hash) = @_; + my $buf = DevIo_DoSimpleRead($owx_hwdevice); + + # Lets' try again: Some drivers return len(0) on the first read... + if(defined($buf) && length($buf) == 0) { + #-- allow some time + select(undef,undef,undef,0.5); + $buf = DevIo_DoSimpleRead($owx_hwdevice); + } + + if(!defined($buf) || length($buf) == 0) { + DevIo_Disconnected($hash); + return undef; + } + return $buf; +} +######################################################################################## +# +# OWX_Verify_CUNO - 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_CUNO ($$) { + my ($hash,$dev) = @_; + my $i; + + #-- Ask the cuno + CUL_SimpleWrite($owx_hwdevice, "OCf"); + #-- sleeping for some time + select(undef,undef,undef,3); + CUL_SimpleWrite($owx_hwdevice, "Oc"); + my $ob = OWX_SimpleRead($owx_hwdevice); + if( $ob ){ + foreach my $dx (split(/\n/,$ob)){ + $dx =~ s/\d+\://; + my $ddx = substr($dx,14,2)."."; + #-- reverse data from culfw + for( my $i=1;$i<7;$i++){ + $ddx .= substr($dx,14-2*$i,2); + } + $ddx .= ".".substr($dx,0,2); + return 1 if( $dev eq $ddx); + } + return 0; + } else { + return 0; + } +} + +1; diff --git a/fhem/FHEM/21_OWAD.pm b/fhem/FHEM/21_OWAD.pm new file mode 100644 index 000000000..17a0caa2b --- /dev/null +++ b/fhem/FHEM/21_OWAD.pm @@ -0,0 +1,1024 @@ +######################################################################################## +# +# OWAD.pm +# +# WIESO UNINITIALIZED in 623FF +# +# FHEM module to commmunicate with 1-Wire A/D converters DS2450 +# +# Attention: This module may communicate with the OWX module, +# but currently not with the 1-Wire File System OWFS +# +# Prefixes for subroutines of this module: +# OW = General 1-Wire routines Peter Henning) +# OWX = 1-Wire bus master interface (Peter Henning) +# OWFS = 1-Wire file system (??) +# +# Prof. Dr. Peter A. Henning, 2012 +# +# Version 2.24 - October, 2012 +# +# Setup bus device in fhem.cfg as +# +# define OWAD [] [interval] +# +# where may be replaced by any name string +# +# is a 1-Wire device type. If omitted, we assume this to be an +# DS2450 A/D converter +# is a 12 character (6 byte) 1-Wire ROM ID +# without Family ID, e.g. A2D90D000800 +# [interval] is an optional query interval in seconds +# +# get id => FAM_ID.ROM_ID.CRC +# get present => 1 if device present, 0 if not +# get interval => query interval +# get reading => measurement for all channels +# get alarm => alarm measurement settings for all channels +# get status => alarm and i/o status for all channels +# +# set interval => set period for measurement +# +# Additional attributes are defined in fhem.cfg, in some cases per channel, where =A,B,C,D +# Note: attributes are read only during initialization procedure - later changes are not used. +# +# attr event on-change/on-update = when to write an event (default= on-update) +# +# attr stateAL0 "" = character string for denoting low normal condition, default is green down triangle +# attr stateAH0 "" = character string for denoting high normal condition, default is green up triangle +# attr stateAL1 "" = character string for denoting low alarm condition, default is red down triangle +# attr stateAH1 "" = character string for denoting high alarm condition, default is red up triangle +# attr Name | = name for the channel | a type description for the measured value +# attr Unit | = unit of measurement for this channel | its abbreviation +# attr Offset = offset added to the reading in this channel +# attr Factor = factor multiplied to (reading+offset) in this channel +# attr Alarm = alarm setting in this channel, either both, low, high or none (default) +# attr Low = measurement value (on the scale determined by offset and factor) for low alarm +# attr High = measurement value (on the scale determined by offset and factor) for high alarm +# +######################################################################################## +# +# 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($$); + +#-- value globals +my @owg_status; +my $owg_state; +#-- channel name - fixed is the first array, variable the second +my @owg_fixed = ("A","B","C","D"); +my @owg_channel; +#-- channel values - always the raw values from the device +my @owg_val; +#-- channel mode - fixed for now +my @owg_mode = ("input","input","input","input"); +#-- resolution in bit - fixed for now +my @owg_resoln = (16,16,16,16); +#-- raw range in mV - fixed for now +my @owg_range = (5100,5100,5100,5100); +#-- alarm status 0 = disabled, 1 = enabled, but not alarmed, 2 = alarmed +my @owg_slow; +my @owg_shigh; +#-- alarm values - always the raw values committed to the device +my @owg_vlow; +my @owg_vhigh; +#-- variables for display strings +my ($stateal1,$stateah1,$stateal0,$stateah0); + +my %gets = ( + "id" => "", + "present" => "", + "interval" => "", + "reading" => "", + "alarm" => "", + "status" => "", +); + +my %sets = ( + "interval" => "" +); + +my %updates = ( + "present" => "", + "reading" => "", + "alarm" => "", + "status" => "" +); + + +######################################################################################## +# +# The following subroutines are independent of the bus interface +# +# Prefix = OWAD +# +######################################################################################## +# +# OWAD_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWAD_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "OWAD_Define"; + $hash->{UndefFn} = "OWAD_Undef"; + $hash->{GetFn} = "OWAD_Get"; + $hash->{SetFn} = "OWAD_Set"; + #Name = channel name + #Offset = a v(oltage) offset added to the reading + #Factor = a v(oltage) factor multiplied with (reading+offset) + #Unit = a unit of measure + my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model:DS2450 loglevel:0,1,2,3,4,5 ". + "stateAL0 stateAL1 stateAH0 stateAH1 event:on-update,on-change "; + + for( my $i=0;$i<4;$i++ ){ + $attlist .= " ".$owg_fixed[$i]."Name"; + $attlist .= " ".$owg_fixed[$i]."Offset"; + $attlist .= " ".$owg_fixed[$i]."Factor"; + $attlist .= " ".$owg_fixed[$i]."Unit"; + $attlist .= " ".$owg_fixed[$i]."Alarm"; + $attlist .= " ".$owg_fixed[$i]."Low"; + $attlist .= " ".$owg_fixed[$i]."High"; + } + $hash->{AttrList} = $attlist; +} + +######################################################################################### +# +# OWAD_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################### + +sub OWAD_Define ($$) { + my ($hash, $def) = @_; + + # define OWAD [] [interval] + # e.g.: define flow OWAD 525715020000 300 + my @a = split("[ \t][ \t]*", $def); + + my ($name,$model,$fam,$id,$crc,$interval,$scale,$ret); + + #-- default + $name = $a[0]; + $interval = 300; + $scale = ""; + $ret = ""; + + #-- check syntax + return "OWAD: Wrong syntax, must be define OWAD [] [interval]" + if(int(@a) < 2 || int(@a) > 5); + + #-- check if this is an old style definition, e.g. is missing + my $a2 = $a[2]; + my $a3 = defined($a[3]) ? $a[3] : ""; + if( $a2 =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $model = "DS2450"; + $id = $a[2]; + if(int(@a)>=4) { $interval = $a[3]; } + } elsif( $a3 =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $model = $a[2]; + return "OWAD: Wrong 1-Wire device model $model" + if( $model ne "DS2450"); + $id = $a[3]; + if(int(@a)>=5) { $interval = $a[4]; } + } else { + return "OWAD: $a[0] ID $a[2] invalid, specify a 12 digit value"; + } + + #-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY" + # determine CRC Code - only if this is a direct interface + $crc = defined($hash->{IODev}->{INTERFACE}) ? sprintf("%02x",OWX_CRC("20.".$id."00")) : "00"; + + #-- Define device internals + $hash->{ROM_ID} = "20.".$id.$crc; + $hash->{OW_ID} = $id; + $hash->{OW_FAMILY} = "20"; + $hash->{PRESENT} = 0; + $hash->{INTERVAL} = $interval; + + #-- Couple to I/O device + AssignIoPort($hash); + Log 3, "OWAD: Warning, no 1-Wire I/O device found for $name." + if(!defined($hash->{IODev}->{NAME})); + $modules{OWAD}{defptr}{$id} = $hash; + $hash->{STATE} = "Defined"; + Log 3, "OWAD: Device $name defined."; + + #-- Initialization reading according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- Start timer for initialization in a few seconds + InternalTimer(time()+10, "OWAD_InitializeDevice", $hash, 0); + + #-- Start timer for updates + InternalTimer(time()+$hash->{INTERVAL}, "OWAD_GetValues", $hash, 0); + + return undef; +} + +######################################################################################## +# +# OWAD_InitializeDevice - delayed setting of initial readings and channel names +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWAD_InitializeDevice($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + + $stateal1 = defined($attr{$name}{stateAL1}) ? $attr{$name}{stateAL1} : ""; + $stateah1 = defined($attr{$name}{stateAH1}) ? $attr{$name}{stateAH1} : ""; + $stateal0 = defined($attr{$name}{stateAL0}) ? $attr{$name}{stateAL0} : ""; + $stateah0 = defined($attr{$name}{stateAH0}) ? $attr{$name}{stateAH0} : ""; + + #-- Initial readings + @owg_val = (0.0,0.0,0.0,0.0); + @owg_slow = (0,0,0,0); + @owg_shigh = (0,0,0,0); + + #-- Set channel names, channel units and alarm values + for( my $i=0;$i"; + push(@cnama,"unknown"); + } + + #-- unit + my $unit = defined($attr{$name}{$owg_fixed[$i]."Unit"}) ? $attr{$name}{$owg_fixed[$i]."Unit"} : "Volt|V"; + my @unarr= split(/\|/,$unit); + if( int(@unarr)!=2 ){ + Log 1, "OWAD: Incomplete channel unit specification $unit. Better use $unit|"; + push(@unarr,""); + } + + #-- offset and scale factor + my $offset = defined($attr{$name}{$owg_fixed[$i]."Offset"}) ? $attr{$name}{$owg_fixed[$i]."Offset"} : 0.0; + my $factor = defined($attr{$name}{$owg_fixed[$i]."Factor"}) ? $attr{$name}{$owg_fixed[$i]."Factor"} : 1.0; + #-- put into readings + $owg_channel[$i] = $cnama[0]; + $hash->{READINGS}{"$owg_channel[$i]"}{TYPE} = $cnama[1]; + $hash->{READINGS}{"$owg_channel[$i]"}{UNIT} = $unarr[0]; + $hash->{READINGS}{"$owg_channel[$i]"}{UNITABBR} = $unarr[1]; + $hash->{READINGS}{"$owg_channel[$i]"}{OFFSET} = $offset; + $hash->{READINGS}{"$owg_channel[$i]"}{FACTOR} = $factor; + + #-- alarm + my $alarm = defined($attr{$name}{$owg_fixed[$i]."Alarm"}) ? $attr{$name}{$owg_fixed[$i]."Alarm"} : "none"; + my $vlow = defined($attr{$name}{$owg_fixed[$i]."Low"}) ? $attr{$name}{$owg_fixed[$i]."Low"} : 0.0; + my $vhigh = defined($attr{$name}{$owg_fixed[$i]."High"}) ? $attr{$name}{$owg_fixed[$i]."High"} : 5.0; + if( $alarm eq "low" || $alarm eq "both" ){ + $owg_slow[$i]=1; + } + if( $alarm eq "high" || $alarm eq "both" ){ + $owg_shigh[$i]=1; + }; + $owg_vlow[$i] = ($vlow/$factor - $offset); + $owg_vhigh[$i] = ($vhigh/$factor - $offset); + } + + #-- set status according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- OWX interface + if( !defined($interface) ){ + return "OWAD: Interface missing"; + } elsif( $interface eq "OWX" ){ + OWXAD_SetPage($hash,"alarm"); + OWXAD_SetPage($hash,"status"); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetPage($hash,"reading"); + #-- Unknown interface + }else{ + return "OWAD: InitializeDevice with wrong IODev type $interface"; + } + + #-- Initialize all the display stuff + OWAD_FormatValues($hash); +} + +######################################################################################## +# +# OWAD_FormatValues - put together various format strings +# +# Parameter hash = hash of device addressed, fs = format string +# +######################################################################################## + +sub OWAD_FormatValues($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + my ($offset,$factor,$vval,$vlow,$vhigh); + my ($value1,$value2,$value3) = ("","",""); + my $galarm = 0; + + my $tn = TimeNow(); + + #-- formats for output + for (my $i=0;$i{READINGS}{"$owg_channel[$i]"}{OFFSET}; + $factor = $hash->{READINGS}{"$owg_channel[$i]"}{FACTOR}; + #-- correct values for proper offset, factor + $vval = int(($owg_val[$i] + $offset)*$factor*1000)/1000;; + #-- put into READINGS + $hash->{READINGS}{"$owg_channel[$i]"}{VAL} = $vval; + $hash->{READINGS}{"$owg_channel[$i]"}{TIME} = $tn; + + #-- correct alarm values for proper offset, factor + $vlow = int(($owg_vlow[$i] + $offset)*$factor*1000)/1000; + $vhigh = int(($owg_vhigh[$i] + $offset)*$factor*1000)/1000; + + #-- put into READINGS + $hash->{READINGS}{$owg_channel[$i]."Low"}{VAL} = $vlow; + $hash->{READINGS}{$owg_channel[$i]."Low"}{TIME} = $tn; + $hash->{READINGS}{$owg_channel[$i]."High"}{VAL} = $vhigh; + $hash->{READINGS}{$owg_channel[$i]."High"}{TIME} = $tn; + + #-- string buildup for return value, STATE and alarm + $value1 .= sprintf( "%s: %5.3f %s", $owg_channel[$i], $vval,$hash->{READINGS}{"$owg_channel[$i]"}{UNITABBR}); + $value2 .= sprintf( "%s: %5.2f %s ", $owg_channel[$i], $vval,$hash->{READINGS}{"$owg_channel[$i]"}{UNITABBR}); + $value3 .= sprintf( "%s: " , $owg_channel[$i]); + + #-- Test for alarm condition + #-- alarm signature low + if( $owg_slow[$i] == 0 ) { + #$value2 .= " "; + $value3 .= "-"; + } else { + if( $vval > $vlow ){ + $owg_slow[$i] = 1; + $value2 .= $stateal0; + $value3 .= $stateal0; + } else { + $galarm = 1; + $owg_slow[$i] = 2; + $value2 .= $stateal1; + $value3 .= $stateal1; + } + } + #-- alarm signature high + if( $owg_shigh[$i] == 0 ) { + #$value2 .= " "; + $value3 .= "-"; + } else { + if( $vval < $vhigh ){ + $owg_shigh[$i] = 1; + $value2 .= $stateah0; + $value3 .= $stateah0; + } else { + $galarm = 1; + $owg_shigh[$i] = 2; + $value2 .= $stateah1; + $value3 .= $stateah1; + } + } + + #-- insert comma + if( $i{STATE} = $value2; + #-- alarm + $hash->{ALARM} = $galarm; + $hash->{READINGS}{alarms}{VAL} = $value3; + $hash->{READINGS}{alarms}{TIME} = $tn; + return $value1; +} + +######################################################################################## +# +# OWAD_Get - Implements GetFn function +# +# Parameter hash = hash of device addressed, a = argument array +# +######################################################################################## + +sub OWAD_Get($@) { + my ($hash, @a) = @_; + + my $reading = $a[1]; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my ($value,$value2,$value3) = (undef,undef,undef); + my $ret = ""; + my $offset; + my $factor; + + #-- check syntax + return "OWAD: Get argument is missing @a" + if(int(@a) != 2); + + #-- check argument + return "OWAD: Get with unknown argument $a[1], choose one of ".join(",", sort keys %gets) + if(!defined($gets{$a[1]})); + + #-- get id + if($a[1] eq "id") { + $value = $hash->{ROM_ID}; + return "$name.id => $value"; + } + + #-- get present + if($a[1] eq "present") { + #-- hash of the busmaster + my $master = $hash->{IODev}; + $value = OWX_Verify($master,$hash->{ROM_ID}); + $hash->{PRESENT} = $value; + return "$name.present => $value"; + } + + #-- get interval + if($a[1] eq "interval") { + $value = $hash->{INTERVAL}; + return "$name.interval => $value"; + } + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- get reading according to interface type + my $interface= $hash->{IODev}->{TYPE}; + if($a[1] eq "reading") { + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXAD_GetPage($hash,"reading"); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetPage($hash,"reading"); + #-- Unknown interface + }else{ + return "OWAD: Get with wrong IODev type $interface"; + } + + #-- process results + if( defined($ret) ){ + return "OWAD: Could not get values from device $name"; + } + $hash->{PRESENT} = 1; + return "OWAD: $name.$reading => ".OWAD_FormatValues($hash); + } + + #-- get alarm values according to interface type + if($a[1] eq "alarm") { + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXAD_GetPage($hash,"alarm"); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetPage($hash,"alarm"); + #-- Unknown interface + }else{ + return "OWAD: Get with wrong IODev type $interface"; + } + + #-- process results + if( defined($ret) ){ + return "OWAD: Could not get values from device $name"; + } + $hash->{PRESENT} = 1; + OWAD_FormatValues($hash); + + #-- output string looks differently here + $value = ""; + for (my $i=0;$i{READINGS}{$owg_channel[$i]."Low"}{VAL}, + $hash->{READINGS}{$owg_channel[$i]."High"}{VAL}; + } + return "OWAD: $name.$reading => $value"; + } + + #-- get status values according to interface type + if($a[1] eq "status") { + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXAD_GetPage($hash,"status"); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetPage($hash,"status"); + #-- Unknown interface + }else{ + return "OWAD: Get with wrong IODev type $interface"; + } + + #-- process results + if( defined($ret) ){ + return "OWAD: Could not get values from device $name"; + } + $hash->{PRESENT} = 1; + OWAD_FormatValues($hash); + return "OWAD: $name.$reading => ".$hash->{READINGS}{alarms}{VAL}; + } +} + +####################################################################################### +# +# OWAD_GetValues - Updates the reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWAD_GetValues($) { + my $hash = shift; + + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my $value = ""; + my $ret = ""; + my $offset; + my $factor; + + #-- define warnings + my $warn = "none"; + $hash->{ALARM} = "0"; + + #-- restart timer for updates + RemoveInternalTimer($hash); + InternalTimer(time()+$hash->{INTERVAL}, "OWAD_GetValues", $hash, 1); + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- Get readings, alarms and stati according to interface type + my $interface= $hash->{IODev}->{TYPE}; + if( $interface eq "OWX" ){ + $ret = OWXAD_GetPage($hash,"reading"); + $ret = OWXAD_GetPage($hash,"alarm"); + $ret = OWXAD_GetPage($hash,"status"); + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetValues($hash); + }else{ + return "OWAD: GetValues with wrong IODev type $interface"; + } + + #-- process results + if( defined($ret) ){ + return "OWAD: Could not get values from device $name"; + } + $hash->{PRESENT} = 1; + + #-- old state, new state + my $oldval = $hash->{STATE}; + $value=OWAD_FormatValues($hash); + my $newval = $hash->{STATE}; + #--logging depends on setting of the event-attribute + Log 5, $value; + my $ev = defined($attr{$name}{"event"}) ? $attr{$name}{"event"} : "on-update"; + if( ($ev eq "on-update") || (($ev eq "on-change") && ($newval ne $oldval)) ){ + $hash->{CHANGED}[0] = $value; + DoTrigger($name, undef); + } + + return undef; +} + +####################################################################################### +# +# OWAD_Set - Set one value for device +# +# Parameter hash = hash of device addressed +# a = argument array +# +######################################################################################## + +sub OWAD_Set($@) { + my ($hash, @a) = @_; + + my $key = $a[1]; + my $value = $a[2]; + + #-- for the selector: which values are possible + if (@a == 2){ + my $newkeys = join(" ", sort keys %sets); + for( my $i=0;$i{NAME}; + my $model = $hash->{OW_MODEL}; + + #-- set new timer interval + if($key eq "interval") { + # check value + return "OWAD: Set with short interval, must be > 1" + if(int($value) < 1); + # update timer + $hash->{INTERVAL} = $value; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWAD_GetValues", $hash, 1); + return undef; + } + + #-- find out which channel we have + my $tc =$key; + if( $tc =~ s/(.*)(Alarm|Low|High)/$channel=$1/se ) { + for (my $i=0;$i{IODev}->{TYPE}; + + #-- check alarm values + if( $key =~ m/(.*)(Alarm)/ ) { + return "OWAD: Set with wrong value $value for $key, allowed is none/low/high/both" + if($value ne "none" && $value ne "low" && $value ne "high" && $value ne "both"); + if( $value eq "low" || $value eq "both" ){ + $owg_slow[$channo]=1; + } else{ + $owg_slow[$channo]=0; + } + if( $value eq "high" || $value eq "both" ){ + $owg_shigh[$channo]=1; + } else{ + $owg_shigh[$channo]=0; + } + + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXAD_SetPage($hash,"status"); + return $ret + if(defined($ret)); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_SetValues($hash,@a); + # return $ret + # if(defined($ret)); + } else { + return "OWAD: Set with wrong IODev type $interface"; + } + }elsif( $key =~ m/(.*)(Low|High)/ ) { + $offset = $attr{$name}{$owg_fixed[$channo]."Offset"}; + $factor = $attr{$name}{$owg_fixed[$channo]."Factor"}; + + #-- find upper and lower boundaries for given offset/factor + my $mmin = 0.0; + + $mmin += $offset if ( $offset ); + $mmin *= $factor if ( $factor ); + + my $mmax = $owg_range[$channo]/1000; + $mmax += $offset if ( $offset ); + $mmax *= $factor if ( $factor ); + + return sprintf("OWAD: Set with wrong value $value for $key, range is [%3.1f,%3.1f]",$mmin,$mmax) + if($value < $mmin || $value > $mmax); + + $value /= $factor if ( $factor ); + $value -= $offset if ( $offset ); + #-- round to those numbers understood by the device + my $value2 = int($value*255000/$owg_range[$channo])*$owg_range[$channo]/255000; + + #-- set alarm value in the device + if( $key =~ m/(.*)Low/ ){ + $owg_vlow[$channo] = $value2; + } elsif( $key =~ m/(.*)High/ ){ + $owg_vhigh[$channo] = $value2; + } + + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXAD_SetPage($hash,"alarm"); + return $ret + if(defined($ret)); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_SetValues($hash,@a); + # return $ret + # if(defined($ret)); + } else { + return "OWAD: Set with wrong IODev type $interface"; + } + } + + #-- process results - we have to reread the device + $hash->{PRESENT} = 1; + OWAD_GetValues($hash); + OWAD_FormatValues($hash); + Log 4, "OWAD: Set $hash->{NAME} $key $value"; + + return undef; +} + +######################################################################################## +# +# OWAD_Undef - Implements UndefFn function +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWAD_Undef ($) { + my ($hash) = @_; + delete($modules{OWAD}{defptr}{$hash->{OW_ID}}); + RemoveInternalTimer($hash); + return undef; +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a 1-Wire bus connected +# via OWFS +# +# Prefix = OWFSAD +# +######################################################################################## + + + + + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a 1-Wire bus connected +# directly to the FHEM server +# +# Prefix = OWXAD +# +######################################################################################## +# +# OWXAD_GetPage - Get one memory page from device +# +# Parameter hash = hash of device addressed +# page = "reading", "alarm" or "status" +# +######################################################################################## + +sub OWXAD_GetPage($$) { + + my ($hash,$page) = @_; + + my ($select, $res, $res2, $res3, @data); + + #-- ID of the device, hash of the busmaster + my $owx_dev = $hash->{ROM_ID}; + my $master = $hash->{IODev}; + + my ($i,$j,$k); + + #=============== get the voltage reading =============================== + if( $page eq "reading") { + OWX_Reset($master); + #-- issue the match ROM command \x55 and the start conversion command + $res= OWX_Complex($master,$owx_dev,"\x3C\x0F\x00\xFF\xFF",0); + if( $res eq 0 ){ + return "OWXAD: Device $owx_dev not accessible for conversion"; + } + #-- conversion needs some 5 ms per channel + select(undef,undef,undef,0.02); + + #-- issue the match ROM command \x55 and the read conversion page command + # \xAA\x00\x00 + $select="\xAA\x00\x00"; + #=============== get the alarm reading =============================== + } elsif ( $page eq "alarm" ) { + #-- issue the match ROM command \x55 and the read alarm page command + # \xAA\x10\x00 + $select="\xAA\x10\x00"; + #=============== get the status reading =============================== + } elsif ( $page eq "status" ) { + #-- issue the match ROM command \x55 and the read status memory page command + # \xAA\x08\x00 r + $select="\xAA\x08\x00"; + #=============== wrong value requested =============================== + } else { + return "OWXAD: Wrong memory page requested"; + } + + #-- reset the bus + OWX_Reset($master); + #-- reading 9 + 3 + 8 data bytes and 2 CRC bytes = 22 bytes + $res=OWX_Complex($master,$owx_dev,$select,10); + #Log 1, "OWXAD: Device $owx_dev returns data of length ".length($res); + if( $res eq 0 ){ + return "OWXAD: Device $owx_dev not accessible in reading $page page"; + } + + #-- reset the bus + OWX_Reset($master); + + #-- process results + @data=split(//,$res); + return "OWXAD: invalid data length, ".int(@data)." bytes" + if (@data != 22); + #return "invalid data" + # if (ord($data[17])<=0); + #return "invalid CRC" + # if (OWX_CRC8(substr($res,10,8),$data[18])==0); + + #=============== get the voltage reading =============================== + if( $page eq "reading"){ + for( $i=0;$i{ROM_ID}; + my $master = $hash->{IODev}; + + my ($i,$j,$k); + + #=============== set the alarm values =============================== + if ( $page eq "alarm" ) { + #-- issue the match ROM command \x55 and the set alarm page command + # \x55\x10\x00 reading 8 data bytes and 2 CRC bytes + $select="\x55\x10\x00"; + for( $i=0;$i<4;$i++){ + $select .= sprintf "%c\xFF\xFF\xFF",int($owg_vlow[$i]*255000/$owg_range[$i]); + $select .= sprintf "%c\xFF\xFF\xFF",int($owg_vhigh[$i]*255000/$owg_range[$i]); + } + #=============== set the status =============================== + } elsif ( $page eq "status" ) { + my ($sb1,$sb2); + #-- issue the match ROM command \x55 and the set status memory page command + # \x55\x08\x00 reading 8 data bytes and 2 CRC bytes + $select="\x55\x08\x00"; + for( $i=0;$i<4;$i++){ + if( $owg_mode[$i] eq "input" ){ + #-- resolution (TODO: check !) + $sb1 = $owg_resoln[$i]-1; + #-- alarm enabled + $sb2 = ( $owg_slow[$i] > 0 ) ? 4 : 0; + $sb2 += ( $owg_shigh[$i] > 0 ) ? 8 : 0; + #-- range + $sb2 |= 1 + if( $owg_range[$i] > 2550 ); + } else { + $sb1 = 128; + $sb2 = 0; + } + $select .= sprintf "%c\xFF\xFF\xFF",$sb1; + $select .= sprintf "%c\xFF\xFF\xFF",$sb2; + } + #=============== wrong page write attempt =============================== + } else { + return "OWXAD: Wrong memory page write attempt"; + } + + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + + #-- process results + if( $res eq 0 ){ + return "OWXAD: Device $owx_dev not accessible for writing"; + } + + return undef; +} + +1; diff --git a/fhem/FHEM/21_OWCOUNT.pm b/fhem/FHEM/21_OWCOUNT.pm new file mode 100644 index 000000000..a5f159630 --- /dev/null +++ b/fhem/FHEM/21_OWCOUNT.pm @@ -0,0 +1,1020 @@ +######################################################################################## +# +# OWCOUNT.pm +# +# FHEM module to commmunicate with 1-Wire Counter/RAM DS2423 +# +# Attention: This module may communicate with the OWX module, +# but currently not with the 1-Wire File System OWFS +# +# Prefixes for subroutines of this module: +# OW = General 1-Wire routines Peter Henning) +# OWX = 1-Wire bus master interface (Peter Henning) +# OWFS = 1-Wire file system (??) +# +# Prof. Dr. Peter A. Henning, 2012 +# +# Version 2.24 - October, 2012 +# +# Setup bus device in fhem.cfg as +# +# define OWCOUNT [] [interval] +# +# where may be replaced by any name string +# +# is a 1-Wire device type. If omitted, we assume this to be an +# DS2423 Counter/RAM +# is a 12 character (6 byte) 1-Wire ROM ID +# without Family ID, e.g. A2D90D000800 +# [interval] is an optional query interval in seconds +# +# get id => FAM_ID.ROM_ID.CRC +# get present => 1 if device present, 0 if not +# get interval => query interval +# get memory => 32 byte string from page 0..13 +# get midnight => todays starting value for counter +# get counter => value for counter +# get counters => values for both counters +# +# set interval => set query interval for measurement +# set memory => 32 byte string into page 0..13 +# set midnight => todays starting value for counter +# set init yes => re-initialize device +# +# Additional attributes are defined in fhem.cfg, in some cases per channel, where =A,B +# Note: attributes are read only during initialization procedure - later changes are not used. +# +# attr event on-change/on-update = when to write an event (default= on-update) +# +# attr UnitInReading = whether the physical unit is written into the reading = 1 (default) or 0 +# attr Name | = name for the channel | a type description for the measured value +# attr Unit | = unit of measurement for this channel | its abbreviation +# attr Offset = offset added to the reading in this channel +# attr Factor = factor multiplied to (reading+offset) in this channel +# attr Mode = counting mode = normal(default) or daily +# attr Period = period for rate calculation = hour (default), minute or second +# +# In normal counting mode each returned counting value will be factor*(reading+offset) +# In daily counting mode each returned counting value will be factor*(reading+offset)-midnight +# where midnight is stored as string in the 32 byte memory associated with the counter +# +# Log Lines +# after each interval : / : / +# example: 2012-07-30_00:07:55 OWX_C Taste: 17.03 p 28.1 p/h B: 7.0 cts 0.0 cts/min +# after midnight : : +# example: 2012-07-30_00:00:57 OWX_C D_29: 2012-7-29_23:59:59 Taste: 110.0 p, B: 7.0 cts +######################################################################################## +# +# 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($$); + +#-- channel name - fixed is the first array, variable the second +my @owg_fixed = ("A","B"); +my @owg_channel; +my @owg_rate; +#-- channel values - always the raw values from the device +my @owg_val; +my @owg_midnight; +my $owg_str; + +my %gets = ( + "id" => "", + "present" => "", + "interval" => "", + "memory" => "", + "midnight" => "", + "counter" => "", + "counters" => "" +); + +my %sets = ( + "interval" => "", + "memory" => "", + "midnight" => "", + "init" => "" +); + +my %updates = ( + "present" => "", + "counter" => "" +); + + +######################################################################################## +# +# The following subroutines are independent of the bus interface +# +# Prefix = OWCOUNT +# +######################################################################################## +# +# OWCOUNT_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWCOUNT_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "OWCOUNT_Define"; + $hash->{UndefFn} = "OWCOUNT_Undef"; + $hash->{GetFn} = "OWCOUNT_Get"; + $hash->{SetFn} = "OWCOUNT_Set"; + + #-- see header for attributes + my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model:DS2423 loglevel:0,1,2,3,4,5 UnitInReading:0,1 ". + "event:on-update,on-change"; + for( my $i=0;$i{AttrList} = $attlist; +} + +######################################################################################### +# +# OWCOUNT_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################### + +sub OWCOUNT_Define ($$) { + my ($hash, $def) = @_; + + my @a = split("[ \t][ \t]*", $def); + my ($name,$model,$fam,$id,$crc,$interval,$scale,$ret); + + #-- default + $name = $a[0]; + $interval = 300; + $scale = ""; + $ret = ""; + + #-- check syntax + return "OWCOUNT: Wrong syntax, must be define OWCOUNT [] [interval]" + if(int(@a) < 2 || int(@a) > 5); + + #-- check if this is an old style definition, e.g. is missing + my $a2 = $a[2]; + my $a3 = defined($a[3]) ? $a[3] : ""; + if( $a2 =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $model = "DS2423"; + $id = $a[2]; + if(int(@a)>=4) { $interval = $a[3]; } + } elsif( $a3 =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $model = $a[2]; + return "OWCOUNT: Wrong 1-Wire device model $model" + if( $model ne "DS2423"); + $id = $a[3]; + if(int(@a)>=5) { $interval = $a[4]; } + } else { + return "OWCOUNT: $a[0] ID $a[2] invalid, specify a 12 digit value"; + } + + #-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY" + # determine CRC Code - only if this is a direct interface + $crc = defined($hash->{IODev}->{INTERFACE}) ? sprintf("%02x",OWX_CRC("1D.".$id."00")) : "00"; + + #-- Define device internals + $hash->{ROM_ID} = "1D.".$id.$crc; + $hash->{OW_ID} = $id; + $hash->{OW_FAMILY} = "1D"; + $hash->{PRESENT} = 0; + $hash->{INTERVAL} = $interval; + + #-- Couple to I/O device + AssignIoPort($hash); + Log 3, "OWCOUNT: Warning, no 1-Wire I/O device found for $name." + if(!defined($hash->{IODev}->{NAME})); + $modules{OWCOUNT}{defptr}{$id} = $hash; + $hash->{STATE} = "Defined"; + Log 3, "OWCOUNT: Device $name defined."; + + #-- Initialization reading according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- Start timer for initialization in a few seconds + InternalTimer(time()+1, "OWCOUNT_InitializeDevice", $hash, 0); + + #-- Start timer for updates + InternalTimer(time()+$hash->{INTERVAL}, "OWCOUNT_GetValues", $hash, 0); + + return undef; +} + +######################################################################################## +# +# OWCOUNT_InitializeDevice - delayed setting of initial readings and channel names +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWCOUNT_InitializeDevice($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + $hash->{PRESENT} = 0; + + #-- Set channel names, channel units and alarm values + for( my $i=0;$i"; + push(@cnama,"unknown"); + } + + #-- unit + my $unit = defined($attr{$name}{$owg_fixed[$i]."Unit"}) ? $attr{$name}{$owg_fixed[$i]."Unit"} : "counts|cts"; + my @unarr= split(/\|/,$unit); + if( int(@unarr)!=2 ){ + Log 1, "OWCOUNT: Incomplete channel unit specification $unit. Better use |$unit"; + push(@unarr,""); + } + + #-- rate unit + my $period = defined($attr{$name}{$owg_fixed[$i]."Period"}) ? $attr{$name}{$owg_fixed[$i]."Period"} : "hour"; + + #-- offset and scale factor + my $offset = defined($attr{$name}{$owg_fixed[$i]."Offset"}) ? $attr{$name}{$owg_fixed[$i]."Offset"} : 0; + my $factor = defined($attr{$name}{$owg_fixed[$i]."Factor"}) ? $attr{$name}{$owg_fixed[$i]."Factor"} : 1; + #-- put into readings + $owg_channel[$i] = $cnama[0]; + $hash->{READINGS}{"$owg_channel[$i]"}{TYPE} = $cnama[1]; + $hash->{READINGS}{"$owg_channel[$i]"}{UNIT} = $unarr[0]; + $hash->{READINGS}{"$owg_channel[$i]"}{UNITABBR} = $unarr[1]; + $hash->{READINGS}{"$owg_channel[$i]"}{PERIOD} = $period; + $hash->{READINGS}{"$owg_channel[$i]"}{OFFSET} = $offset; + $hash->{READINGS}{"$owg_channel[$i]"}{FACTOR} = $factor; + + $owg_rate[$i] = $cnama[0]."_rate"; + my $runit = ""; + if( $period eq "hour" ){ + $runit = "/h"; + }elsif( $period eq "minute" ){ + $runit = "/min"; + } else { + $runit = "/s"; + } + $hash->{READINGS}{"$owg_rate[$i]"}{TYPE} = $cnama[1]."_rate"; + $hash->{READINGS}{"$owg_rate[$i]"}{UNIT} = $unarr[0].$runit; + $hash->{READINGS}{"$owg_rate[$i]"}{UNITABBR} = $unarr[1].$runit; + #-- some special cases + # Energy/Power + $hash->{READINGS}{"$owg_rate[$i]"}{UNIT} = "kW" + if ($unarr[0].$runit eq "kWh/h" ); + $hash->{READINGS}{"$owg_rate[$i]"}{UNITABBR} = "kW" + if ($unarr[1].$runit eq "kWh/h" ); + #Log 1,"OWCOUNT InitializeDevice with period $period and UNITABBR = ".$hash->{READINGS}{"$owg_rate[$i]"}{UNITABBR}; + + } + + #-- set status according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- OWX interface + if( !defined($interface) ){ + return "OWCOUNT: Interface missing"; + } elsif( $interface eq "OWX" ){ + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetPage($hash,"reading"); + #-- Unknown interface + }else{ + return "OWCOUNT: InitializeDevice with wrong IODev type $interface"; + } + #-- Initialize all the display stuff + OWCOUNT_FormatValues($hash); +} + +######################################################################################## +# +# OWCOUNT_FormatValues - put together various format strings and assemble STATE variable +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWCOUNT_FormatValues($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + my ($offset,$factor,$period,$unit,$runit,$midnight,$vval,$vrate); + my ($value1,$value2,$value3,$value4,$value5) = ("","","","",""); + my $galarm = 0; + + my $tn = TimeNow(); + my ($sec, $min, $hour, $day, $month, $year, $wday,$yday,$isdst) = localtime(time); + my ($seco,$mino,$houro,$dayo,$montho,$yearo,$dayrest); + my $daybreak = 0; + my $monthbreak = 0; + + my $present = $hash->{PRESENT}; + #-- remove units if they are unwanted + my $unir = defined($attr{$name}{"UnitInReading"}) ? $attr{$name}{"UnitInReading"} : 1; + + #-- formats for output + for (my $i=0;$i{READINGS}{"$owg_channel[$i]"}{OFFSET}; + $factor = $hash->{READINGS}{"$owg_channel[$i]"}{FACTOR}; + $unit = $hash->{READINGS}{"$owg_channel[$i]"}{UNITABBR}; + $period = $hash->{READINGS}{"$owg_channel[$i]"}{PERIOD}; + $runit = $hash->{READINGS}{"$owg_rate[$i]"}{UNITABBR}; + + #-- only if attribute value Mode=daily, take the midnight value from memory + if( defined($attr{$name}{$owg_fixed[$i]."Mode"} )){ + if( $attr{$name}{$owg_fixed[$i]."Mode"} eq "daily"){ + $midnight = $owg_midnight[$i]; + #-- parse float from midnight + $midnight =~ /([\d\.]+)/; + $midnight = 0.0 if(!(defined($midnight))); + } else { + $midnight = 0.0; + } + } else { + $midnight = 0.0; + } + + #-- correct values for proper offset, factor + # careful: midnight value has not been corrected so far ! + #-- 1 decimal + if( $factor == 1.0 ){ + $vval = int(($owg_val[$i] + $offset - $midnight)*10)/10; + #-- 3 decimals + } else { + $vval = int((($owg_val[$i] + $offset)*$factor - $midnight)*1000)/1000; + } + + #-- get the old values + my $oldval = $hash->{READINGS}{"$owg_channel[$i]"}{VAL}; + my $oldtim = $hash->{READINGS}{"$owg_channel[$i]"}{TIME}; + $oldtim = "" if(!defined($oldtim)); + + #-- safeguard against the case where no previous measurement + if( length($oldtim) > 0 ){ + #-- time difference in seconds + ($yearo,$montho,$dayrest) = split(/-/,$oldtim); + $dayo = substr($dayrest,0,2); + ($houro,$mino,$seco) = split(/:/,substr($dayrest,3)); + my $delt = ($hour-$houro)*3600 + ($min-$mino)*60 + ($sec-$seco); + #-- correct time for wraparound at midnight + if( ($delt<0) && ($present==1)){ + $daybreak = 1; + $delt += 86400; + } + #-- correct $vval for wraparound of 32 bit counter + if( ($vval < $oldval) && ($daybreak==0) && ($present==1) ){ + Log 1,"OWCOUNT TODO: Counter wraparound"; + } + + if( $daybreak==1 ){ + #-- linear interpolation + my $dt = ((24-$houro)*3600 -$mino*60 - $seco)/( ($hour+24-$houro)*3600 + ($min-$mino)*60 + ($sec-$seco) ); + my $dv = $oldval*(1-$dt)+$vval*$dt; + #-- correct reading in daily mode + if( $midnight > 0.0 ){ + $vval -= $dv; + $delt *= (1-$dt); + $oldval = 0.0; + } + #-- in any mode store the interpolated value in the midnight store + $midnight += $dv; + OWXCOUNT_SetPage($hash,14+$i,sprintf("%f",$midnight)); + #-- string buildup for monthly logging + $value4 .= sprintf( "%s: %5.1f %s", $owg_channel[$i], $dv,$unit); + if( $day<$dayo ){ + $monthbreak = 1; + Log 1, "OWCOUNT: Change of month"; + #-- string buildup for yearly logging + $value5 .= sprintf( "%s: %5.1f %s", $owg_channel[$i], $dv,$unit); + } + } + #-- rate + if( ($delt > 0.0) && $present ){ + $vrate = ($vval-$oldval)/$delt; + } else { + $vrate = 0.0; + } + #-- correct rate for period setting + if( $period eq "hour" ){ + $vrate*=3600; + }elsif( $period eq "minute" ){ + $vrate*=60; + } + + if( !defined($runit) ){ + Log 1,"OWCOUNT: Error in rate unit definition. i=$i, owg_rate[i]=".$owg_rate[$i]; + $runit = "ERR"; + } + + #-- string buildup for return value and STATE + if( $unir ){ + #-- 1 decimal + if( $factor == 1.0 ){ + $value1 .= sprintf( "%s: %5.1f %s %5.2f %s", $owg_channel[$i], $vval,$unit,$vrate,$runit); + $value2 .= sprintf( "%s: %5.1f %s %5.2f %s", $owg_channel[$i], $vval,$unit,$vrate,$runit); + #-- 3 decimals + } else { + $value1 .= sprintf( "%s: %5.3f %s %5.2f %s", $owg_channel[$i], $vval,$unit,$vrate,$runit); + $value2 .= sprintf( "%s: %5.2f %s %5.2f %s", $owg_channel[$i], $vval,$unit,$vrate,$runit); + } + }else { + #-- 1 decimal + if( $factor == 1.0 ){ + $value1 .= sprintf( "%s: %5.1f %5.2f", $owg_channel[$i], $vval,$vrate); + $value2 .= sprintf( "%s: %5.1f %5.2f", $owg_channel[$i], $vval,$vrate); + #-- 3 decimals + } else { + $value1 .= sprintf( "%s: %5.3f %5.2f", $owg_channel[$i], $vval,$vrate); + $value2 .= sprintf( "%s: %5.2f %5.2f", $owg_channel[$i], $vval,$vrate); + } + } + $value3 .= sprintf( "%s: " , $owg_channel[$i]); + } + #-- put into READINGS + $hash->{READINGS}{"$owg_channel[$i]"}{VAL} = $vval; + #-- but times and rate only when valid + if( $present ){ + $hash->{READINGS}{"$owg_channel[$i]"}{TIME} = $tn; + $hash->{READINGS}{"$owg_rate[$i]"}{VAL} = $vrate; + $hash->{READINGS}{"$owg_rate[$i]"}{TIME} = $tn; + } else { + $hash->{READINGS}{"$owg_channel[$i]"}{TIME} = ""; + $hash->{READINGS}{"$owg_rate[$i]"}{VAL} = ""; + $hash->{READINGS}{"$owg_rate[$i]"}{TIME} = ""; + } + #-- insert comma + if( $i{STATE} = $value2; + + #-- write units as header if they are unwanted in lines + if( $unir == 0 ){ + #TODO: the entry into CHANGED must be into the lowest array position + } + + if( $daybreak == 1 ){ + $value4 = sprintf("D_%d: %d-%d-%d_23:59:59 %s",$dayo,$yearo,$montho,$dayo,$value4); + #-- needs to be higher array elements, + $hash->{CHANGED}[1] = $value4; + Log 1,$name." ".$value4; + if( $monthbreak == 1){ + $value5 = sprintf("M_%d: %d-%d-%d_23:59:59 %s",$montho,$yearo,$montho,$dayo,$value5); + #-- needs to be higher array elements, + $hash->{CHANGED}[2] = $value5; + Log 1,$name." ".$value5; + } + } + return $value1; +} + +######################################################################################## +# +# OWCOUNT_Get - Implements GetFn function +# +# Parameter hash = hash of device addressed, a = argument array +# +######################################################################################## + +sub OWCOUNT_Get($@) { + my ($hash, @a) = @_; + + my $reading = $a[1]; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my $value = undef; + my $ret = ""; + my $page; + my $channo = undef; + my $channel; + + #-- check syntax + return "OWCOUNT: Get argument is missing @a" + if(int(@a) < 2); + + #-- check argument + return "OWCOUNT: Get with unknown argument $a[1], choose one of ".join(",", sort keys %gets) + if(!defined($gets{$a[1]})); + + #-- get id + if($a[1] eq "id") { + $value = $hash->{ROM_ID}; + return "$name.id => $value"; + } + + #-- get present + if($a[1] eq "present") { + #-- hash of the busmaster + my $master = $hash->{IODev}; + $value = OWX_Verify($master,$hash->{ROM_ID}); + $hash->{PRESENT} = $value; + return "$name.present => $value"; + } + + #-- get interval + if($a[1] eq "interval") { + $value = $hash->{INTERVAL}; + return "$name.interval => $value"; + } + + #-- reset presence + #-- TODO: THIS IS TOO STRONG !!! + #$hash->{PRESENT} = 0; + + #-- get memory page/counter according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- check syntax for getting memory page 0..13 or midnight A/B + if( ($reading eq "memory") || ($reading eq "midnight") ){ + if( $reading eq "memory" ){ + return "OWCOUNT: set needs parameter when reading memory: " + if( int(@a)<2 ); + $page=int($a[2]); + if( ($page<0) || ($page>13) ){ + return "OWXCOUNT: Wrong memory page requested"; + } + }else{ + return "OWCOUNT: set needs parameter when reading midnight: " + if( int(@a)<2 ); + #-- find out which channel we have + if( ($a[2] eq $owg_channel[0]) || ($a[2] eq "A") ){ + $page=14; + }elsif( ($a[2] eq $owg_channel[1]) || ($a[2] eq "B") ){ + $page=15; + } else { + return "OWCOUNT: invalid midnight counter address, must be A, B or defined channel name" + } + } + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXCOUNT_GetPage($hash,$page); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetPage($hash,"reading"); + #-- Unknown interface + }else{ + return "OWCOUNT: Get with wrong IODev type $interface"; + } + #-- when we have a return code, we have an error + if( $ret ){ + return $ret; + }else{ + return "OWCOUNT: $name.$reading [$page] =>".$owg_str; + } + } + + #-- check syntax for getting counter + if( $reading eq "counter" ){ + return "OWCOUNT: get needs parameter when reading counter: " + if( int(@a)<2 ); + #-- find out which channel we have + if( ($a[2] eq $owg_channel[0]) || ($a[2] eq "A") ){ + $page=14; + }elsif( ($a[2] eq $owg_channel[1]) || ($a[2] eq "B") ){ + $page=15; + } else { + return "OWCOUNT: invalid counter address, must be A, B or defined channel name" + } + + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXCOUNT_GetPage($hash,$page); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetPage($hash,"reading"); + #-- Unknown interface + }else{ + return "OWCOUNT: Get with wrong IODev type $interface"; + } + #-- check syntax for getting counters + }elsif( $reading eq "counters" ){ + return "OWCOUNT: get needs no parameter when reading counters" + if( int(@a)==1 ); + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXCOUNT_GetPage($hash,14); + $ret = OWXCOUNT_GetPage($hash,15); + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetValues($hash); + }else{ + return "OWCOUNT: GetValues with wrong IODev type $interface"; + } + } + #-- process results + if( $ret ){ + return "OWCOUNT: Could not get values from device $name"; + } + $hash->{PRESENT} = 1; + return "OWCOUNT: $name.$reading => ".OWCOUNT_FormatValues($hash); + +} + +####################################################################################### +# +# OWCOUNT_GetValues - Updates the reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWCOUNT_GetValues($) { + my $hash = shift; + + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my $value = ""; + my $ret = ""; + my $offset; + my $factor; + + #-- define warnings + my $warn = "none"; + $hash->{ALARM} = "0"; + + #-- restart timer for updates + RemoveInternalTimer($hash); + InternalTimer(time()+$hash->{INTERVAL}, "OWCOUNT_GetValues", $hash, 1); + + #-- reset presence - maybe this is too strong + $hash->{PRESENT} = 0; + + #-- Get readings according to interface type + my $interface= $hash->{IODev}->{TYPE}; + if( $interface eq "OWX" ){ + $ret = OWXCOUNT_GetPage($hash,14); + $ret = OWXCOUNT_GetPage($hash,15); + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetValues($hash); + }else{ + return "OWCOUNT: GetValues with wrong IODev type $interface"; + } + + #-- process results + if( defined($ret) ){ + return "OWCOUNT: Could not get values from device $name"; + } + $hash->{PRESENT} = 1; + + #-- old state, new state + my $oldval = $hash->{STATE}; + $value=OWCOUNT_FormatValues($hash); + my $newval = $hash->{STATE}; + #--logging depends on setting of the event-attribute + Log 5, $value; + my $ev = defined($attr{$name}{"event"}) ? $attr{$name}{"event"} : "on-update"; + if( ($ev eq "on-update") || (($ev eq "on-change") && ($newval ne $oldval)) ){ + $hash->{CHANGED}[0] = $value; + DoTrigger($name, undef); + } + + return undef; +} + +####################################################################################### +# +# OWCOUNT_Set - Set one value for device +# +# Parameter hash = hash of device addressed +# a = argument array +# +######################################################################################## + +sub OWCOUNT_Set($@) { + my ($hash, @a) = @_; + + my $key = $a[1]; + my $value = $a[2]; + + #-- for the selector: which values are possible + if (@a == 2){ + my $newkeys = join(" ", keys %sets); + return $newkeys ; + } + + #-- check syntax + return "OWCOUNT: Set needs one parameter" + if( int(@a)!=3 ); + #-- check argument + if( !defined($sets{$a[1]}) ){ + return "OWCOUNT: Set with unknown argument $a[1]"; + } + + #-- define vars + my $ret = undef; + my $page; + my $data; + my $channo = undef; + my $channel; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + + #-- reset the device + if($key eq "init") { + return "OWCOUNT: init needs parameter 'yes'" + if($value ne "yes"); + OWCOUNT_InitializeDevice($hash); + return "OWCOUNT: Re-initialized device"; + } + + #-- set new timer interval + if($key eq "interval") { + # check value + return "OWCOUNT: Set with short interval, must be > 1" + if(int($value) < 1); + # update timer + $hash->{INTERVAL} = $value; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWCOUNT_GetValues", $hash, 1); + return undef; + } + + #-- set memory page/counter according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- check syntax for setting memory page 0..13 or midnight A/B + if( ($key eq "memory") || ($key eq "midnight") ){ + if( $key eq "memory" ){ + return "OWCOUNT: set needs parameter when writing memory: " + if( int(@a)<2 ); + $page=int($a[2]); + if( ($page<0) || ($page>13) ){ + return "OWXCOUNT: Wrong memory page write attempted"; + } + }else{ + return "OWCOUNT: set needs parameter when writing midnight: " + if( int(@a)<2 ); + #-- find out which channel we have + if( ($a[2] eq $owg_channel[0]) || ($a[2] eq "A") ){ + $page=14; + }elsif( ($a[2] eq $owg_channel[1]) || ($a[2] eq "B") ){ + $page=15; + } else { + return "OWCOUNT: invalid midnight counter address, must be A, B or defined channel name" + } + } + + $data=$a[3]; + for( my $i=4;$i 32 ){ + Log 1,"OWXCOUNT: memory data truncated to 32 characters"; + $data=substr($data,0,32); + }elsif( length($data) < 32 ){ + for(my $i=length($data)-1;$i<32;$i++){ + $data.=" "; + } + } + + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXCOUNT_SetPage($hash,$page,$data); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_setPage($hash,$page,$data); + #-- Unknown interface + }else{ + return "OWCOUNT: Set with wrong IODev type $interface"; + } + } + + #-- process results - we have to reread the device + $hash->{PRESENT} = 1; + OWCOUNT_GetValues($hash); + OWCOUNT_FormatValues($hash); + Log 4, "OWCOUNT: Set $hash->{NAME} $key $value"; +} + +######################################################################################## +# +# OWCOUNT_Undef - Implements UndefFn function +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWCOUNT_Undef ($) { + my ($hash) = @_; + delete($modules{OWCOUNT}{defptr}{$hash->{OW_ID}}); + RemoveInternalTimer($hash); + return undef; +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a 1-Wire bus connected +# via OWFS +# +# Prefix = OWFSCOUNT +# +######################################################################################## + + + + + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a 1-Wire bus connected +# directly to the FHEM server +# +# Prefix = OWXCOUNT +# +######################################################################################## +# +# OWXAD_GetPage - Get one memory page + counter from device +# +# Parameter hash = hash of device addressed +# page = 0..15 +# +######################################################################################## + +sub OWXCOUNT_GetPage($$) { + my ($hash,$page) = @_; + + my ($select, $res, $res2, $res3, @data); + + #-- ID of the device, hash of the busmaster + my $owx_dev = $hash->{ROM_ID}; + my $master = $hash->{IODev}; + + my ($i,$j,$k); + + #=============== wrong value requested =============================== + if( ($page<0) || ($page>15) ){ + return "OWXCOUNT: Wrong memory page requested"; + } + #=============== get memory + counter =============================== + #-- issue the match ROM command \x55 and the read memory + counter command + # \xA5 TA1 TA2 reading 40 data bytes and 2 CRC bytes + my $ta2 = ($page*32) >> 8; + my $ta1 = ($page*32) & 255; + #Log 1, "OWXCOUNT: getting page Nr. $ta2 $ta1"; + $select=sprintf("\xA5%c%c",$ta1,$ta2); + #-- reset the bus + OWX_Reset($master); + #-- reading 9 + 3 + 40 data bytes and 2 CRC bytes = 54 bytes + $res=OWX_Complex($master,$owx_dev,$select,42); + if( $res eq 0 ){ + return "OWX: Device $owx_dev not accessible in reading $page page"; + } + + #-- process results + if( length($res) < 54 ) { + #Log 1, "OWXCOUNT: warning, have received ".length($res)." bytes in first step"; + #-- read the data in a second step + $res.=OWX_Complex($master,"","",0); + #-- process results + if( length($res) < 54 ) { + #Log 1, "OWXCOUNT: warning, have received ".length($res)." bytes in second step"; + #-- read the data in a third step + $res.=OWX_Complex($master,"","",0); + } + } + #-- reset the bus + OWX_Reset($master); + + #-- process results + @data=split(//,$res); + return "OWXCOUNT: invalid data length, ".length($res)." bytes in three steps" + if( length($res) < 54); + #return "invalid data" + # if (ord($data[17])<=0); + #return "invalid CRC" + # if (OWX_CRC8(substr($res,10,8),$data[18])==0); + + #-- first 12 byte are 9 ROM ID +3 command, next 32 are memory + #-- memory part, treated as string + $owg_str=substr($res,12,32); + #-- counter part + if( ($page == 14) || ($page == 15) ){ + @data=split(//,substr($res,44)); + if ( ($data[4] | $data[5] | $data[6] | $data[7]) ne "\x00" ){ + Log 1, "OWXCOUNT: Device $owx_dev returns invalid data ".ord($data[4])." ".ord($data[5])." ".ord($data[6])." ".ord($data[7]); + return "OWXCOUNT: Device $owx_dev returns invalid data"; + } + + #-- first ignore memory and only use counter (Fehler gefunden von jamesgo) + my $value = (ord($data[3])<<24) + (ord($data[2])<<16) +(ord($data[1])<<8) + ord($data[0]); + + if( $page == 14) { + $owg_val[0] = $value; + $owg_midnight[0] = $owg_str; + }elsif( $page == 15) { + $owg_val[1] = $value; + $owg_midnight[1] = $owg_str; + } + } + + return undef; +} + +######################################################################################## +# +# OWXCOUNT_SetPage - Set one memory page of device +# +# Parameter hash = hash of device addressed +# page = "alarm" or "status" +# +######################################################################################## + +sub OWXCOUNT_SetPage($$$) { + + my ($hash,$page,$data) = @_; + + my ($select, $res, $res2, $res3); + + #-- ID of the device, hash of the busmaster + my $owx_dev = $hash->{ROM_ID}; + my $master = $hash->{IODev}; + + #=============== wrong value requested =============================== + if( ($page<0) || ($page>15) ){ + return "OWXCOUNT: Wrong memory page requested"; + } + #=============== set memory ========================================= + #-- issue the match ROM command \x55 and the write scratchpad command + # \x0F TA1 TA2 and the read scratchpad command reading 3 data bytes + my $ta2 = ($page*32) >> 8; + my $ta1 = ($page*32) & 255; + #Log 1, "OWXCOUNT: setting page Nr. $ta2 $ta1"; + $select=sprintf("\x0F%c%c",$ta1,$ta2).$data; + #-- reset the bus + OWX_Reset($master); + #-- reading 9 + 3 + 16 bytes = 29 bytes + $res=OWX_Complex($master,$owx_dev,$select,0); + if( $res eq 0 ){ + return "OWX: Device $owx_dev not accessible in writing scratchpad"; + } + + #-- issue the match ROM command \x55 and the read scratchpad command + # \xAA + #-- reset the bus + OWX_Reset($master); + #-- reading 9 + 4 + 16 bytes = 28 bytes + # TODO: sometimes much less than 28 + $res=OWX_Complex($master,$owx_dev,"\xAA",28); + if( length($res) < 13 ){ + return "OWX: Device $owx_dev not accessible in reading scratchpad"; + } + + #-- issue the match ROM command \x55 and the copy scratchpad command + # \x5A followed by 3 byte authentication code + $select="\x5A".substr($res,10,3); + #-- reset the bus + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,6); + + #-- process results + if( $res eq 0 ){ + return "OWXCOUNT: Device $owx_dev not accessible for writing"; + } + + return undef; +} + +1; diff --git a/fhem/FHEM/21_OWID.pm b/fhem/FHEM/21_OWID.pm new file mode 100644 index 000000000..45c6c9135 --- /dev/null +++ b/fhem/FHEM/21_OWID.pm @@ -0,0 +1,212 @@ +######################################################################################## +# +# OWID.pm +# +# FHEM module to commmunicate with general 1-Wire ID-ROMS +# +# Attention: This module may communicate with the OWX module, +# but currently not with the 1-Wire File System OWFS +# +# Prefixes for subroutines of this module: +# OW = General 1-Wire routines Peter Henning) +# +# Prof. Dr. Peter A. Henning, 2012 +# +# Version 2.24 - October, 2012 +# +# Setup bus device in fhem.cfg as +# +# define OWID +# +# where may be replaced by any name string +# +# is a 2 character (1 byte) 1-Wire Family ID +# +# is a 12 character (6 byte) 1-Wire ROM ID +# without Family ID, e.g. A2D90D000800 +# +# get id => FAM_ID.ROM_ID.CRC +# get present => 1 if device present, 0 if not +# +# +######################################################################################## +# +# 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 %gets = ( + "present" => "", + "id" => "" +); +my %sets = (); +my %updates = (); + +######################################################################################## +# +# The following subroutines are independent of the bus interface +# +# Prefix = OWID +# +######################################################################################## +# +# OWID_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWID_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "OWID_Define"; + $hash->{UndefFn} = "OWID_Undef"; + $hash->{GetFn} = "OWID_Get"; + $hash->{SetFn} = undef; + my $attlist = "IODev do_not_notify:0,1 showtime:0,1 loglevel:0,1,2,3,4,5 "; + $hash->{AttrList} = $attlist; +} + +######################################################################################### +# +# OWID_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################### + +sub OWID_Define ($$) { + my ($hash, $def) = @_; + + #-- define OWID + my @a = split("[ \t][ \t]*", $def); + + my ($name,$fam,$id,$crc,$ret); + + #-- default + $name = $a[0]; + $ret = ""; + + #-- check syntax + return "OWID: Wrong syntax, must be define OWID " + if(int(@a) !=4 ); + + #-- check id + if( $a[2] =~ m/^[0-9|a-f|A-F]{2}$/ ) { + $fam = $a[2]; + } else { + return "OWID: $a[0] family id $a[2] invalid, specify a 2 digit value"; + } + if( $a[3] =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $id = $a[3]; + } else { + return "OWID: $a[0] ID $a[3] invalid, specify a 12 digit value"; + } + + #-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY" + # determine CRC Code YY - only if this is a direct interface + $crc = defined($hash->{IODev}->{INTERFACE}) ? sprintf("%02x",OWX_CRC($fam.".".$id."00")) : "00"; + + #-- Define device internals + $hash->{ROM_ID} = $fam.".".$id.$crc; + $hash->{OW_ID} = $id; + $hash->{OW_FAMILY} = $fam; + $hash->{PRESENT} = 0; + + #-- Couple to I/O device + AssignIoPort($hash); + Log 3, "OWID: Warning, no 1-Wire I/O device found for $name." + if(!defined($hash->{IODev}->{NAME})); + + $modules{OWID}{defptr}{$id} = $hash; + + $hash->{STATE} = "Defined"; + Log 3, "OWID: Device $name defined."; + + #-- Initialization reading according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + $hash->{STATE} = "Initialized"; + return undef; +} + +######################################################################################## +# +# OWID_Get - Implements GetFn function +# +# Parameter hash = hash of device addressed, a = argument array +# +######################################################################################## + +sub OWID_Get($@) { + my ($hash, @a) = @_; + + my $reading = $a[1]; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my $value = undef; + my $ret = ""; + my $offset; + my $factor; + + #-- check syntax + return "OWID: Get argument is missing @a" + if(int(@a) != 2); + + #-- check argument + return "OWID: Get with unknown argument $a[1], choose one of ".join(",", sort keys %gets) + if(!defined($gets{$a[1]})); + + #-- get id + if($a[1] eq "id") { + $value = $hash->{ROM_ID}; + return "$name.id => $value"; + } + + #-- get present + if($a[1] eq "present") { + #-- hash of the busmaster + my $master = $hash->{IODev}; + $value = OWX_Verify($master,$hash->{ROM_ID}); + $hash->{PRESENT} = $value; + return "$name.present => $value"; + } +} + +######################################################################################## +# +# OWID_Undef - Implements UndefFn function +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWID_Undef ($) { + my ($hash) = @_; + delete($modules{OWID}{defptr}{$hash->{OW_ID}}); + RemoveInternalTimer($hash); + return undef; +} + +1; diff --git a/fhem/FHEM/21_OWLCD.pm b/fhem/FHEM/21_OWLCD.pm new file mode 100644 index 000000000..2f9c27cfc --- /dev/null +++ b/fhem/FHEM/21_OWLCD.pm @@ -0,0 +1,1037 @@ +######################################################################################## +# +# OWLCD.pm +# +# FHEM module to commmunicate with the 1-Wire LCD hardware +# +# Attention: This module may communicate with the OWX module, +# but currently not with the 1-Wire File System OWFS +# +# Prefixes for subroutines of this module: +# OW = General 1-Wire routines Peter Henning +# +# Prof. Dr. Peter A. Henning, 2012 +# +# Version 2.24 - October, 2012 +# +# Setup bus device in fhem.cfg as +# +# define OWLCD +# +# where may be replaced by any name string +# +# is a 12 character (6 byte) 1-Wire ROM ID +# without Family ID, e.g. A2D90D000800 +# +# get id => FAM_ID.ROM_ID.CRC +# get present => 1 if device present, 0 if not +# get gpio => current state of the gpio pins (15 = all off, 0 = all on) +# get counter => four values (16 Bit) of the gpio counter +# get version => firmware version of the LCD adapter +# get memory => get one of the internal memory pages 0..6 +# +# set alert red|yellow|beep|none => set one of the alert states (gpio pins) +# set icon on|off|blink => set one of the icons 0..14 +# set icon 15 0..6 => set icon no. 15 in one of its values +# set line => set one of the display lines 0..3 +# set memory set one of the internal memory pages 0..6 +# set gpio => state of the gpio pins 0..7 +# set backlight on|off => set backlight on or off +# set lcd on|off => set LCD power on or off +# set reset => reset the display +# set test => display a test content +# +# Careful: Not ASCII ! strange Codepage +######################################################################################## +# +# 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($$); + +#-- controller may be HD44780 or KS0073 +# these values have to be changed for different display +# geometries or memory maps +my $lcdcontroller = "KS0073"; +my $lcdlines = 4; +my $lcdchars = 20; +my @lcdpage = (0,32,64,96); +#my @lcdpage = (0,64,20,84); + +#-- declare variables +my %gets = ( + "present" => "", + "id" => "", + "memory" => "", + "gpio" => "", + "counter" => "", + "version" => "", + #"register" => "", + #"data" => "" +); +my %sets = ( + "icon" => "", + "line" => "", + "alert" => "", + "memory" => "", + "gpio" => "", + "backlight" => "", + "lcd" => "", + "reset" => "", + "test" => "" + +); +my %updates = (); + +######################################################################################## +# +# The following subroutines are independent of the bus interface +# +# Prefix = OWLCD +# +######################################################################################## +# +# OWLCD_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWLCD_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "OWLCD_Define"; + $hash->{UndefFn} = "OWLCD_Undef"; + $hash->{GetFn} = "OWLCD_Get"; + $hash->{SetFn} = "OWLCD_Set"; + my $attlist = "IODev do_not_notify:0,1 showtime:0,1 loglevel:0,1,2,3,4,5 ". + ""; + $hash->{AttrList} = $attlist; +} + +######################################################################################### +# +# OWLCD_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################### + +sub OWLCD_Define ($$) { + my ($hash, $def) = @_; + + #-- define OWLCD + my @a = split("[ \t][ \t]*", $def); + + my ($name,$fam,$id,$crc,$ret); + + #-- default + $name = $a[0]; + $ret = ""; + + #-- check syntax + return "OWLCD: Wrong syntax, must be define OWLCD " + if(int(@a) !=3 ); + + #-- check id + if( $a[2] =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $id = $a[2]; + } else { + return "OWLCD: $a[0] ID $a[2] invalid, specify a 12 digit value"; + } + + #-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY" + # determine CRC Code - only if this is a direct interface + $crc = defined($hash->{IODev}->{INTERFACE}) ? sprintf("%02x",OWX_CRC("FF.".$id."00")) : "00"; + + #-- Define device internals + $hash->{ROM_ID} = "FF.".$id.$crc; + $hash->{OW_ID} = $id; + $hash->{OW_FAMILY} = "FF"; + $hash->{PRESENT} = 0; + + #-- Couple to I/O device + AssignIoPort($hash); + Log 3, "OWLCD: Warning, no 1-Wire I/O device found for $name." + if(!defined($hash->{IODev}->{NAME})); + + $modules{OWLCD}{defptr}{$id} = $hash; + + $hash->{STATE} = "Defined"; + Log 3, "OWLCD: Device $name defined."; + + #-- Initialization reading according to interface type + my $interface= $hash->{IODev}->{TYPE}; + #-- OWX interface + if( $interface eq "OWX" ){ + OWXLCD_InitializeDevice($hash); + #-- set backlight on + OWXLCD_SetFunction($hash,"bklon",0); + #-- erase all icons + OWXLCD_SetIcon($hash,0,0); + #-- erase alarm state + OWXLCD_SetFunction($hash,"gpio",15); + #-- Unknown interface + }else{ + return "OWLCD: Wrong IODev type $interface"; + } + $hash->{STATE} = "Initialized"; + return undef; +} + +######################################################################################## +# +# OWLCD_Get - Implements GetFn function +# +# Parameter hash = hash of device addressed, a = argument array +# +######################################################################################## + +sub OWLCD_Get($@) { + my ($hash, @a) = @_; + + my $reading = $a[1]; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my $value = undef; + my $ret = ""; + my $offset; + my $factor; + + #-- check syntax + return "OWLCD: Get argument is missing @a" + if(int(@a) < 2); + + #-- check argument + return "OWLCD: Get with unknown argument $a[1], choose one of ".join(",", sort keys %gets) + if(!defined($gets{$a[1]})); + + #-- get id + if($a[1] eq "id") { + $value = $hash->{ROM_ID}; + return "$name.id => $value"; + } + + #-- get present + if($a[1] eq "present") { + #-- hash of the busmaster + my $master = $hash->{IODev}; + $value = OWX_Verify($master,$hash->{ROM_ID}); + $hash->{PRESENT} = $value; + return "$name.present => $value"; + } + + #-- get gpio states + if($a[1] eq "gpio") { + $value = OWXLCD_Get($hash,"gpio"); + return "$name.gpio => $value"; + } + + #-- get gpio counters + if($a[1] eq "counter") { + $value = OWXLCD_Get($hash,"counter"); + return "$name.counter => $value"; + } + + #-- get version + if($a[1] eq "version") { + $value = OWXLCD_Get($hash,"version"); + return "$name.version => $value"; + } + + #-- get EEPROM content + if($a[1] eq "memory") { + my $page = ($a[2] =~ m/\d/) ? int($a[2]) : 0; + Log 1,"Calling GetMemory with page $page"; + $value = OWXLCD_GetMemory($hash,$page); + return "$name $reading $page => $value"; + } +} + +####################################################################################### +# +# OWLCD_Set - Set one value for device +# +# Parameter hash = hash of device addressed +# a = argument array +# +######################################################################################## + +sub OWLCD_Set($@) { + my ($hash, @a) = @_; + + my $key = $a[1]; + my $value = $a[2]; + my ($line,$icon,$i); + + #-- for the selector: which values are possible + return join(" ", keys %sets) + if ( (@a == 2) && !(($key eq "reset") || ($key eq "test")) ); + + #-- check argument + if( !defined($sets{$a[1]}) ){ + return "OWLCD: Set with unknown argument $a[1]"; + } + + #-- check syntax for setting line + if( $key eq "line" ){ + return "OWLCD: Set needs one or two parameters when setting line value: <#line> " + if( int(@a)<3 ); + $line = ($a[2] =~ m/\d/) ? $a[2] : 0; + $value = $a[3]; + if( defined($value) ){ + for( $i=4; $i< int(@a); $i++){ + $value .= " ".$a[$i]; + } + }else{ + $value=""; + } + #-- check syntax for setting memory + } elsif( $key eq "memory" ){ + return "OWLCD: Set needs two parameters when setting memory page: <#page> " + if( int(@a)<4 ); + $line = ($a[2] =~ m/\d/) ? int($a[2]) : 0; + $value = $a[3]; + for( $i=4; $i< int(@a); $i++){ + $value .= " ".$a[$i]; + } + #-- check syntax for setting alert + } elsif( $key eq "alert" ){ + return "OWLCD: Set needs a parameter when setting alert: /none/off" + if( int(@a)<3 ); + #-- check syntax for setting icon + } elsif ( $key eq "icon" ){ + if( ($a[2] ne "0") && ($a[2] ne "none") ){ + return "OWLCD: Set needs two parameters when setting icon value: <#icon> on/off/blink (resp. 0..5/off/blink for #16)" + if( (int(@a)!=4) ); + $icon = ($a[2] =~ m/\d\d?/) ? $a[2] : 0; + $value = $a[3]; + } else { + return "OWLCD: Set needs only one parameter when resetting icons" + if( (int(@a)!=3) ); + $icon = 0; + $value = "OFF"; + } + #-- check syntax for reset and test + } elsif ( ($key eq "reset") || ($key eq "test") ){ + return "OWLCD: Set needs no parameters when setting $key value" + if( int(@a)!=2 ); + #-- other syntax + } else { + return "OWLCD: Set needs one parameter when setting $key value" + if( int(@a)!=3 ); + } + + #-- define vars + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + + #-- set gpio ports from all off = to all on = 7 + if($key eq "gpio") { + #-- check value and write to device + return "OWLCD: Set with wrong value for gpio port, must be 0 <= gpio <= 7" + if( ! ((int($value) >= 0) && (int($value) <= 7)) ); + OWXLCD_SetFunction($hash, "gpio", int($value)); + return undef; + } + + #-- set LCD ON or OFF + if($key eq "lcd") { + #-- check value and write to device + if( uc($value) eq "ON"){ + OWXLCD_SetFunction($hash, "lcdon", 0); + }elsif( uc($value) eq "OFF" ){ + OWXLCD_SetFunction($hash, "lcdoff", 0); + } else { + return "OWLCD: Set with wrong value for lcd, must be on/off" + } + return undef; + } + + #-- set LCD Backlight ON or OFF + if($key eq "backlight") { + #-- check value and write to device + if( uc($value) eq "ON"){ + OWXLCD_SetFunction($hash, "bklon", 0); + }elsif( uc($value) eq "OFF" ){ + OWXLCD_SetFunction($hash, "bkloff", 0); + } else { + return "OWLCD: Set with wrong value for backlight, must be on/off" + } + return undef; + } + + #-- reset + if($key eq "reset") { + OWXLCD_SetFunction($hash,"reset",0); + OWXLCD_SetIcon($hash,0,0); + OWXLCD_SetFunction($hash,"gpio",15); + return undef; + } + + #-- set icon + if($key eq "icon") { + return "OWLCD: Wrong icon type, choose 0..16" + if( ( 0 > $icon ) || ($icon > 16) ); + #-- check value and write to device + if( $icon == 16 ){ + if( uc($value) eq "OFF" ){ + OWXLCD_SetIcon($hash, 16, 0); + }elsif( uc($value) eq "BLINK" ){ + OWXLCD_SetIcon($hash, 16, 6); + }elsif( ((int($value) > 0) && (int($value) < 6)) ){ + OWXLCD_SetIcon($hash, 16, int($value)); + } else { + return "OWLCD: Set with wrong value for icon #16, must be 0..5/off/blink" + } + }else{ + if( uc($value) eq "OFF"){ + OWXLCD_SetIcon($hash, $icon, 0); + }elsif( uc($value) eq "ON" ){ + OWXLCD_SetIcon($hash, $icon, 1); + }elsif( uc($value) eq "BLINK" ){ + OWXLCD_SetIcon($hash, $icon, 2); + } else { + return "OWLCD: Set with wrong value for icon $icon, must be on/off/blink" + } + } + return undef; + } + + #-- set a single LCD line + if($key eq "line") { + return "OWLCD: Wrong line number, choose 0..".$lcdlines + if( ( 0 > $line ) || ($line > ($lcdlines-1)) ); + return "OWLCD: Wrong line length, must be < ".$lcdchars + if( length($value) > $lcdchars ); + #-- check value and write to device + OWXLCD_SetLine($hash,$line,$value); + return undef; + } + + #-- set memory page 0..6 + if($key eq "memory") { + return "OWLCD: Wrong page number, choose 0..6" + if( (0 > $line) || ($line > 6) ); + return "OWLCD: Wrong line length, must be <=16 " + if( length($value) > 16 ); + #-- check value and write to device + Log 1,"Calling SetMemory with page $line"; + OWXLCD_SetMemory($hash,$line,$value); + return undef; + } + + #-- set alert + if($key eq "alert") { + if(lc($value) eq "beep") { + OWXLCD_SetFunction($hash,"gpio",14); + return undef; + }elsif(lc($value) eq "red") { + OWXLCD_SetFunction($hash,"gpio",13); + return undef; + }elsif(lc($value) eq "yellow") { + OWXLCD_SetFunction($hash,"gpio",11); + return undef; + }elsif( (lc($value) eq "off") || (lc($value) eq "none") ) { + OWXLCD_SetFunction($hash,"gpio",15); + return undef; + }else{ + return "OWLCD: Set with wrong value for alert type, must be beep/red/yellow/off"; + } + } + + #-- start test + if($key eq "test") { + OWXLCD_SetLine($hash,0,"Hallo Welt"); + OWXLCD_SetLine($hash,1,"Mary had a big lamb"); + OWXLCD_SetLine($hash,2,"Solar 4.322 kW "); + OWXLCD_SetLine($hash,3,"\x5B\x5C\x5E\x7B\x7C\x7E\xBE"); + return undef; + } +} + +######################################################################################## +# +# OWLCD_Undef - Implements UndefFn function +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWLCD_Undef ($) { + my ($hash) = @_; + delete($modules{OWLCD}{defptr}{$hash->{OW_ID}}); + RemoveInternalTimer($hash); + return undef; +} + +######################################################################################## +# +# OWXLCD_Byte - write a single byte to the LCD device +# +# Parameter hash = hash of device addressed +# cmd = register or data +# byte = byte +# +######################################################################################## + +sub OWXLCD_Byte($$$) { + + my ($hash,$cmd,$byte) = @_; + + my ($select, $select2, $res, $res2, $res3, @data); + + #-- 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); + + #=============== write to LCD register =============================== + if ( $cmd eq "register" ) { + #-- issue the read LCD register command \x10 + $select = sprintf("\x10%c",$byte); + #=============== write to LCD data =============================== + }elsif ( $cmd eq "data" ) { + #-- issue the read LCD data command \x12 + $select = sprintf("\x12%c",$byte); + #=============== wrong value requested =============================== + } else { + return "OWXLCD: Wrong byte write attempt"; + } + + #-- write to device + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + #-- process results + if( $res eq 0 ){ + return "OWLCD: Device $owx_dev not accessible for writing a byte"; + } + + return undef; +} + +######################################################################################## +# +# OWXLCD_Get - get values from the LCD device +# +# Parameter hash = hash of device addressed +# cmd = command string +# +######################################################################################## + +sub OWXLCD_Get($$) { + + my ($hash,$cmd,$value) = @_; + + my ($select, $select2, $len, $addr, $res, $res2, $res3, @data); + + #-- 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); + + #=============== fill scratch with gpio ports =============================== + if ( $cmd eq "gpio" ) { + #-- issue the read GPIO command \x22 (1 byte) + $select = "\x22"; + $len = 1; + #=============== fill scratch with gpio counters =============================== + }elsif ( $cmd eq "counter" ) { + #-- issue the read counter command \x23 (8 bytes) + $select = "\x23"; + $len = 8; + #=============== fill scratch with version =============================== + }elsif ( $cmd eq "version" ) { + #-- issue the read version command \x41 + $select = "\x41"; + $len = 16; + } else { + return "OWXLCD: Wrong get attempt"; + } + #-- write to device + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + + #-- process results + if( $res eq 0 ){ + return "OWLCD: Device $owx_dev not accessible for reading"; + } + + #-- issue the read scratchpad command \xBE + $select2 = "\xBE"; + #-- write to device + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select2,$len); + + #-- process results + if( $res eq 0 ){ + return "OWLCD: Device $owx_dev not accessible for reading in 2nd step"; + } + + #-- process results (10 byes or more have been sent) + $res = substr($res,10); + + #=============== gpio ports =============================== + if ( $cmd eq "gpio" ) { + return ord($res); + #=============== gpio counters =============================== + }elsif ( $cmd eq "counter" ) { + for( $i=0; $i<4; $i++){ + $data[$i] = ord(substr($res,2*$i+1,1))*256+ord(substr($res,2*$i,1)); + } + return join(" ",@data); + #=============== version =============================== + }elsif ( $cmd eq "version" ) { + return $res; + } + + return $res; +} + +######################################################################################## +# +# OWXLCD_GetMemory - get memory page from LCD device (EXPERIMENTAL) +# +# Parameter hash = hash of device addressed +# page = memory page address +# +######################################################################################## + +sub OWXLCD_GetMemory($$) { + + my ($hash,$page) = @_; + + my ($select, $res, $res2, $res3); + + #-- 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); + + #-- issue the match ROM command \x55 and the copy eeprom to scratchpad command \x4E + #Log 1," page read is ".$page; + $select = sprintf("\4E%c\x10\x37",$page); + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + + #-- process results + if( $res eq 0 ){ + return "OWLCD: Device $owx_dev not accessible for reading"; + } + + #-- sleeping for some time + #select(undef,undef,undef,0.5); + + #-- issue the match ROM command \x55 and the read scratchpad command \xBE + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,"\xBE",16); + + #-- process results + if( $res eq 0 ){ + return "OWLCD: Device $owx_dev not accessible for reading in 2nd step"; + } + + #-- process results (10 byes or more have been sent) + $res2 = substr($res,11,16); + + Log 1," Having received ".length($res)." bytes"; + return $res2; +} + +######################################################################################## +# +# OWXLCD_InitializeDevice - initialize the display +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWXLCD_InitializeDevice($) { + my ($hash) = @_; + + my ($i,$data,$select, $res); + + #-- 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}; + + #-- supposedly we do not need to do anything with a HD44780 + if( $lcdcontroller eq "HD44780"){ + return undef; + #-- need some additional sequence for KS0073 + }elsif ( $lcdcontroller eq "KS0073"){ + + #-- Function Set: 4 bit data size, RE => 0 = \x20 + #OWXLCD_Byte($hash,"register",32); + + #-- Entry Mode Set: cursor auto increment = \x06 + #OWXLCD_Byte($hash,"register",6); + + #-- Function Set: 4 bit data size, RE => 1, blink Enable = \x26 + OWXLCD_Byte($hash,"register",38); + + #-- Ext. Function Set: 4 line mode = \x09 + OWXLCD_Byte($hash,"register",9); + + #-- Function Set: 4 bit data size, RE => 0 = \x20 + OWXLCD_Byte($hash,"register",32); + + #-- Display ON/OFF: display on, cursor off, blink off = \x0C + OWXLCD_Byte($hash,"register",12); + + #-- Clear Display + OWXLCD_Byte($hash,"register",1); + + return undef; + #-- or else + } else { + return "OWXLCD: Wrong LCD controller type"; + } + +} + +######################################################################################## +# +# OWXLCD_SetFunction - write state and values of the LCD device +# +# Parameter hash = hash of device addressed +# cmd = command string +# value = data value +# +######################################################################################## + +sub OWXLCD_SetFunction($$$) { + + my ($hash,$cmd,$value) = @_; + + my ($select, $res, $res2, $res3, @data); + + #-- ID of the device, hash of the busmaster + my $owx_dev = $hash->{ROM_ID}; + my $master = $hash->{IODev}; + + my ($i,$j,$k); + + #=============== set gpio ports =============================== + if ( $cmd eq "gpio" ) { + #-- issue the write GPIO command + # \x21 followed by the data value (= integer 0 - 7) + $select = sprintf("\x21%c",$value); + #=============== switch LCD on =============================== + }elsif ( $cmd eq "lcdon" ) { + #-- issue the lcd on cmd + $select = "\x03"; + #=============== switch LCD off =============================== + }elsif ( $cmd eq "lcdoff" ) { + #-- issue the lcd off cmd + $select = "\x05"; + #=============== switch LCD backlight on =============================== + }elsif ( $cmd eq "bklon" ) { + #-- issue the backlight on cmd + $select = "\x08"; + #=============== switch LCD backlight off =============================== + }elsif ( $cmd eq "bkloff" ) { + #-- issue the backlight off cmd + $select = "\x07"; + #=============== switch LCD backlight off =============================== + }elsif ( $cmd eq "reset" ) { + #-- issue the clear LCD command + $select = "\x49"; + #=============== wrong write attempt =============================== + } else { + return "OWXLCD: Wrong function selected"; + } + + #-- write to device + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + #-- process results + if( $res eq 0 ){ + return "OWLCD: Device $owx_dev not accessible for writing"; + } + + return undef; +} + +######################################################################################## +# +# OWXLCD_SetIcon - set one of the icons +# +# Parameter hash = hash of device addressed +# icon = address of the icon used = 0,1 .. 16 (0 = all off) +# value = data value: 0 = off, 1 = on, 2 = blink +# for battery icon 16: 0 = off, 1 = empty ... 5 = full, 6 = empty blink +# +######################################################################################## + +sub OWXLCD_SetIcon($$$) { + my ($hash,$icon,$value) = @_; + + my ($i,$data,$select, $res); + + #-- ID of the device, hash of the busmaster + my $owx_dev = $hash->{ROM_ID}; + my $master = $hash->{IODev}; + + #-- only for KS0073 + if ( $lcdcontroller eq "KS0073"){ + + #-- write 16 zeros to erase all icons + if( $icon == 0){ + #-- 4 bit data size, RE => 1, blink Enable = \x26 + $select = "\x10\x26"; + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + + #-- SEGRAM addres to 0 = \x40, + $select = "\x10\x40"; + #-- write 16 zeros to scratchpad + $select .= "\x4E\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"; + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + + #-- issue the copy scratchpad to LCD command \x48 + $select="\x48"; + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + } else { + #-- determine data value + if( int($icon) != 16 ){ + if( $value == 0 ){ + $data = 0; + } elsif ( $value == 1) { + $data = 16; + } elsif ( $value == 2) { + $data = 80; + } else { + return "OWXLCD: Wrong data value $value for icon $icon"; + } + } else { + if( $value == 0 ){ + $data = 0; + } elsif ( $value == 1) { + $data = 16; + } elsif ( $value == 2) { + $data = 24; + } elsif ( $value == 3) { + $data = 28; + } elsif ( $value == 4) { + $data = 30; + } elsif ( $value == 5) { + $data = 31; + } elsif ( $value == 6) { + $data = 80; + } else { + return "OWXLCD: Wrong data value $value for icon $icon"; + } + } + #-- 4 bit data size, RE => 1, blink Enable = \x26 + $select = "\x10\x26"; + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + + #-- SEGRAM addres to 0 = \x40 + icon address + $select = sprintf("\x10%c",63+$icon); + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + + #-- data + $select = sprintf("\x12%c",$data); + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + } + + #-- return to normal state + $select = "\x10\x20"; + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + #-- or else + } else { + return "OWXLCD: Wrong LCD controller type"; + } +} + +######################################################################################## +# +# OWXLCD_SetLine - set one of the display lines +# +# Parameter hash = hash of device addressed +# line = line number (0..3) +# msg = data string to be written +# +######################################################################################## + +sub OWXLCD_SetLine($$$) { + + my ($hash,$line,$msg) = @_; + + my ($select, $res, $res2, $res3, $i, $msgA, $msgB); + $res2 = ""; + $line = int($line); + $msg = defined($msg) ? $msg : ""; + #-- replace umlaut chars for special codepage + $msg =~ s/ä/\x7B/g; + $msg =~ s/ö/\x7C/g; + $msg =~ s/ü/\x7E/g; + $msg =~ s/Ä/\x5B/g; + $msg =~ s/Ö/\x5C/g; + $msg =~ s/Ü/\x5E/g; + $msg =~ s/ß/\xBE/g; + + #--take out degree sign + if( $msg =~ m/.*\°\;.*/ ) { + my @ma = split(/\°\;/,$msg); + $msg = $ma[0]."\x80".$ma[1]; + } + #-- ID of the device, hash of the busmaster + my $owx_dev = $hash->{ROM_ID}; + my $master = $hash->{IODev}; + + #-- split if longer than 16 bytes, fill each with blanks + # has already been checked to be <= $lcdchars + if( $lcdchars > 16 ){ + if( length($msg) > 16 ) { + $msgA = substr($msg,0,16); + $msgB = substr($msg,16,length($msg)-16); + for($i = 0;$i<$lcdchars-length($msg);$i++){ + $msgB .= "\x20"; + } + } else { + $msgA = $msg; + for($i = 0;$i<16-length($msg);$i++){ + $msgA .= "\x20"; + } + for($i = 0;$i<$lcdchars-16;$i++){ + $msgB .= "\x20"; + } + } + }else{ + $msgA = $msg; + for($i = 0;$i<$lcdchars-length($msg);$i++){ + $msgA .= "\x20"; + } + $msgB = undef; + } + + #-- issue the match ROM command \x55 and the write scratchpad command \x4E + # followed by LCD page address and the text + $select=sprintf("\x4E%c",$lcdpage[$line]).$msgA; + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + + #-- issue the copy scratchpad to LCD command \x48 + $select="\x48"; + OWX_Reset($master); + $res3=OWX_Complex($master,$owx_dev,$select,0); + + #-- if second string available: + if( defined($msgB) ) { + #select(undef,undef,undef,0.005); + #-- issue the match ROM command \x55 and the write scratchpad command \x4E + # followed by LCD page address and the text + $select=sprintf("\x4E%c",$lcdpage[$line]+16).$msgB; + OWX_Reset($master); + $res2=OWX_Complex($master,$owx_dev,$select,0); + + #-- issue the copy scratchpad to LCD command \x48 + $select="\x48"; + OWX_Reset($master); + $res3=OWX_Complex($master,$owx_dev,$select,0); + } + + #-- process results + if( ($res eq 0) || ($res2 eq 0) || ($res3 eq 0) ){ + return "OWLCD: Device $owx_dev not accessible for writing"; + } + + return undef; + +} + +######################################################################################## +# +# OWXLCD_SetMemory - set internal nonvolatile memory +# +# Parameter hash = hash of device addressed +# page = page number (0..14) +# msg = data string to be written +# +######################################################################################## + +sub OWXLCD_SetMemory($$$) { + + my ($hash,$page,$msg) = @_; + + my ($select, $res, $res2, $res3, $i, $msgA); + $page = int($page); + $msg = defined($msg) ? $msg : ""; + + #-- ID of the device, hash of the busmaster + my $owx_dev = $hash->{ROM_ID}; + my $master = $hash->{IODev}; + + #-- fillup with blanks + $msgA = $msg; + for($i = 0;$i<16-length($msg);$i++){ + $msgA .= "\x20"; + } + + #-- issue the match ROM command \x55 and the write scratchpad command \x4E + # followed by LCD page address and the text + #Log 1," page written is ".$page; + $select=sprintf("\x4E\%c",$page).$msgA; + OWX_Reset($master); + $res=OWX_Complex($master,$owx_dev,$select,0); + + #-- issue the copy scratchpad to EEPROM command \x39 + OWX_Reset($master); + $res2=OWX_Complex($master,$owx_dev,"\x39",0); + + #-- process results + if( ($res eq 0) || ($res2 eq 0) ){ + return "OWLCD: Device $owx_dev not accessible for writing"; + } + + return undef; + +} + +1; diff --git a/fhem/FHEM/21_OWMULTI.pm b/fhem/FHEM/21_OWMULTI.pm new file mode 100644 index 000000000..1807c5819 --- /dev/null +++ b/fhem/FHEM/21_OWMULTI.pm @@ -0,0 +1,850 @@ +######################################################################################## +# +# OWMULTI.pm +# +# FHEM module to commmunicate with 1-Wire chip DS2438Z - Smart Battery Monitor +# +# Prefixes for subroutines of this module: +# OW = General 1-Wire routines (Martin Fischer, Peter Henning) +# OWX = 1-Wire bus master interface (Peter Henning) +# +# Prof. Dr. Peter A. Henning, 2012 +# +# Version 2.24 - October, 2012 +# +# Setup bus device in fhem.cfg as +# +# define OWMULTI [] [interval] +# +# where may be replaced by any name string +# +# is a 1-Wire device type. If omitted, we assume this to be an +# DS2438 +# +# is a 12 character (6 byte) 1-Wire ROM ID +# without Family ID, e.g. A2D90D000800 +# [interval] is an optional query interval in seconds +# +# get id => OW_FAMILY.ROM_ID.CRC +# get present => 1 if device present, 0 if not +# get interval => query interval +# get reading => measurement value obtained from VFunction +# get temperature => temperature measurement +# get VDD => supply voltage measurement +# get V|raw => raw external voltage measurement +# +# set interval => set period for measurement +# +# Additional attributes are defined in fhem.cfg +# Note: attributes "tempXXXX" are read during every update operation. +# +# attr event on-change/on-update = when to write an event (default= on-update) +# +# attr tempOffset = temperature offset in degree Celsius added to the raw temperature reading +# attr tempUnit = unit of measurement, e.g. Celsius/Kelvin/Fahrenheit or C/K/F, default is Celsius +# attr VName | = name for the channel | a type description for the measured value +# attr VUnit | = unit of measurement for the voltage channel | its abbreviation +# attr Vfunction = arbitrary functional expression involving the values VDD, V, T +# VDD is replaced by the measured supply voltage in Volt, +# V by the measured external voltage +# T by the measured and corrected temperature in its unit +# +######################################################################################## +# +# 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($$); + +#-- temperature and voltage globals - always the raw values from the device +my $owg_temp; +my $owg_volt; +my $owg_vdd; +my $owg_channel; + +my %gets = ( + "id" => "", + "present" => "", + "interval" => "", + "reading" => "", + "temperature" => "", + "VDD" => "", + "V" => "", + "raw" => "", +); + +my %sets = ( + "interval" => "", +); + +my %updates = ( + "present" => "", + "reading" => "", +); + +######################################################################################## +# +# The following subroutines are independent of the bus interface +# +# Prefix = OWMULTI +# +######################################################################################## +# +# OWMULTI_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWMULTI_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "OWMULTI_Define"; + $hash->{UndefFn} = "OWMULTI_Undef"; + $hash->{GetFn} = "OWMULTI_Get"; + $hash->{SetFn} = "OWMULTI_Set"; + #tempOffset = a temperature offset added to the temperature reading for correction + #tempUnit = a unit of measure: C/F/K + $hash->{AttrList}= "IODev do_not_notify:0,1 showtime:0,1 loglevel:0,1,2,3,4,5 ". + "event:on-update,on-change ". + "tempOffset tempUnit:C,Celsius,F,Fahrenheit,K,Kelvin ". + "VName VUnit VFunction"; + } + +######################################################################################## +# +# OWMULTI_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################## + +sub OWMULTI_Define ($$) { + my ($hash, $def) = @_; + + # define OWMULTI [] [interval] + # e.g.: define flow OWMULTI 525715020000 300 + my @a = split("[ \t][ \t]*", $def); + + my ($name,$model,$fam,$id,$crc,$interval,$ret); + my $tn = TimeNow(); + + #-- default + $name = $a[0]; + $interval = 300; + $ret = ""; + + #-- check syntax + return "OWMULTI: Wrong syntax, must be define OWMULTI [] [interval]" + if(int(@a) < 2 || int(@a) > 6); + + #-- check if this is an old style definition, e.g. is missing + my $a2 = $a[2]; + my $a3 = defined($a[3]) ? $a[3] : ""; + if( ($a2 eq "none") || ($a3 eq "none") ) { + return "OWMULTI: ID = none is obsolete now, please redefine"; + } elsif( $a2 =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $model = "DS2438"; + $id = $a[2]; + if(int(@a)>=4) { $interval = $a[3]; } + Log 1, "OWMULTI: Parameter [alarminterval] is obsolete now - must be set with I/O-Device" + if(int(@a) == 5); + } elsif( $a3 =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $model = $a[2]; + $id = $a[3]; + if(int(@a)>=5) { $interval = $a[4]; } + Log 1, "OWMULTI: Parameter [alarminterval] is obsolete now - must be set with I/O-Device" + if(int(@a) == 6); + } else { + return "OWMULTI: $a[0] ID $a[2] invalid, specify a 12 digit value"; + } + + #-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY" + # FF = family id follows from the model + # YY must be determined from id + if( $model eq "DS2438" ){ + $fam = "26"; + }else{ + return "OWMULTI: Wrong 1-Wire device model $model"; + } + # determine CRC Code - only if this is a direct interface + $crc = defined($hash->{IODev}->{INTERFACE}) ? sprintf("%02x",OWX_CRC($fam.".".$id."00")) : "00"; + + #-- define device internals + $hash->{OW_ID} = $id; + $hash->{OW_FAMILY} = $fam; + $hash->{PRESENT} = 0; + $hash->{ROM_ID} = $fam.".".$id.$crc; + $hash->{INTERVAL} = $interval; + + #-- Couple to I/O device + AssignIoPort($hash); + Log 3, "OWMULTI: Warning, no 1-Wire I/O device found for $name." + if(!defined($hash->{IODev}->{NAME})); + $modules{OWMULTI}{defptr}{$id} = $hash; + $hash->{STATE} = "Defined"; + Log 3, "OWMULTI: Device $name defined."; + + #-- Start timer for initialization in a few seconds + InternalTimer(time()+10, "OWMULTI_InitializeDevice", $hash, 0); + + #-- Start timer for updates + InternalTimer(time()+$hash->{INTERVAL}, "OWMULTI_GetValues", $hash, 0); + + return undef; +} + +######################################################################################## +# +# OWMULTI_InitializeDevice - delayed setting of initial readings and channel names +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWMULTI_InitializeDevice($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + my @args; + + #-- unit attribute defined ? + $hash->{READINGS}{"temperature"}{UNIT} = defined($attr{$name}{"tempUnit"}) ? $attr{$name}{"tempUnit"} : "Celsius"; + $hash->{READINGS}{"temperature"}{TYPE} = "temperature"; + + #-- Initial readings temperature sensor + $owg_temp = 0.0; + $owg_volt = 0.0; + $owg_vdd = 5.0; + #-- Set channel name, channel unit for voltage channel + my $cname = defined($attr{$name}{"VName"}) ? $attr{$name}{"VName"} : "voltage|voltage"; + my @cnama = split(/\|/,$cname); + if( int(@cnama)!=2){ + Log 1, "OWMULTI: Incomplete channel name specification $cname. Better use $cname|"; + push(@cnama,"unknown"); + } + + #-- unit + my $unit = defined($attr{$name}{"VUnit"}) ? $attr{$name}{"VUnit"} : "Volt|V"; + my @unarr= split(/\|/,$unit); + if( int(@unarr)!=2 ){ + Log 1, "OWMULTI: Incomplete channel unit specification $unit. Better use $unit|"; + push(@unarr,""); + } + + #-- put into readings + $owg_channel = $cnama[0]; + $hash->{READINGS}{"$owg_channel"}{TYPE} = $cnama[1]; + $hash->{READINGS}{"$owg_channel"}{UNIT} = $unarr[0]; + $hash->{READINGS}{"$owg_channel"}{UNITABBR} = $unarr[1]; + + #-- Initialize all the display stuff + OWMULTI_FormatValues($hash); + +} + +######################################################################################## +# +# OWMULTI_FormatValues - put together various format strings +# +# Parameter hash = hash of device addressed, fs = format string +# +######################################################################################## + +sub OWMULTI_FormatValues($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + my ($tunit,$toffset,$tfactor,$tabbr,$tval,$vfunc,$vval); + my ($value1,$value2) = ("",""); + + my $tn = TimeNow(); + + #-- attributes defined ? + $tunit = defined($attr{$name}{"tempUnit"}) ? $attr{$name}{"tempUnit"} : $hash->{READINGS}{"temperature"}{UNIT}; + $toffset = defined($attr{$name}{"tempOffset"}) ? $attr{$name}{"tempOffset"} : 0.0 ; + $tfactor = 1.0; + + if( $tunit eq "Celsius" ){ + $tabbr = "°C"; + } elsif ($tunit eq "Kelvin" ){ + $tabbr = "K"; + $toffset += "273.16" + } elsif ($tunit eq "Fahrenheit" ){ + $tabbr = "°F"; + $toffset = ($toffset+32)/1.8; + $tfactor = 1.8; + } else { + $tabbr="?"; + Log 1, "OWMULTI_FormatValues: unknown unit $tunit"; + } + #-- these values are rather coplex to obtain, therefore save them in the hash + $hash->{READINGS}{"temperature"}{UNIT} = $tunit; + $hash->{READINGS}{"temperature"}{UNITABBR} = $tabbr; + $hash->{tempf}{offset} = $toffset; + $hash->{tempf}{factor} = $tfactor; + + #-- correct values for proper offset, factor + $tval = ($owg_temp + $toffset)*$tfactor; + + #-- put into READINGS + $hash->{READINGS}{"temperature"}{VAL} = $tval; + $hash->{READINGS}{"temperature"}{TIME} = $tn; + + my $cname = defined($attr{$name}{"VName"}) ? $attr{$name}{"VName"} : "voltage|voltage"; + my @cnama = split(/\|/,$cname); + $owg_channel=$cnama[0]; + + #-- attribute VFunction defined ? + $vfunc = defined($attr{$name}{"VFunction"}) ? $attr{$name}{"VFunction"} : "V"; + + #-- replace by proper values + $vfunc =~ s/VDD/\$owg_vdd/g; + $vfunc =~ s/V/\$owg_volt/g; + $vfunc =~ s/T/\$tval/g; + + #-- determine the measured value from the function + $vfunc = "\$owg_vdd = $owg_vdd; \$owg_volt = $owg_volt; \$tval = $tval; ".$vfunc; + $vfunc = eval($vfunc); + if( $vfunc ne "" ){ + $vval = int( $vfunc*1000 )/1000; + } else { + $vval = 0.0; + } + + #-- put into READINGS + $hash->{READINGS}{"$owg_channel"}{VAL} = $vval; + $hash->{READINGS}{"$owg_channel"}{TIME} = $tn; + $hash->{READINGS}{"VDD"}{VAL} = $owg_vdd; + $hash->{READINGS}{"VDD"}{TIME} = $tn; + + #-- string buildup for return value, STATE + $value1 .= sprintf( "%s: %5.3f %s temperature %5.3f %s VDD %5.2f V", $owg_channel, $vval,$hash->{READINGS}{"$owg_channel"}{UNITABBR},$tval,$tabbr,$owg_vdd); + $value2 .= sprintf( "%s: %5.2f %s (T: %5.2f %s)", $owg_channel, $vval,$hash->{READINGS}{"$owg_channel"}{UNITABBR},$tval,$tabbr); + + #-- STATE + $hash->{STATE} = $value2; + + return $value1; +} + +######################################################################################## +# +# OWMULTI_Get - Implements GetFn function +# +# Parameter hash = hash of device addressed, a = argument array +# +######################################################################################## + +sub OWMULTI_Get($@) { + my ($hash, @a) = @_; + + my $reading = $a[1]; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my $value = undef; + my $ret = ""; + + #-- check syntax + return "OWMULTI: Get argument is missing @a" + if(int(@a) != 2); + + #-- check argument + return "OWMULTI: Get with unknown argument $a[1], choose one of ".join(",", sort keys %gets) + if(!defined($gets{$a[1]})); + + #-- get id + if($a[1] eq "id") { + $value = $hash->{ROM_ID}; + return "$name.id => $value"; + } + + #-- Get other values according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- get present + if($a[1] eq "present" ) { + #-- OWX interface + if( $interface eq "OWX" ){ + #-- hash of the busmaster + my $master = $hash->{IODev}; + $value = OWX_Verify($master,$hash->{ROM_ID}); + $hash->{PRESENT} = $value; + return "$name.present => $value"; + } else { + return "OWMULTI: Verification not yet implemented for interface $interface"; + } + } + + #-- get interval + if($reading eq "interval") { + $value = $hash->{INTERVAL}; + return "$name.interval => $value"; + } + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- OWX interface + if( $interface eq "OWX" ){ + #-- not different from getting all values .. + $ret = OWXMULTI_GetValues($hash); + #-- OWFS interface not yet implemented + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSMULTI_GetValues($hash); + #-- Unknown interface + }else{ + return "OWMULTI: Get with wrong IODev type $interface"; + } + + #-- process results + if( defined($ret) ){ + return "OWMULTI: Could not get values from device $name, return was $ret"; + } + $hash->{PRESENT} = 1; + OWMULTI_FormatValues($hash); + + #-- return the special reading + if ($reading eq "reading") { + return "OWMULTI: $name.reading => ". + $hash->{READINGS}{"$owg_channel"}{VAL}; + } + if ($reading eq "temperature") { + return "OWMULTI: $name.temperature => ". + $hash->{READINGS}{"temperature"}{VAL}; + } + if ($reading eq "VDD") { + return "OWMULTI: $name.VDD => ". + $hash->{READINGS}{"VDD"}{VAL}; + } + if ( ($reading eq "V")|($reading eq "raw")) { + return "OWMULTI: $name.V => ". + $owg_volt; + } + return undef; +} + +####################################################################################### +# +# OWMULTI_GetValues - Updates the readings from device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWMULTI_GetValues($@) { + my $hash = shift; + + my $name = $hash->{NAME}; + my $value = ""; + my $ret = ""; + + #-- restart timer for updates + RemoveInternalTimer($hash); + InternalTimer(time()+$hash->{INTERVAL}, "OWMULTI_GetValues", $hash, 1); + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- Get values according to interface type + my $interface= $hash->{IODev}->{TYPE}; + if( $interface eq "OWX" ){ + #-- max 3 tries + for(my $try=0; $try<3; $try++){ + $ret = OWXMULTI_GetValues($hash); + last + if( !defined($ret) ); + } + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSTHERM_GetValues($hash); + }else{ + Log 3, "OWMULTI: GetValues with wrong IODev type $interface"; + return 1; + } + + #-- process results + if( defined($ret) ){ + Log 3, "OWMULTI: Could not get values from device $name, reason $ret"; + return 1; + } + $hash->{PRESENT} = 1; + + #-- old state, new state + my $oldval = $hash->{STATE}; + $value=OWMULTI_FormatValues($hash); + my $newval = $hash->{STATE}; + #--logging depends on setting of the event-attribute + Log 5, $value; + my $ev = defined($attr{$name}{"event"}) ? $attr{$name}{"event"} : "on-update"; + if( ($ev eq "on-update") || (($ev eq "on-change") && ($newval ne $oldval)) ){ + $hash->{CHANGED}[0] = $value; + DoTrigger($name, undef); + } + + return undef; +} + +####################################################################################### +# +# OWMULTI_Set - Set one value for device +# +# Parameter hash = hash of device addressed +# a = argument string +# +######################################################################################## + +sub OWMULTI_Set($@) { + my ($hash, @a) = @_; + + #-- for the selector: which values are possible + return join(" ", sort keys %sets) if(@a == 2); + #-- check syntax + return "OWMULTI: Set needs one parameter" + if(int(@a) != 3); + #-- check argument + return "OWMULTI: 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; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + + #-- set new timer interval + if($key eq "interval") { + # check value + return "OWMULTI: Set with short interval, must be > 1" + if(int($value) < 1); + # update timer + $hash->{INTERVAL} = $value; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWMULTI_GetValues", $hash, 1); + return undef; + } + + #-- set other values depending on interface type + my $interface = $hash->{IODev}->{TYPE}; + my $offset = $hash->{tempf}{offset}; + my $factor = $hash->{tempf}{factor}; + + #-- find upper and lower boundaries for given offset/factor + my $mmin = (-55+$offset)*$factor; + my $mmax = (125+$offset)*$factor; + return sprintf("OWMULTI: Set with wrong value $value for $key, range is [%3.1f,%3.1f]",$mmin,$mmax) + if($value < $mmin || $value > $mmax); + + #-- seems to be ok, put into the device + $a[2] = int($value/$factor-$offset); + + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXMULTI_SetValues($hash,@a); + #-- OWFS interface not yet implemented + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSTHERM_SetValues($hash,@a); + # return $ret + # if(defined($ret)); + } else { + return "OWMULTI: Set with wrong IODev type $interface"; + } + + #-- process results - we have to reread the device + $hash->{PRESENT} = 1; + OWMULTI_GetValues($hash); + OWMULTI_FormatValues($hash); + Log 4, "OWMULTI: Set $hash->{NAME} $key $value"; + + return undef; +} + +######################################################################################## +# +# OWMULTI_Undef - Implements UndefFn function +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWMULTI_Undef ($) { + my ($hash) = @_; + + delete($modules{OWMULTI}{defptr}{$hash->{OW_ID}}); + RemoveInternalTimer($hash); + return undef; +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a 1-Wire bus connected +# directly to the FHEM server +# +# Prefix = OWXMULTI +# +######################################################################################## +# +# OWXMULTI_GetValues - Get reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWXMULTI_GetValues($) { + + my ($hash) = @_; + + my ($i,$j,$k,$res,$res2); + + #-- ID of the device + my $owx_dev = $hash->{ROM_ID}; + #-- hash of the busmaster + my $master = $hash->{IODev}; + + #-- switch the device to current measurement off, VDD only + OWX_Reset($master); + #-- issue the match ROM command \x55 and the write scratchpad command + if( OWX_Complex($master,$owx_dev,"\x4E\x00\x08",0) eq 0 ){ + return "$owx_dev write status failed"; + } + + #-- copy scratchpad to register + OWX_Reset($master); + #-- issue the match ROM command \x55 and the copy scratchpad command + if( OWX_Complex($master,$owx_dev,"\x48\x00",0) eq 0){ + return "$owx_dev copy scratchpad failed"; + } + + #-- initiate temperature conversion + OWX_Reset($master); + #-- issue the match ROM command \x55 and the start conversion command + if( OWX_Complex($master,$owx_dev,"\x44",0) eq 0 ){ + return "$owx_dev temperature conversion failed"; + } + #-- conversion needs some 10 ms ! + select(undef,undef,undef,0.012); + + #-- initiate voltage conversion + OWX_Reset($master); + #-- issue the match ROM command \x55 and the start conversion command + if( OWX_Complex($master,$owx_dev,"\xB4",0) eq 0 ){ + return "$owx_dev voltage conversion failed"; + } + #-- conversion needs some 4 ms ! + select(undef,undef,undef,0.006); + + #-- from memory to scratchpad + OWX_Reset($master); + #-- issue the match ROM command \x55 and the recall memory command + if( OWX_Complex($master,$owx_dev,"\xB8\x00",0) eq 0 ){ + return "$owx_dev recall memory failed"; + } + #-- copy needs some 10 ms ! + select(undef,undef,undef,0.012); + + #-- NOW ask the specific device + OWX_Reset($master); + #-- issue the match ROM command \x55 and the read scratchpad command \xBE + #-- reading 9 + 2 + 9 data bytes = 20 bytes + $res=OWX_Complex($master,$owx_dev,"\xBE\x00",9); + #Log 1,"OWXMULTI: data length from reading device is ".length($res)." bytes"; + #-- process results + if( $res eq 0 ){ + return "$owx_dev not accessible in 2nd step"; + } + + # $res2 = "====> OWXMULTI Received "; + # for(my $i=0;$i DS2438 + + #-- temperature + my $lsb = ord($data[12]); + my $msb = ord($data[13]) & 127; + my $sign = ord($data[13]) & 128; + + #-- test with -55 degrees + #$lsb = 0; + #$sign = 1; + #$msb = 73; + + #-- 2's complement form = signed bytes + $owg_temp = $msb+ $lsb/256; + if( $sign !=0 ){ + $owg_temp = -128+$owg_temp; + } + + #-- voltage + $lsb = ord($data[14]); + $msb = ord($data[15]) & 3; + + #-- test with 5V + #$lsb = 244; + #$msb = 1; + + #-- supply voltage + $owg_vdd = ($msb*256+ $lsb)/100; + + #-- switch the device to current measurement off, V external only + OWX_Reset($master); + #-- issue the match ROM command \x55 and the write scratchpad command + if( OWX_Complex($master,$owx_dev,"\x4E\x00\x00",0) eq 0 ){ + return "$owx_dev write status failed"; + } + + #-- copy scratchpad to register + OWX_Reset($master); + #-- issue the match ROM command \x55 and the copy scratchpad command + if( OWX_Complex($master,$owx_dev,"\x48\x00",0) eq 0){ + return "$owx_dev copy scratchpad failed"; + } + + #-- initiate voltage conversion + OWX_Reset($master); + #-- issue the match ROM command \x55 and the start conversion command + if( OWX_Complex($master,$owx_dev,"\xB4",0) eq 0 ){ + return "$owx_dev voltage conversion failed"; + } + #-- conversion needs some 4 ms ! + select(undef,undef,undef,0.006); + + #-- from memory to scratchpad + OWX_Reset($master); + #-- issue the match ROM command \x55 and the recall memory command + if( OWX_Complex($master,$owx_dev,"\xB8\x00",0) eq 0 ){ + return "$owx_dev recall memory failed"; + } + #-- copy needs some 10 ms ! + select(undef,undef,undef,0.012); + + #-- NOW ask the specific device + OWX_Reset($master); + #-- issue the match ROM command \x55 and the read scratchpad command \xBE + #-- reading 9 + 2 + 9 data bytes = 20 bytes + $res=OWX_Complex($master,$owx_dev,"\xBE\x00",9); + #Log 1,"OWXMULTI: data length from reading device is ".length($res)." bytes"; + #-- process results + if( $res eq 0 ){ + return "$owx_dev not accessible in 2nd step"; + } + + # $res2 = "====> OWXMULTI Received "; + # for(my $i=0;$i DS2438 + + #-- voltage + $lsb = ord($data[14]); + $msb = ord($data[15]) & 3; + + #-- test with 7.2 V + #$lsb = 208; + #$msb = 2; + + #-- external voltage + $owg_volt = ($msb*256+ $lsb)/100; + + return undef; + + #} else { + # return "OWXMULTI: Unknown device family $hash->{OW_FAMILY}\n"; + #} +} + +####################################################################################### +# +# OWXMULTI_SetValues - Implements SetFn function +# +# Parameter hash = hash of device addressed +# a = argument array +# +######################################################################################## + +sub OWXMULTI_SetValues($@) { + my ($hash, @a) = @_; + + my ($i,$j,$k); + + my $name = $hash->{NAME}; + #-- ID of the device + my $owx_dev = $hash->{ROM_ID}; + #-- hash of the busmaster + my $master = $hash->{IODev}; + + #-- define vars + my $key = $a[1]; + my $value = $a[2]; + + 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("\x4E%c%c\x48",0,0); + my $res=OWX_Complex($master,$owx_dev,$select,0); + + if( $res eq 0 ){ + return "OWXMULTI: Device $owx_dev not accessible"; + } + + DoTrigger($name, undef) if($init_done); + return undef; +} + + + +1; diff --git a/fhem/FHEM/21_OWSWITCH.pm b/fhem/FHEM/21_OWSWITCH.pm new file mode 100644 index 000000000..ff5a409a4 --- /dev/null +++ b/fhem/FHEM/21_OWSWITCH.pm @@ -0,0 +1,894 @@ +######################################################################################## +# +# OWSWITCH.pm +# +# FHEM module to commmunicate with 1-Wire adressable switches DS2413, DS206, DS2408 +# +# Attention: This module may communicate with the OWX module, +# but currently not with the 1-Wire File System OWFS +# +# TODO: Kanalattribute ändern zur Laufzeit. +# +# +# Prefixes for subroutines of this module: +# OW = General 1-Wire routines Peter Henning) +# OWX = 1-Wire bus master interface (Peter Henning) +# OWFS = 1-Wire file system (??) +# +# Prof. Dr. Peter A. Henning, 2012 +# +# Version 2.24 - October, 2012 +# +# Setup bus device in fhem.cfg as +# +# define OWSWITCH [] [interval] +# +# where may be replaced by any name string +# +# is a 1-Wire device type. If omitted, we assume this to be an +# DS2413. Allowed values are DS2413, DS2406 +# is a 12 character (6 byte) 1-Wire ROM ID +# without Family ID, e.g. A2D90D000800 +# [interval] is an optional query interval in seconds +# +# get id => FAM_ID.ROM_ID.CRC +# get present => 1 if device present, 0 if not +# get interval => query interval +# get input => state for channel (name A, B or defined channel name) +# note: this value reflects the measured value, not necessarily the one set as +# output state, because the output transistors are open collector switches. A measured +# state of 1 = OFF therefore corresponds to an output state of 1 = OFF, but a measured +# state of 0 = ON can also be due to an external shortening of the output. +# get gpio => values for channels +# +# set interval => set period for measurement +# set output ON|OFF => set value for channel (name A, B or defined channel name) +# note: 1 = OFF, 0 = ON in normal usage. See also the note above +# set gpio value => set values for channels (3 = both OFF, 1 = B ON 2 = A ON 0 = both ON) +# set init yes => re-initialize device +# +# Additional attributes are defined in fhem.cfg, in some cases per channel, where =A,B +# Note: attributes are read only during initialization procedure - later changes are not used. +# +# attr event on-change/on-update = when to write an event (default= on-update) +# +# attr Name | = name for the channel | a type description for the measured value +# attr Unit | = values to display in state variable for on|off condition +# attr stateS = character string denoting external shortening condition +# +######################################################################################## +# +# 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($$); + +#-- channel name - fixed is the first array, variable the second +my @owg_fixed = ("A","B","C","D","E","F","G","H"); +my @owg_channel; +#-- channel values - always the raw input resp. output values from the device +my @owg_val; +my @owg_vax; + +my %gets = ( + "id" => "", + "present" => "", + "interval" => "", + "input" => "", + "gpio" => "" +); + +my %sets = ( + "interval" => "", + "output" => "", + "gpio" => "", + "init" => "" +); + +my %updates = ( + "present" => "", + "gpio" => "" +); + +my %cnumber = ( + "DS2413" => 2, + "DS2406" => 2, + "DS2408" => 8 + ); + +######################################################################################## +# +# The following subroutines are independent of the bus interface +# +# Prefix = OWSWITCH +# +######################################################################################## +# +# OWSWITCH_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWSWITCH_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "OWSWITCH_Define"; + $hash->{UndefFn} = "OWSWITCH_Undef"; + $hash->{GetFn} = "OWSWITCH_Get"; + $hash->{SetFn} = "OWSWITCH_Set"; + + my $attlist = "IODev do_not_notify:0,1 showtime:0,1 model:DS2413,DS2406,DS2408 loglevel:0,1,2,3,4,5 ". + "event:on-update,on-change"; + + #TODO: correct number of channels + + for( my $i=0;$i<8;$i++ ){ + $attlist .= " ".$owg_fixed[$i]."Name"; + $attlist .= " ".$owg_fixed[$i]."Unit"; + $attlist .= " ".$owg_fixed[$i]."stateS"; + } + $hash->{AttrList} = $attlist; +} + +######################################################################################### +# +# OWSWITCH_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################### + +sub OWSWITCH_Define ($$) { + my ($hash, $def) = @_; + + # define OWSWITCH [] [interval] + # e.g.: define flow OWSWITCH 525715020000 300 + my @a = split("[ \t][ \t]*", $def); + + my ($name,$model,$fam,$id,$crc,$interval,$scale,$ret); + + #-- default + $name = $a[0]; + $interval = 300; + $scale = ""; + $ret = ""; + + #-- check syntax + return "OWSWITCH: Wrong syntax, must be define OWSWITCH [] [interval]" + if(int(@a) < 2 || int(@a) > 5); + + #-- check if this is an old style definition, e.g. is missing + my $a2 = $a[2]; + my $a3 = defined($a[3]) ? $a[3] : ""; + if( $a2 =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $model = "DS2413"; + $id = $a[2]; + if(int(@a)>=4) { $interval = $a[3]; } + } elsif( $a3 =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $model = $a[2]; + $id = $a[3]; + if(int(@a)>=5) { $interval = $a[4]; } + } else { + return "OWSWITCH: $a[0] ID $a[2] invalid, specify a 12 digit value"; + } + #-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY" + # FF = family id follows from the model + # YY must be determined from id + if( $model eq "DS2413" ){ + $fam = "3A"; + CommandAttr (undef,"$name model DS2413"); + }elsif( $model eq "DS2406" ){ + $fam = "12"; + CommandAttr (undef,"$name model DS2406"); + }elsif( $model eq "DS2408" ){ + $fam = "29"; + CommandAttr (undef,"$name model DS2408"); + }else{ + return "OWSWITCH: Wrong 1-Wire device model $model"; + } + + #-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY" + # determine CRC Code - only if this is a direct interface + $crc = defined($hash->{IODev}->{INTERFACE}) ? sprintf("%02x",OWX_CRC($fam.".".$id."00")) : "00"; + + #-- Define device internals + $hash->{ROM_ID} = $fam.".".$id.$crc; + $hash->{OW_ID} = $id; + $hash->{OW_FAMILY} = $fam; + $hash->{PRESENT} = 0; + $hash->{INTERVAL} = $interval; + + #-- Couple to I/O device + AssignIoPort($hash); + Log 3, "OWSWITCH: Warning, no 1-Wire I/O device found for $name." + if(!defined($hash->{IODev}->{NAME})); + $modules{OWSWITCH}{defptr}{$id} = $hash; + $hash->{STATE} = "Defined"; + Log 3, "OWSWITCH: Device $name defined."; + + #-- Initialization reading according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- Start timer for initialization in a few seconds + InternalTimer(time()+1, "OWSWITCH_InitializeDevice", $hash, 0); + + #-- Start timer for updates + InternalTimer(time()+$hash->{INTERVAL}, "OWSWITCH_GetValues", $hash, 0); + + return undef; +} + +######################################################################################## +# +# OWSWITCH_InitializeDevice - delayed setting of initial readings and channel names +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWSWITCH_InitializeDevice($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + + #-- Set channel names, channel units + for( my $i=0;$i<$cnumber{$attr{$name}{"model"}} ;$i++) { + #-- Initial readings OFF + $owg_val[$i] = 1; + $owg_vax[$i] = 1; + #-- name + my $cname = defined($attr{$name}{$owg_fixed[$i]."Name"}) ? $attr{$name}{$owg_fixed[$i]."Name"} : $owg_fixed[$i]."|onoff"; + my @cnama = split(/\|/,$cname); + if( int(@cnama)!=2){ + Log 1, "OWSWITCH: Incomplete channel name specification $cname. Better use $cname|"; + push(@cnama,"unknown"); + } + + #-- unit + my $unit = defined($attr{$name}{$owg_fixed[$i]."Unit"}) ? $attr{$name}{$owg_fixed[$i]."Unit"} : "ON|OFF"; + my @unarr= split(/\|/,$unit); + if( int(@unarr)!=2 ){ + Log 1, "OWSWITCH: Wrong channel unit specification $unit, replaced by ON|OFF"; + $unit="ON|OFF"; + } + + #-- put into readings + $owg_channel[$i] = $cnama[0]; + $hash->{READINGS}{"$owg_channel[$i]"}{TYPE} = $cnama[1]; + $hash->{READINGS}{"$owg_channel[$i]"}{UNIT} = $unit; + $hash->{READINGS}{"$owg_channel[$i]"}{UNITABBR} = $unit; + } + + #-- set status according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- OWX interface + if( !defined($interface) ){ + return "OWSWITCH: Interface missing"; + } elsif( $interface eq "OWX" ){ + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetPage($hash,"reading"); + #-- Unknown interface + }else{ + return "OWSWITCH: InitializeDevice with wrong IODev type $interface"; + } + + #-- Initialize all the display stuff + OWSWITCH_FormatValues($hash); +} + +######################################################################################## +# +# OWSWITCH_FormatValues - put together various format strings +# +# Parameter hash = hash of device addressed, fs = format string +# +######################################################################################## + +sub OWSWITCH_FormatValues($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + my ($offset,$factor,$vval,$vvax,$vstr,$cname,@cnama,@unarr); + my ($value1,$value2,$value3) = ("","",""); + + my $tn = TimeNow(); + + #-- formats for output + for (my $i=0;$i<$cnumber{$attr{$name}{"model"}};$i++){ + $cname = defined($attr{$name}{$owg_fixed[$i]."Name"}) ? $attr{$name}{$owg_fixed[$i]."Name"} : $owg_fixed[$i]; + @cnama = split(/\|/,$cname); + $owg_channel[$i]=$cnama[0]; + + #-- input state is 0 = ON or 1 = OFF + $vval = $owg_val[$i]; + #-- output state is 0 = ON or 1 = OFF + $vvax = $owg_vax[$i]; + + #-- string buildup for return value and STATE + @unarr= split(/\|/,$hash->{READINGS}{"$owg_channel[$i]"}{UNIT}); + $cname = defined($attr{$name}{$owg_fixed[$i]."stateS"}) ? $attr{$name}{$owg_fixed[$i]."stateS"} : ""; + $vstr = $unarr[$vval]; + $vstr .= $cname if( ($vval == 0) && ($vvax == 1) ); + $vstr = "ERR" if( ($vval == 1) && ($vvax == 0) ); + + $value1 .= sprintf( "%s: %s", $owg_channel[$i], $vstr); + $value2 .= sprintf( "%s: %s ", $owg_channel[$i], $vstr); + $value3 .= sprintf( "%s: " , $owg_channel[$i]); + + #-- put into READINGS + $hash->{READINGS}{"$owg_channel[$i]"}{VAL} = $vstr; + $hash->{READINGS}{"$owg_channel[$i]"}{TIME} = $tn; + + #-- insert comma + if( $i<$cnumber{$attr{$name}{"model"}}-1 ){ + $value1 .= " "; + $value2 .= ", "; + $value3 .= ", "; + } + } + #-- STATE + $hash->{STATE} = $value2; + + return $value1; +} + +######################################################################################## +# +# OWSWITCH_Get - Implements GetFn function +# +# Parameter hash = hash of device addressed, a = argument array +# +######################################################################################## + +sub OWSWITCH_Get($@) { + my ($hash, @a) = @_; + + my $reading = $a[1]; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my ($value,$value2,$value3) = (undef,undef,undef); + my $ret = ""; + my $offset; + my $factor; + my $page; + + #-- check syntax + return "OWSWITCH: Get argument is missing @a" + if(int(@a) < 2); + + #-- check argument + return "OWSWITCH: Get with unknown argument $a[1], choose one of ".join(",", sort keys %gets) + if(!defined($gets{$a[1]})); + + #-- get id + if($a[1] eq "id") { + $value = $hash->{ROM_ID}; + return "$name.id => $value"; + } + + #-- get present + if($a[1] eq "present") { + #-- hash of the busmaster + my $master = $hash->{IODev}; + $value = OWX_Verify($master,$hash->{ROM_ID}); + $hash->{PRESENT} = $value; + return "$name.present => $value"; + } + + #-- get interval + if($a[1] eq "interval") { + $value = $hash->{INTERVAL}; + return "$name.interval => $value"; + } + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- get values according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- get single state + # TODO: WAS passiert, wenn channel name noch falsch ist ? + if( $reading eq "input" ){ + return "OWSWITCH: get needs parameter when reading input: " + if( int(@a)<2 ); + my $fnd=undef; + for (my $i=0;$i<$cnumber{$attr{$name}{"model"}};$i++){ + if( ($a[2] eq $owg_channel[$i]) || ($a[2] eq $owg_fixed[$i]) ){ + $fnd=$i; + last; + } + } + return "OWSWITCH: invalid output address, must be A,B,... or defined channel name" + if( !defined($fnd) ); + + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXSWITCH_GetState($hash); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSSWITCH_GetPage($hash,"reading"); + #-- Unknown interface + }else{ + return "OWSWITCH: Get with wrong IODev type $interface"; + } + #-- process results + OWSWITCH_FormatValues($hash); + my @states = split(/,/,$hash->{STATE}); + + return $a[2]." = ".$states[$fnd]; + + #-- get all states + }elsif( $reading eq "gpio" ){ + return "OWSWITCH: get needs no parameter when reading gpio" + if( int(@a)==1 ); + + if( $interface eq "OWX" ){ + $ret = OWXSWITCH_GetState($hash); + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetValues($hash); + }else{ + return "OWSWITCH: GetValues with wrong IODev type $interface"; + } + } + #-- process results + if( defined($ret) ){ + return "OWSWITCH: Could not get values from device $name"; + } + $hash->{PRESENT} = 1; + return "OWSWITCH: $name.$reading => ".OWSWITCH_FormatValues($hash); + +} + +####################################################################################### +# +# OWSWITCH_GetValues - Updates the reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWSWITCH_GetValues($) { + my $hash = shift; + + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my $value = ""; + my $ret = ""; + my $offset; + my $factor; + + #-- restart timer for updates + RemoveInternalTimer($hash); + InternalTimer(time()+$hash->{INTERVAL}, "OWSWITCH_GetValues", $hash, 1); + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- Get readings according to interface type + my $interface= $hash->{IODev}->{TYPE}; + if( $interface eq "OWX" ){ + $ret = OWXSWITCH_GetState($hash); + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSSWITCH_GetValues($hash); + }else{ + return "OWSWITCH: GetValues with wrong IODev type $interface"; + } + + #-- process results + if( defined($ret) ){ + return "OWSWITCH: Could not get values from device $name"; + } + $hash->{PRESENT} = 1; + #-- old state, new state + my $oldval = $hash->{STATE}; + $value=OWSWITCH_FormatValues($hash); + my $newval = $hash->{STATE}; + #--logging depends on setting of the event-attribute + Log 5, $value; + my $ev = defined($attr{$name}{"event"}) ? $attr{$name}{"event"} : "on-update"; + if( ($ev eq "on-update") || (($ev eq "on-change") && ($newval ne $oldval)) ){ + $hash->{CHANGED}[0] = $value; + DoTrigger($name, undef); + } + + return undef; +} + +####################################################################################### +# +# OWSWITCH_Set - Set one value for device +# +# Parameter hash = hash of device addressed +# a = argument array +# +######################################################################################## + +sub OWSWITCH_Set($@) { + my ($hash, @a) = @_; + + my $key = $a[1]; + my $value = $a[2]; + + #-- for the selector: which values are possible + if (@a == 2){ + my $newkeys = join(" ", sort keys %sets); + return $newkeys ; + } + + #-- check argument + if( !defined($sets{$a[1]}) ){ + return "OWSWITCH: Set with unknown argument $a[1]"; + } + + #-- define vars + my $ret = undef; + my $channel = undef; + my $channo = undef; + my $condx; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + + #-- reset the device + if($key eq "init") { + return "OWCOUNT: init needs parameter 'yes'" + if($value ne "yes"); + OWSWITCH_InitializeDevice($hash); + return "OWCOUNT: Re-initialized device"; + } + + #-- set new timer interval + if($key eq "interval") { + # check value + return "OWSWITCH: Set with short interval, must be > 1" + if(int($value) < 1); + # update timer + $hash->{INTERVAL} = $value; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWSWITCH_GetValues", $hash, 1); + return undef; + } + + + #-- Set readings according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- set single state + # TODO: WAS passiert, wenn channel name noch falsch ist ? + if( $key eq "output" ){ + return "OWSWITCH: get needs parameter when writing output: " + if( int(@a)<2 ); + #-- find out which channel we have + my $fnd=undef; + for (my $i=0;$i<$cnumber{$attr{$name}{"model"}};$i++){ + if( ($a[2] eq $owg_channel[$i]) || ($a[2] eq $owg_fixed[$i]) ){ + $fnd=$i; + last; + } + } + return "OWSWITCH: invalid output address, must be A,B,... or defined channel name" + if( !defined($fnd) ); + #-- prepare gpio value + my $nval; + if( lc($a[3]) eq "on" ){ + $nval = 0; + }elsif( lc($a[3]) eq "off" ){ + $nval = 1; + }else{ + return "OWSWITCH: Wrong data value $a[3], must be ON or OFF"; + } + + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXSWITCH_GetState($hash); + $value = 0; + #-- vax or val ? + for (my $i=0;$i<$cnumber{$attr{$name}{"model"}};$i++){ + $value += ($owg_vax[$i]<<$i) + if( $i != $fnd ); + $value += ($nval<<$i) + if( $i == $fnd ); + } + $ret = OWXSWITCH_SetState($hash,$value); + #-- OWFS interface + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSAD_GetPage($hash,"reading"); + #-- Unknown interface + }else{ + return "OWSWITCH: Get with wrong IODev type $interface"; + } + + #-- set state + }elsif( $key eq "gpio" ){ + #-- check value and write to device + return "OWSWITCH: Set with wrong value for gpio port, must be 0 <= gpio <= ".(1 << $cnumber{$attr{$name}{"model"}} - 1) + if( ! ((int($value) >= 0) && (int($value) <= (1 << $cnumber{$attr{$name}{"model"}} -1 ))) ); + + if( $interface eq "OWX" ){ + $ret = OWXSWITCH_SetState($hash,int($value)); + #}elsif( $interface eq "OWFS" ){ + # $ret = OWFSSWITCH_GetValues($hash); + }else{ + return "OWSWITCH: GetValues with wrong IODev type $interface"; + } + } + + #-- process results - we have to reread the device + $hash->{PRESENT} = 1; + OWSWITCH_GetValues($hash); + #OWSWITCH_FormatValues($hash); + Log 4, "OWSWITCH: Set $hash->{NAME} $key $value"; + #$hash->{CHANGED}[0] = $value; + return undef; +} + +######################################################################################## +# +# OWSWITCH_Undef - Implements UndefFn function +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWSWITCH_Undef ($) { + my ($hash) = @_; + delete($modules{OWSWITCH}{defptr}{$hash->{OW_ID}}); + RemoveInternalTimer($hash); + return undef; +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a 1-Wire bus connected +# via OWFS +# +# Prefix = OWFSSWITCH +# +######################################################################################## + + + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a 1-Wire bus connected +# directly to the FHEM server +# +# Prefix = OWXSWITCH +# +######################################################################################## +# +# OWXAD_GetState - Get gpio ports from device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWXSWITCH_GetState($) { + my ($hash) = @_; + + my ($select, $res, $res2, $res3, @data); + + #-- 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); + + #-- family = 3A => DS2413 + if( $hash->{OW_FAMILY} eq "3A" ) { + #=============== get gpio values =============================== + #-- issue the match ROM command \x55 and the read gpio command + # \xF5 plus 2 empty bytes + #-- reset the bus + OWX_Reset($master); + #-- read the data + $res=OWX_Complex($master,$owx_dev,"\xF5",2); + if( $res eq 0 ){ + return "OWXSWITCH: Device $owx_dev not accessible in reading"; + } + #-- family = 12 => DS2406 + }elsif( $hash->{OW_FAMILY} eq "12" ) { + #=============== get gpio values =============================== + #-- issue the match ROM command \x55 and the access channel command + # \xF5 plus the two byte channel control and the value + $select=sprintf("\xF5\xDC\xFF"); + #-- reset the bus + OWX_Reset($master); + #-- read the data + $res=OWX_Complex($master,$owx_dev,$select,1); + if( $res eq 0 ){ + return "OWXSWITCH: Device $owx_dev not accessible in writing"; + } + #-- family = 29 => DS2408 + }elsif( $hash->{OW_FAMILY} eq "29" ) { + #=============== get gpio values =============================== + #-- issue the match ROM command \x55 and the read PIO rtegisters command + # \xF5 plus the two byte channel target address + #-- reading 9 + 3 + 10 data bytes = 22 bytes + $select=sprintf("\xF0\x88\x00"); + #-- reset the bus + OWX_Reset($master); + #-- read the data + $res=OWX_Complex($master,$owx_dev,$select,10); + if( $res eq 0 ){ + return "OWXSWITCH: Device $owx_dev not accessible in writing"; + } + } else { + return "OWXSWITCH: Unknown device family $hash->{OW_FAMILY}\n"; + } + + #-- process results + @data=split(//,substr($res,10)); + #return "invalid data length" + # if (@data != 22); + #return "invalid data" + # if (ord($data[17])<=0); + #return "invalid CRC" + # if (OWX_CRC8(substr($res,10,8),$data[18])==0); + + #-- reset the bus + OWX_Reset($master); + + # note: value 1 corresponds to OFF, 0 to ON normally + # note: val = input value, vax = output value + #-- family = 3A => DS2413 + if( $hash->{OW_FAMILY} eq "3A" ) { + $owg_val[0] = ord($data[0]) & 1; + $owg_vax[0] = (ord($data[0])>>1) & 1; + $owg_val[1] = (ord($data[0])>>2) & 1; + $owg_vax[1] = (ord($data[0])>>3) & 1; + + #-- family = 12 => DS2406 + }elsif( $hash->{OW_FAMILY} eq "12" ) { + $owg_val[0] = (ord($data[2])>>2) & 1; + $owg_vax[0] = ord($data[2]) & 1; + $owg_val[1] = (ord($data[2])>>3) & 1; + $owg_vax[1] = (ord($data[2])>>1) & 1; + #-- family = 29 => DS2408 + }elsif( $hash->{OW_FAMILY} eq "29" ) { + for(my $i=0;$i<8;$i++){ + $owg_val[$i] = (ord($data[2])>>$i) & 1; + $owg_vax[$i] = (ord($data[3])>>$i) & 1; + } + } + return undef +} + +######################################################################################## +# +# OWXSWITCH_SetPage - Set gpio ports of device +# +# Parameter hash = hash of device addressed +# value = integer value for device outputs +# +######################################################################################## + +sub OWXSWITCH_SetState($$) { + + my ($hash,$value) = @_; + + + my ($select, $res, $res2, @data); + + #-- 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); + + #-- family = 3A => DS2413 + if( $hash->{OW_FAMILY} eq "3A" ) { + #=============== set gpio values =============================== + #-- issue the match ROM command \x55 and the write gpio command + # \x5A plus the value byte and its complement + $select=sprintf("\x5A%c%c",252+$value,3-$value); + #-- reset the bus + OWX_Reset($master); + #-- read the data + $res=OWX_Complex($master,$owx_dev,$select,1); + if( $res eq 0 ){ + return "OWXSWITCH: Device $owx_dev not accessible in writing"; + } + #-- family = 12 => DS2406 + }elsif( $hash->{OW_FAMILY} eq "12" ) { + #=============== set gpio values =============================== + # Writing the output state via the access channel command does + # not work contrary to documentation. Using the write status command + #-- issue the match ROM command \x55 and the read status command + # \xAA at address TA1 = \x07 TA2 = \x00 + #-- reset the bus + OWX_Reset($master); + #-- read the data + $res = OWX_Complex($master,$owx_dev,"\xAA\x07\x00",1); + my $stat = substr($res,10,1); + my $statneu = ( $stat & 159 ) | (($value<<5) & 96) ; + #-- issue the match ROM command \x55 and the write status command + # \x55 at address TA1 = \x07 TA2 = \x00 + # + $select=sprintf("\x55\x07\x00%c",$statneu); + #-- reset the bus + OWX_Reset($master); + #-- read the data + $res=OWX_Complex($master,$owx_dev,$select,2); + if( $res eq 0 ){ + return "OWXSWITCH: Device $owx_dev not accessible in writing"; + } + $owg_val[0] = $value % 2; + $owg_vax[0] = $owg_val[0]; + $owg_val[1] = int($value / 2); + $owg_vax[1] = $owg_val[1]; + #-- family = 29 => DS2408 + }elsif( $hash->{OW_FAMILY} eq "29" ) { + #=============== set gpio values =============================== + #-- issue the match ROM command \x55 and the write gpio command + # \x5A plus the value byte and its complement + $select=sprintf("\x5A%c%c",$value,255-$value); + #-- reset the bus + OWX_Reset($master); + #-- read the data + $res=OWX_Complex($master,$owx_dev,$select,1); + if( $res eq 0 ){ + return "OWXSWITCH: Device $owx_dev not accessible in writing"; + } + + } else { + return "OWXSWITCH: Unknown device family $hash->{OW_FAMILY}\n"; + } + #-- reset the bus + OWX_Reset($master); + + #-- process results + @data=split(//,substr($res,10)); + + #-- family = 3A => DS2413 + if( $hash->{OW_FAMILY} eq "3A" ) { + if( $data[2] ne "\xAA"){ + return "OWXSWITCH: State could not be set for device $owx_dev"; + } + #-- family = 12 => DS2406 + }elsif( $hash->{OW_FAMILY} eq "12" ) { + #-- very crude check - should be CRC + if( int(@data) != 5){ + return "OWXSWITCH: State could not be set for device $owx_dev"; + } + #-- family = 29 => DS2408 + }elsif( $hash->{OW_FAMILY} eq "29" ) { + if( $data[2] ne "\xAA"){ + return "OWXSWITCH: State could not be set for device $owx_dev"; + } + } + return undef + +} + +1; diff --git a/fhem/FHEM/21_OWTHERM.pm b/fhem/FHEM/21_OWTHERM.pm new file mode 100755 index 000000000..c2c962cc3 --- /dev/null +++ b/fhem/FHEM/21_OWTHERM.pm @@ -0,0 +1,815 @@ +######################################################################################## +# +# OWTHERM.pm +# +# FHEM module to commmunicate with 1-Wire temperature sensors DS1820, DS18S20, DS18B20, DS1822 +# +# Attention: This module may communicate with the OWX module, +# and also with the 1-Wire File System OWFS +# +# 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) +# +# Prof. Dr. Peter A. Henning, 2012 +# Martin Fischer, 2011 +# +# Version 2.24 - October, 2012 +# +# Setup bus device in fhem.cfg as +# +# define OWTHERM [] [interval] +# +# 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 +# Currently allowed values are DS1820, DS18B20, DS1822 +# is a 12 character (6 byte) 1-Wire ROM ID +# without Family ID, e.g. A2D90D000800 +# [interval] is an optional query interval in seconds +# +# get id => OW_FAMILY.ROM_ID.CRC +# get present => 1 if device present, 0 if not +# get interval => query interval +# get temperature => temperature measurement +# get alarm => alarm temperature settings +# +# set interval => set period for measurement +# set tempLow => lower alarm temperature setting +# set tempHigh => higher alarm temperature setting +# +# Additional attributes are defined in fhem.cfg +# Note: attributes "tempXXXX" are read during every update operation. +# +# attr event on-change/on-update = when to write an event (default= on-update) +# +# attr stateAL "" = character string for denoting low alarm condition, default is red down triangle +# attr stateAH "" = character string for denoting high alarm condition, default is red up triangle +# attr tempOffset = temperature offset in degree Celsius added to the raw temperature reading +# attr tempUnit = unit of measurement, e.g. Celsius/Kelvin/Fahrenheit or C/K/F, default is Celsius +# attr tempLow = value for low alarm +# attr tempHigh = value for high alarm +# +######################################################################################## +# +# 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($$); + +#-- temperature globals - always the raw values from the device +my $owg_temp = 0; +my $owg_th = 0; +my $owg_tl = 0; + +#-- variables for display strings +my $stateal; +my $stateah; + +my %gets = ( + "id" => "", + "present" => "", + "interval" => "", + "temperature" => "", + "alarm" => "" +); + +my %sets = ( + "interval" => "", + "tempHigh" => "", + "tempLow" => "" +); + +my %updates = ( + "present" => "", + "temperature" => "", + "alarm" => "" +); + +######################################################################################## +# +# The following subroutines are independent of the bus interface +# +# Prefix = OWTHERM +# +######################################################################################## +# +# OWTHERM_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWTHERM_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "OWTHERM_Define"; + $hash->{UndefFn} = "OWTHERM_Undef"; + $hash->{GetFn} = "OWTHERM_Get"; + $hash->{SetFn} = "OWTHERM_Set"; + #tempOffset = a temperature offset added to the temperature reading for correction + #tempUnit = a unit of measure: C/F/K + $hash->{AttrList}= "IODev do_not_notify:0,1 showtime:0,1 loglevel:0,1,2,3,4,5 ". + "event:on-update,on-change ". + "stateAL stateAH ". + "tempOffset tempUnit:C,Celsius,F,Fahrenheit,K,Kelvin ". + "tempLow tempHigh"; + } + +######################################################################################## +# +# OWTHERM_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################## + +sub OWTHERM_Define ($$) { + my ($hash, $def) = @_; + + # define OWTHERM [] [interval] + # e.g.: define flow OWTHERM 525715020000 300 + my @a = split("[ \t][ \t]*", $def); + + my ($name,$model,$fam,$id,$crc,$interval,$ret); + my $tn = TimeNow(); + + #-- default + $name = $a[0]; + $interval = 300; + $ret = ""; + + #-- check syntax + return "OWTHERM: Wrong syntax, must be define OWTHERM [] [interval]" + if(int(@a) < 2 || int(@a) > 6); + + #-- check if this is an old style definition, e.g. is missing + my $a2 = $a[2]; + my $a3 = defined($a[3]) ? $a[3] : ""; + if( ($a2 eq "none") || ($a3 eq "none") ) { + return "OWTHERM: ID = none is obsolete now, please redefine"; + } elsif( $a2 =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $model = "DS1820"; + $id = $a[2]; + if(int(@a)>=4) { $interval = $a[3]; } + Log 1, "OWTHERM: Parameter [alarminterval] is obsolete now - must be set with I/O-Device" + if(int(@a) == 5); + } elsif( $a3 =~ m/^[0-9|a-f|A-F]{12}$/ ) { + $model = $a[2]; + $id = $a[3]; + if(int(@a)>=5) { $interval = $a[4]; } + Log 1, "OWTHERM: Parameter [alarminterval] is obsolete now - must be set with I/O-Device" + if(int(@a) == 6); + } else { + return "OWTHERM: $a[0] ID $a[2] invalid, specify a 12 digit value"; + } + + #-- 1-Wire ROM identifier in the form "FF.XXXXXXXXXXXX.YY" + # FF = family id follows from the model + # YY must be determined from id + if( $model eq "DS1820" ){ + $fam = "10"; + }elsif( $model eq "DS1822" ){ + $fam = "22"; + }elsif( $model eq "DS18B20" ){ + $fam = "28"; + }else{ + return "OWTHERM: Wrong 1-Wire device model $model"; + } + # determine CRC Code - only if this is a direct interface + $crc = defined($hash->{IODev}->{INTERFACE}) ? sprintf("%02x",OWX_CRC($fam.".".$id."00")) : "00"; + + #-- define device internals + $hash->{ALARM} = 0; + $hash->{OW_ID} = $id; + $hash->{OW_FAMILY} = $fam; + $hash->{PRESENT} = 0; + $hash->{ROM_ID} = $fam.".".$id.$crc; + $hash->{INTERVAL} = $interval; + + #-- Couple to I/O device + AssignIoPort($hash); + Log 3, "OWTHERM: Warning, no 1-Wire I/O device found for $name." + if(!defined($hash->{IODev}->{NAME})); + $modules{OWTHERM}{defptr}{$id} = $hash; + $hash->{STATE} = "Defined"; + Log 3, "OWTHERM: Device $name defined."; + + #-- Start timer for initialization in a few seconds + InternalTimer(time()+10, "OWTHERM_InitializeDevice", $hash, 0); + + #-- Start timer for updates + InternalTimer(time()+$hash->{INTERVAL}, "OWTHERM_GetValues", $hash, 0); + + return undef; +} + +######################################################################################## +# +# OWTHERM_InitializeDevice - delayed setting of initial readings and channel names +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWTHERM_InitializeDevice($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + my @args; + + $stateal = defined($attr{$name}{stateAL}) ? $attr{$name}{stateAL} : ""; + $stateah = defined($attr{$name}{stateAH}) ? $attr{$name}{stateAH} : ""; + + #-- unit attribute defined ? + $hash->{READINGS}{"temperature"}{UNIT} = defined($attr{$name}{"tempUnit"}) ? $attr{$name}{"tempUnit"} : "Celsius"; + $hash->{READINGS}{"temperature"}{TYPE} = "temperature"; + + #-- Initial readings temperature sensor + $owg_temp = 0.0; + $owg_tl = defined($attr{$name}{"tempLow"}) ? $attr{$name}{"tempLow"} : 0.0; + $owg_th = defined($attr{$name}{"tempHigh"}) ? $attr{$name}{"tempHigh"} : 100.0; + #-- Initialize all the display stuff + OWTHERM_FormatValues($hash); + #-- alarm + @args = ($name,"tempLow",$owg_tl); + OWTHERM_Set($hash,@args); + @args = ($name,"tempHigh",$owg_th); + OWTHERM_Set($hash,@args); + +} + +######################################################################################## +# +# OWTHERM_FormatValues - put together various format strings +# +# Parameter hash = hash of device addressed, fs = format string +# +######################################################################################## + +sub OWTHERM_FormatValues($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + my ($unit,$offset,$factor,$abbr,$vval,$vlow,$vhigh,$statef); + my ($value1,$value2,$value3) = ("","",""); + + my $tn = TimeNow(); + + #-- attributes defined ? + $stateal = defined($attr{$name}{stateAL}) ? $attr{$name}{stateAL} : ""; + $stateah = defined($attr{$name}{stateAH}) ? $attr{$name}{stateAH} : ""; + $unit = defined($attr{$name}{"tempUnit"}) ? $attr{$name}{"tempUnit"} : $hash->{READINGS}{"temperature"}{UNIT}; + $offset = defined($attr{$name}{"tempOffset"}) ? $attr{$name}{"tempOffset"} : 0.0 ; + $factor = 1.0; + + if( $unit eq "Celsius" ){ + $abbr = "°C"; + } elsif ($unit eq "Kelvin" ){ + $abbr = "K"; + $offset += "273.16" + } elsif ($unit eq "Fahrenheit" ){ + $abbr = "°F"; + $offset = ($offset+32)/1.8; + $factor = 1.8; + } else { + $abbr="?"; + Log 1, "OWTHERM_FormatValues: unknown unit $unit"; + } + #-- these values are rather coplex to obtain, therefore save them in the hash + $hash->{READINGS}{"temperature"}{UNIT} = $unit; + $hash->{READINGS}{"temperature"}{UNITABBR} = $abbr; + $hash->{tempf}{offset} = $offset; + $hash->{tempf}{factor} = $factor; + + #-- correct values for proper offset, factor + $vval = ($owg_temp + $offset)*$factor; + + #-- put into READINGS + $hash->{READINGS}{"temperature"}{VAL} = $vval; + $hash->{READINGS}{"temperature"}{TIME} = $tn; + + #-- correct alarm values for proper offset, factor + $vlow = ($owg_tl + $offset)*$factor; + $vhigh = ($owg_th + $offset)*$factor; + + #-- put into READINGS + $hash->{READINGS}{"tempLow"}{VAL} = $vlow; + $hash->{READINGS}{"tempLow"}{TIME} = $tn; + $hash->{READINGS}{"tempHigh"}{VAL} = $vhigh; + $hash->{READINGS}{"tempHigh"}{TIME} = $tn; + + #-- formats for output + $statef = "%5.2f ".$abbr; + $value1 = "temperature: ".sprintf($statef,$vval); + $value2 = sprintf($statef,$vval); + $hash->{ALARM} = 1; + + #-- Test for alarm condition + if( ($vval <= $vlow) && ( $vval >= $vhigh ) ){ + $value2 .= " ".$stateal.$stateah; + $value3 .= " ".$stateal.$stateah; + }elsif( $vval <= $vlow ){ + $value2 .= " ".$stateal; + $value3 .= " ".$stateal; + }elsif( $vval >= $vhigh ){ + $value2 .= " ".$stateah; + $value3 .= " ".$stateah; + } else { + $hash->{ALARM} = 0; + } + + #-- STATE + $hash->{STATE} = $value2; + #-- alarm + #$hash->{READINGS}{alarms}{VAL} = $value3; + #$hash->{READINGS}{alarms}{TIME} = $tn; + return $value1; +} + +######################################################################################## +# +# OWTHERM_Get - Implements GetFn function +# +# Parameter hash = hash of device addressed, a = argument array +# +######################################################################################## + +sub OWTHERM_Get($@) { + my ($hash, @a) = @_; + + my $reading = $a[1]; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + my $value = undef; + my $ret = ""; + + #-- check syntax + return "OWTHERM: Get argument is missing @a" + if(int(@a) != 2); + + #-- check argument + return "OWTHERM: Get with unknown argument $a[1], choose one of ".join(",", sort keys %gets) + if(!defined($gets{$a[1]})); + + #-- get id + if($a[1] eq "id") { + $value = $hash->{ROM_ID}; + return "$name.id => $value"; + } + + #-- Get other values according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- get present + if($a[1] eq "present" ) { + #-- OWX interface + if( $interface eq "OWX" ){ + #-- hash of the busmaster + my $master = $hash->{IODev}; + $value = OWX_Verify($master,$hash->{ROM_ID}); + $hash->{PRESENT} = $value; + return "$name.present => $value"; + } else { + return "OWTHERM: Verification not yet implemented for interface $interface"; + } + } + + #-- get interval + if($reading eq "interval") { + $value = $hash->{INTERVAL}; + return "$name.interval => $value"; + } + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- OWX interface + if( $interface eq "OWX" ){ + #-- not different from getting all values .. + $ret = OWXTHERM_GetValues($hash); + #-- OWFS interface + }elsif( $interface eq "OWFS" ){ + $ret = OWFSTHERM_GetValues($hash); + #-- Unknown interface + }else{ + return "OWTHERM: Get with wrong IODev type $interface"; + } + + #-- process results + if( defined($ret) ){ + return "OWTHERM: Could not get values from device $name, return was $ret"; + } + $hash->{PRESENT} = 1; + OWTHERM_FormatValues($hash); + + #-- return the special reading + if ($reading eq "temperature") { + return "OWTHERM: $name.temperature => ". + $hash->{READINGS}{"temperature"}{VAL}; + } elsif ($reading eq "alarm") { + return "OWTHERM: $name.alarm => L ".$hash->{READINGS}{"tempLow"}{VAL}. + " H ".$hash->{READINGS}{"tempHigh"}{VAL}; + } + return undef; +} + +####################################################################################### +# +# OWTHERM_GetValues - Updates the readings from device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWTHERM_GetValues($@) { + my $hash = shift; + + my $name = $hash->{NAME}; + my $value = ""; + my $ret = ""; + + #-- restart timer for updates + RemoveInternalTimer($hash); + InternalTimer(time()+$hash->{INTERVAL}, "OWTHERM_GetValues", $hash, 1); + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- Get values according to interface type + my $interface= $hash->{IODev}->{TYPE}; + if( $interface eq "OWX" ){ + #-- max 3 tries + for(my $try=0; $try<3; $try++){ + $ret = OWXTHERM_GetValues($hash); + last + if( !defined($ret) ); + } + }elsif( $interface eq "OWFS" ){ + $ret = OWFSTHERM_GetValues($hash); + }else{ + Log 3, "OWTHERM: GetValues with wrong IODev type $interface"; + return 1; + } + + #-- process results + if( defined($ret) ){ + Log 3, "OWTHERM: Could not get values from device $name, reason $ret"; + return 1; + } + $hash->{PRESENT} = 1; + + #-- old state, new state + my $oldval = $hash->{STATE}; + $value=OWTHERM_FormatValues($hash); + my $newval = $hash->{STATE}; + #--logging depends on setting of the event-attribute + Log 5, $value; + my $ev = defined($attr{$name}{"event"}) ? $attr{$name}{"event"} : "on-update"; + if( ($ev eq "on-update") || (($ev eq "on-change") && ($newval ne $oldval)) ){ + $hash->{CHANGED}[0] = $value; + DoTrigger($name, undef); + } + + return undef; +} + +####################################################################################### +# +# OWTHERM_Set - Set one value for device +# +# Parameter hash = hash of device addressed +# a = argument string +# +######################################################################################## + +sub OWTHERM_Set($@) { + my ($hash, @a) = @_; + + #-- for the selector: which values are possible + return join(" ", sort keys %sets) if(@a == 2); + #-- check syntax + return "OWTHERM: Set needs one parameter" + if(int(@a) != 3); + #-- check argument + return "OWTHERM: 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; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + + #-- set new timer interval + if($key eq "interval") { + # check value + return "OWTHERM: Set with short interval, must be > 1" + if(int($value) < 1); + # update timer + $hash->{INTERVAL} = $value; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTHERM_GetValues", $hash, 1); + return undef; + } + + #-- set other values depending on interface type + my $interface = $hash->{IODev}->{TYPE}; + my $offset = $hash->{tempf}{offset}; + my $factor = $hash->{tempf}{factor}; + + #-- find upper and lower boundaries for given offset/factor + my $mmin = (-55+$offset)*$factor; + my $mmax = (125+$offset)*$factor; + return sprintf("OWTHERM: Set with wrong value $value for $key, range is [%3.1f,%3.1f]",$mmin,$mmax) + if($value < $mmin || $value > $mmax); + + #-- seems to be ok, put into the device + $a[2] = int($value/$factor-$offset); + + #-- OWX interface + if( $interface eq "OWX" ){ + $ret = OWXTHERM_SetValues($hash,@a); + #-- OWFS interface + }elsif( $interface eq "OWFS" ){ + $ret = OWFSTHERM_SetValues($hash,@a); + return $ret + if(defined($ret)); + } else { + return "OWTHERM: Set with wrong IODev type $interface"; + } + + #-- process results - we have to reread the device + $hash->{PRESENT} = 1; + OWTHERM_GetValues($hash); + OWTHERM_FormatValues($hash); + Log 4, "OWTHERM: Set $hash->{NAME} $key $value"; + + return undef; +} + +######################################################################################## +# +# OWTHERM_Undef - Implements UndefFn function +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWTHERM_Undef ($) { + my ($hash) = @_; + + delete($modules{OWTHERM}{defptr}{$hash->{OW_ID}}); + RemoveInternalTimer($hash); + return undef; +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a 1-Wire bus connected +# via OWFS +# +# Prefix = OWFSTHERM +# +######################################################################################## +# +# OWFSTHERM_GetValues - Get reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWFSTHERM_GetValues($) +{ + my ($hash) = @_; + + my $ret = OW::get("/uncached/".$hash->{OW_FAMILY}.".".$hash->{OW_ID}."/temperature"); + if( defined($ret) ) { + $hash->{PRESENT} = 1; + $owg_temp = $ret; + $owg_th = OW::get("/uncached/".$hash->{OW_FAMILY}.".".$hash->{OW_ID}."/temphigh"); + $owg_tl = OW::get("/uncached/".$hash->{OW_FAMILY}.".".$hash->{OW_ID}."/templow"); + } else { + $hash->{PRESENT} = 0; + $owg_temp = 0.0; + $owg_th = 0.0; + $owg_tl = 0.0; + } + + return undef; +} + +####################################################################################### +# +# OWFSTHERM_SetValues - Implements SetFn function +# +# Parameter hash = hash of device addressed +# a = argument array +# +######################################################################################## + +sub OWFSTHERM_SetValues($@) { + my ($hash, @a) = @_; + + #-- define vars + my $key = lc($a[1]); + my $value = $a[2]; + + return OW::put($hash->{OW_FAMILY}.".".$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 = OWXTHERM +# +######################################################################################## +# +# OWXTHERM_GetValues - Get reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWXTHERM_GetValues($) { + + my ($hash) = @_; + + my ($i,$j,$k); + + #-- For default, perform the conversion NOT now + my $con=1; + + #-- ID of the device + my $owx_dev = $hash->{ROM_ID}; + #-- hash of the busmaster + my $master = $hash->{IODev}; + + #-- check, if the conversion has been called before - only on devices with real power + if( defined($attr{$hash->{IODev}->{NAME}}{buspower}) && ( $attr{$hash->{IODev}->{NAME}}{buspower} eq "real") ){ + $con=0; + } + + #-- 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 + if( OWX_Complex($master,$owx_dev,"\x44",0) eq 0 ){ + return "$owx_dev not accessible"; + } + #-- conversion needs some 950 ms - but we may also do it in shorter time ! + select(undef,undef,undef,1.0); + } + + #-- NOW ask the specific device + OWX_Reset($master); + #-- issue the match ROM command \x55 and the read scratchpad command \xBE + #-- reading 9 + 1 + 8 data bytes and 1 CRC byte = 19 bytes + my $res=OWX_Complex($master,$owx_dev,"\xBE",9); + #Log 1,"OWXTHERM: data length from reading device is ".length($res)." bytes"; + #-- process results + if( $res eq 0 ){ + return "$owx_dev not accessible in 2nd step"; + } + + #-- process results + my @data=split(//,$res); + return "invalid data length, ".int(@data)." bytes" + if (@data != 19); + return "invalid data" + if (ord($data[17])<=0); + return "invalid CRC" + if (OWX_CRC8(substr($res,10,8),$data[18])==0); + + #-- this must be different for the different device types + # family = 10 => DS1820, DS18S20 + if( $hash->{OW_FAMILY} eq "10" ) { + + my $count_remain = ord($data[16]); + my $count_perc = ord($data[17]); + my $delta = -0.25 + ($count_perc - $count_remain)/$count_perc; + + my $lsb = ord($data[10]); + my $msb = 0; + my $sign = ord($data[11]) & 255; + + #-- test with -25 degrees + #$lsb = 12*16+14; + #$sign = 1; + #$delta = 0; + + #-- 2's complement form = signed bytes + $owg_temp = int($lsb/2) + $delta; + if( $sign !=0 ){ + $owg_temp = -128+$owg_temp; + } + + $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; + + } elsif ( ($hash->{OW_FAMILY} eq "22") || ($hash->{OW_FAMILY} eq "28") ) { + + my $lsb = ord($data[10]); + my $msb = ord($data[11]) & 7; + my $sign = ord($data[11]) & 248; + + #-- test with -55 degrees + #$lsb = 9*16; + #$sign = 1; + #$msb = 7; + + #-- 2's complement form = signed bytes + $owg_temp = $msb*16+ $lsb/16; + if( $sign !=0 ){ + $owg_temp = -128+$owg_temp; + } + $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 "OWXTHERM: Unknown device family $hash->{OW_FAMILY}\n"; + } +} + +####################################################################################### +# +# OWXTHERM_SetValues - Implements SetFn function +# +# Parameter hash = hash of device addressed +# a = argument array +# +######################################################################################## + +sub OWXTHERM_SetValues($@) { + my ($hash, @a) = @_; + + my ($i,$j,$k); + + my $name = $hash->{NAME}; + #-- ID of the device + my $owx_dev = $hash->{ROM_ID}; + #-- hash of the busmaster + my $master = $hash->{IODev}; + + #-- define vars + my $key = $a[1]; + my $value = $a[2]; + $owg_tl = $value if( $key eq "tempLow" ); + $owg_th = $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("\x4E%c%c\x48",$thp,$tlp); + my $res=OWX_Complex($master,$owx_dev,$select,0); + + if( $res eq 0 ){ + return "OWXTHERM: Device $owx_dev not accessible"; + } + + DoTrigger($name, undef) if($init_done); + return undef; +} + + + +1;