diff --git a/contrib/contrib/00_TAHR.pm b/contrib/contrib/00_TAHR.pm new file mode 100644 index 000000000..4b460078e --- /dev/null +++ b/contrib/contrib/00_TAHR.pm @@ -0,0 +1,153 @@ +############################################### +# Sample fhem module, one-level approach, controlling a single device like a +# directly attached heating regulator. +# The alternative is a two level approach, where a physical device like a CUL +# is a bridge to a large number of logical devices (like FS20 actors, S300 +# sensors, etc) + +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); + +sub TAHR_Read($); +sub TAHR_Ready($); +sub TAHR_setbits($$); +sub TAHR_SetReading($$$$); + +my %tahr_sets = ( + "ww_soll" => "0C07656565%02x6565", + "ww_betriebsart" => "0C0E%02x6565656565", +); + + +sub +TAHR_Initialize($) +{ + my ($hash) = @_; + + require "$attr{global}{modpath}/FHEM/DevIo.pm"; + + $hash->{ReadFn} = "TAHR_Read"; + $hash->{ReadyFn} = "TAHR_Ready"; + $hash->{DefFn} = "TAHR_Define"; + $hash->{UndefFn} = "TAHR_Undef"; + $hash->{SetFn} = "TAHR_Set"; + $hash->{AttrList}= "do_not_notify:1,0 loglevel:0,1,2,3,4,5,6"; +} + +##################################### +sub +TAHR_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "wrong syntax: define TAHR [devicename|none]" + if(@a != 3); + + DevIo_CloseDev($hash); + my $name = $a[0]; + my $dev = $a[2]; + + if($dev eq "none") { + Log 1, "TAHR device is none, commands will be echoed only"; + return undef; + } + + $hash->{DeviceName} = $dev; + my $ret = DevIo_OpenDev($hash, 0, "TAHR_Poll"); + return $ret; +} + + +##################################### +sub +TAHR_Undef($$) +{ + my ($hash, $arg) = @_; + DevIo_CloseDev($hash); + RemoveInternalTimer($hash); + return undef; +} + +##################################### +sub +TAHR_Set($@) +{ + my ($hash, @a) = @_; + + return "\"set TAHR\" needs at least an argument" if(@a < 2); + + my $cmd = $tahr_sets{$a[1]}; + return "Unknown argument $a[1], choose one of " . + join(" ", sort keys %tahr_sets) if(!defined($cmd)); + # FIXME + DevIo_SimpleWrite($hash, $cmd); + return undef; +} + + +##################################### +# called from the global loop, when the select for hash->{FD} reports data +sub +TAHR_Read($) +{ + my ($hash) = @_; + my $name = $hash->{NAME}; + my ($data, $crc); + + my $buf = DevIo_SimpleRead($hash); + return "" if(!defined($buf)); + + $buf = unpack('H*', $buf); + Log 5, "RAW: $buf"; + + ###################################### + # Analyze the data + my $tn = TimeNow(); + my ($key, $val) = ("key", "val"); + + # FIXME + TAHR_SetReading($hash, $tn, $key, $val); +} + +##################################### +sub +TAHR_Ready($) +{ + my ($hash) = @_; + + return DevIo_OpenDev($hash, 1, undef) + if($hash->{STATE} eq "disconnected"); + + # This is relevant for windows/USB only + my $po = $hash->{USBDev}; + my ($BlockingFlags, $InBytes, $OutBytes, $ErrorFlags) = $po->status; + return ($InBytes>0); +} + +sub +TAHR_Poll($) +{ + my ($hash) = @_; + return if($hash->{STATE} eq "disconnected"); + # FIXME + DevIo_SimpleWrite($hash, "02"); # Request data + InternalTimer(gettimeofday()+5, "TAHR_Poll", $hash, 0); +} + +sub +TAHR_SetReading($$$$) +{ + my ($hash,$tn,$key,$val) = @_; + my $name = $hash->{NAME}; + Log GetLogLevel($name,4), "$name: $key $val"; + $hash->{READINGS}{$key}{TIME} = $tn; + $hash->{READINGS}{$key}{VAL} = $val; + DoTrigger($name, "$key: $val"); +} + + +1; diff --git a/contrib/contrib/1-Wire/00_OWX.pm b/contrib/contrib/1-Wire/00_OWX.pm new file mode 100644 index 000000000..c2effa4ee --- /dev/null +++ b/contrib/contrib/1-Wire/00_OWX.pm @@ -0,0 +1,1590 @@ +######################################################################################## +# +# OWX.pm +# +# FHEM module to commmunicate directly with 1-Wire bus devices +# via an active DS2480/DS2490/DS9097U bus master interface or +# via a passive DS9097 interface +# +# Version 1.11 - March, 2012 +# +# Prof. Dr. Peter A. Henning, 2012 +# +# Setup interface as: +# +# define OWX +# +# where may be replaced by any name string +# is a serial (USB) device +# +# get alarms => find alarmed 1-Wire devices +# get devices => find all 1-Wire devices +# +# set interval => set period for temperature conversion and alarm testing +# 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; + +# Prototypes to make komodo happy +use vars qw{%attr %defs}; +sub Log($$); + +# Line counter +my $cline=0; + +# These we may get on request +my %gets = ( + "alarms" => "A", + "devices" => "D" +); + +# These occur in a pulldown menu as settable values for the bus master +my %sets = ( + "interval" => "T", + "followAlarms" => "F" +); + +# These are attributes +my %attrs = ( +); + +#-- some globals needed for the 1-Wire module +my $owx_serport; +#-- baud rate serial interface +my $owx_baud=9600; +#-- Debugging +my $owx_debug=0; +#-- bus master mode +my $owx_mode="undef"; +#-- bus interface +my $owx_interface=""; +#-- 8 byte 1-Wire device address +my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); +#-- List of addresses found on the bus +my @owx_devs=(); +my @owx_fams=(); +my @owx_alarm_devs=(); +#-- 16 byte search string +my @owx_search=(0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0); +#-- search state for 1-Wire bus search +my $owx_LastDiscrepancy = 0; +my $owx_LastFamilyDiscrepancy = 0; +my $owx_LastDeviceFlag = 0; + + +######################################################################################## +# +# The following subroutines are independent of the bus interface +# +######################################################################################## +# +# OWX_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWX_Initialize ($) { + my ($hash) = @_; + #-- Provider + #$hash->{Clients} = ":OWCOUNT:OWHUB:OWLCD:OWMULTI:OWSWITCH:OWTEMP:"; + $hash->{Clients} = ":OWAD:OWCOUNT:OWID:OWLCD:OWTEMP:"; + + #-- 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_Alarms - Find devices on the 1-Wire bus, +# which have the alarm flag set +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : no device present +# +######################################################################################## + +sub OWX_Alarms ($) { + my ($hash) = @_; + my @owx_alarm_names=(); + + #-- Discover all alarmed devices on the 1-Wire bus + @owx_alarm_devs=(); + my $res = OWX_First($hash,"alarm"); + while( $owx_LastDeviceFlag==0 && $res != 0){ + $res = $res & OWX_Next($hash,"alarm"); + } + if( @owx_alarm_devs == 0){ + return "OWX: No alarmed 1-Wire devices found "; + } + + #-- walk through all the devices to get their proper fhem names + foreach my $fhem_dev (sort keys %main::defs) { + #-- 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"); + foreach my $owx_dev (@owx_alarm_devs) { + #-- two pieces of the ROM ID found on the bus + my $owx_rnf = substr($owx_dev,3,12); + my $owx_f = substr($owx_dev,0,2); + my $id_owx = $owx_f.".".$owx_rnf; + + #-- skip if not in alarm list + if( $owx_dev eq $main::defs{$fhem_dev}{ROM_ID} ){ + $main::defs{$fhem_dev}{STATE} = "Alarmed"; + push(@owx_alarm_names,$main::defs{$fhem_dev}{NAME}); + } + } + } + #-- so far, so good - what do we want to do with this ? + return "OWX: Alarmed 1-Wire devices found (".join(",",@owx_alarm_names).")"; +} + +######################################################################################## +# +# OWX_Block - Send data block +# +# Parameter hash = hash of bus master, data = string to send +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub OWX_Block ($$) { + my ($hash,$data) =@_; + + if( $owx_interface eq "DS2480" ){ + return OWX_Block_2480($hash,$data); + }elsif( $owx_interface eq "DS9097" ){ + return OWX_Block_9097($hash,$data); + }else{ + Log 1,"OWX: Block called with unknown interface"; + return 0; + } +} + +######################################################################################## +# +# OWX_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 +# +######################################################################################## + +sub OWX_CRC ($) { + my ($romid) = @_; + + my @crc8_table = ( + 0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65, + 157,195, 33,127,252,162, 64, 30, 95, 1,227,189, 62, 96,130,220, + 35,125,159,193, 66, 28,254,160,225,191, 93, 3,128,222, 60, 98, + 190,224, 2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255, + 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89, 7, + 219,133,103, 57,186,228, 6, 88, 25, 71,165,251,120, 38,196,154, + 101, 59,217,135, 4, 90,184,230,167,249, 27, 69,198,152,122, 36, + 248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91, 5,231,185, + 140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205, + 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80, + 175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238, + 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115, + 202,148,118, 40,171,245, 23, 73, 8, 86,180,234,105, 55,213,139, + 87, 9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22, + 233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168, + 116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53); + + my $crc8=0; + + 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_CRC16 - Calculate the CRC16 code of a string +# +# 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); +} + +#//-------------------------------------------------------------------# + +#Aufruf der Funktion im Programm: + +#{ +#//... +# unsigned int DEVICE_CRC16=0; +# DEVICE_CRC16 = calcCRC16r (DEVICE_CRC16,chr,0xA001); +#//... +#} + + + +######################################################################################## +# +# OWX_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################## + +sub OWX_Define ($$) { + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + if(int(@a) >= 3){ + #-- 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]; + $hash->{DeviceName} = $dev; + #-- Dummy 1-Wire ROM identifier + $hash->{ROM_ID} = "FF"; + + #-- First step: open the serial device to test it + #Log 3, "OWX opening device $dev"; + $owx_serport = new Device::SerialPort ($dev); + return "OWX: Can't open $dev: $!" if(!$owx_serport); + Log 3, "OWX: opened device $dev"; + $owx_serport->reset_error(); + $owx_serport->baudrate(9600); + $owx_serport->databits(8); + $owx_serport->parity('none'); + $owx_serport->stopbits(1); + $owx_serport->handshake('none'); + $owx_serport->write_settings; + #-- sleeping for some time + select(undef,undef,undef,0.1); + #$owx_serport->close(); + + #-- Second 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()+5, "OWX_Discover", $hash,0); + + #-- Default settings + $hash->{interval} = 60; # 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->{TYPE} = "OWX"; + #$hash->{T} = "OWX"; + $hash->{STATE} = "Initialized"; + $hash->{INTERFACE} = $owx_interface; + $init_done = 1; + + #-- Intiate first alarm detection and eventually conversion in a minute or so + InternalTimer(gettimeofday() + 60, "OWX_Kick", $hash,1); + $init_done = $oid; + $hash->{STATE} = "Active"; + return undef; + } else { + #-- check syntax + return "OWX: Syntax error - must be define OWX" + } +} + +######################################################################################## +# +# OWX_Detect - Detect 1-Wire interface +# +# Method rather crude - treated as an 2480, and see whatis returned +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_Detect ($) { + my ($hash) = @_; + + my ($i,$j,$k,$l,$res,$ret,$ress); + #-- timing byte for DS2480 + OWX_Query_2480($hash,"\xC1\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"); + #$ress = "OWX: Answer was "; + #for($i=0;$i{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 present in defined devices + # even with improper family + #print " FHEM-Device = ".substr($id_fhem,3,12)." OWX discovered device ".substr($id_owx,3,12)."\n"; + 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 OWTEMP DS1820 $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 OWTEMP DS1822 $owx_rnf"); + #-- Family 10 28 = Temperature sensor, assume DS18B20 as default + }elsif( $owx_f eq "28" ){ + CommandDefine(undef,"$name OWTEMP DS18B20 $owx_rnf"); + #-- Family FF = LCD display + }elsif( $owx_f eq "FF" ){ + CommandDefine(undef,"$name OWLCD $owx_rnf"); + #-- All unknown families are ID only + } 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; + #-- 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 the bus + # TODO: IF WE HAVE MULTIPLE IO Devices ??? + 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); + 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 (".join(",",@owx_names).")"; + return "OWX: 1-Wire devices found (".join(",",@owx_names).")"; +} + +######################################################################################## +# +# OWX_First - Find the 'first' devices on the 1-Wire bus +# +# Parameter hash = hash of bus master, mode +# +# Return 1 : device found, ROM number pushed to list +# 0 : no device present +# +######################################################################################## + +sub OWX_First ($$) { + my ($hash,$mode) = @_; + + #-- clear 16 byte of search data + @owx_search=(0,0,0,0 ,0,0,0,0, 0,0,0,0, 0,0,0,0); + #-- reset the search state + $owx_LastDiscrepancy = 0; + $owx_LastDeviceFlag = 0; + $owx_LastFamilyDiscrepancy = 0; + #-- now do the search + return OWX_Search($hash,$mode); +} + +######################################################################################## +# +# OWX_Get - Implements GetFn function +# +# Parameter hash = hash of the bus master a = argument array +# +######################################################################################## + +sub OWX_Get($@) { + my ($hash, @a) = @_; + return "OWX: Get needs exactly one parameter" if(@a != 2); + + my $name = $hash->{NAME}; + my $owx_dev = $hash->{ROM_ID}; + + if( $a[1] eq "alarms") { + my $res = OWX_Alarms($hash); + #-- process result + return $res + + } elsif( $a[1] eq "devices") { + my $res = OWX_Discover($hash); + #-- process result + return $res + + } else { + return "OWX: Get with unknown argument $a[1], choose one of ". + join(",", sort keys %gets); + } +} + +######################################################################################## +# +# OWX_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_Block($hash,"\xCC\x44"); + 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_Next - Find the 'next' devices on the 1-Wire bus +# +# Parameter hash = hash of bus master, mode +# +# Return 1 : device found, ROM number in owx_ROM_ID and pushed to list (LastDeviceFlag=0) +# or only in owx_ROM_ID (LastDeviceFlag=1) +# 0 : device not found, or ot searched at all +# +######################################################################################## + +sub OWX_Next ($$) { + my ($hash,$mode) = @_; + #-- now do the search + return OWX_Search($hash,$mode); +} + +######################################################################################## +# +# OWX_Reset - Reset the 1-Wire bus +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_Reset ($) { + + my ($hash)=@_; + if( $owx_interface eq "DS2480" ){ + return OWX_Reset_2480($hash); + }elsif( $owx_interface eq "DS9097" ){ + return OWX_Reset_9097($hash); + }else{ + Log 1,"OWX: Reset called with unknown interface"; + return 0; + } +} + +######################################################################################## +# +# OWX_Search - Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +# search state. +# +# Parameter hash = hash of bus master, mode=alarm,discover or verify +# +# Return 1 : device found, ROM number in owx_ROM_ID and pushed to list (LastDeviceFlag=0) +# or only in owx_ROM_ID (LastDeviceFlag=1) +# 0 : device not found, or ot searched at all +# +######################################################################################## + +sub OWX_Search ($$) { + my ($hash,$mode)=@_; + + #-- if the last call was the last one, no search + if ($owx_LastDeviceFlag==1){ + return 0; + } + #-- 1-Wire reset + if (OWX_Reset($hash)==0){ + #-- reset the search + Log 1, "OWX: Search reset failed"; + $owx_LastDiscrepancy = 0; + $owx_LastDeviceFlag = 0; + $owx_LastFamilyDiscrepancy = 0; + return 0; + } + + #-- Here we call the device dependent part + if( $owx_interface eq "DS2480" ){ + OWX_Search_2480($hash,$mode); + }elsif( $owx_interface eq "DS9097" ){ + OWX_Search_9097($hash,$mode); + }else{ + Log 1,"OWX: Search called with unknown interface"; + return 0; + } + #--check if we really found a device + if( OWX_CRC(0)!= 0){ + #-- reset the search + Log 1, "OWX: Search CRC failed "; + $owx_LastDiscrepancy = 0; + $owx_LastDeviceFlag = 0; + $owx_LastFamilyDiscrepancy = 0; + return 0; + } + + #-- character version of device ROM_ID, first byte = family + my $dev=sprintf("%02X.%02X%02X%02X%02X%02X%02X.%02X",@owx_ROM_ID); + + #-- for some reason this does not work - replaced by another test, see below + #if( $owx_LastDiscrepancy==0 ){ + # $owx_LastDeviceFlag=1; + #} + #-- + if( $owx_LastDiscrepancy==$owx_LastFamilyDiscrepancy ){ + $owx_LastFamilyDiscrepancy=0; + } + + #-- mode was to verify presence of a device + if ($mode eq "verify") { + Log 5, "OWX: Device verified $dev"; + return 1; + #-- mode was to discover devices + } elsif( $mode eq "discover" ){ + #-- check families + my $famfnd=0; + foreach (@owx_fams){ + if( substr($dev,0,2) eq $_ ){ + #-- if present, set the fam found flag + $famfnd=1; + last; + } + } + push(@owx_fams,substr($dev,0,2)) if( !$famfnd ); + foreach (@owx_devs){ + if( $dev eq $_ ){ + #-- if present, set the last device found flag + $owx_LastDeviceFlag=1; + last; + } + } + if( $owx_LastDeviceFlag!=1 ){ + #-- push to list + push(@owx_devs,$dev); + Log 5, "OWX: New device found $dev"; + } + return 1; + + #-- mode was to discover alarm devices + } else { + for(my $i=0;$i<@owx_alarm_devs;$i++){ + if( $dev eq $owx_alarm_devs[$i] ){ + #-- if present, set the last device found flag + $owx_LastDeviceFlag=1; + last; + } + } + if( $owx_LastDeviceFlag!=1 ){ + #--push to list + push(@owx_alarm_devs,$dev); + Log 5, "OWX: New alarm device found $dev"; + } + return 1; + } +} + +######################################################################################## +# +# OWX_Set - Implements SetFn function +# +# Parameter hash , a = argument array +# +######################################################################################## + +sub OWX_Set($@) { + my ($hash, @a) = @_; + my $name = shift @a; + my $res; + + #-- First we need to find the ROM ID corresponding to the device name + my $owx_romid = $hash->{ROM_ID}; + Log 5, "OWX_Set request $name $owx_romid ".join(" ",@a); + + #-- for the selector: which values are possible + return join(" ", sort keys %sets) if(@a != 2); + return "OWX_Set: With unknown argument $a[0], choose one of " . join(" ", sort keys %sets) + if(!defined($sets{$a[0]})); + + #-- Set timer value + if( $a[0] eq "interval" ){ + #-- only values >= 15 secs allowed + if( $a[1] >= 15){ + $hash->{interval} = $a[1]; + $res = 1; + } else { + $res = 0; + } + } + + #-- 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); + 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; + #-- from search string to byte id + my $devs=$dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + #-- reset the search state + $owx_LastDiscrepancy = 64; + $owx_LastDeviceFlag = 0; + #-- now do the search + my $res=OWX_Search($hash,"verify"); + my $dev2=sprintf("%02X.%02X%02X%02X%02X%02X%02X.%02X",@owx_ROM_ID); + #-- reset the search state + $owx_LastDiscrepancy = 0; + $owx_LastDeviceFlag = 0; + #-- check result + if ($dev eq $dev2){ + return 1; + }else{ + return 0; + } +} + +######################################################################################## +# +# The following subroutines in alphabetical order are only for a DS2480 bus interface +# +######################################################################################### +# +# OWX_Block_2480 - Send data block (Fig. 6 of Maxim AN192) +# +# Parameter hash = hash of bus master, data = string to send +# +# Return response, if OK +# 0 if not OK +# +######################################################################################## + +sub OWX_Block_2480 ($$) { + my ($hash,$data) =@_; + + my $data2=""; + #-- if necessary, prepend E1 character for data mode + if( ($owx_mode ne "data") && (substr($data,0,1) ne '\xE1')) { + $data2 = "\xE1"; + } + #-- all E3 characters have to be duplicated + for(my $i=0;$i{DeviceName}; + + $owx_serport->baudrate($owx_baud); + $owx_serport->write_settings; + + if( $owx_debug > 1){ + my $res = "OWX: Sending out "; + for($i=0;$iwrite($cmd); + + Log 1, "OWX: Write incomplete $count_out ne ".(length($cmd))."" if ( $count_out != length($cmd) ); + #-- sleeping for some time + select(undef,undef,undef,0.04); + + #-- read the data + my ($count_in, $string_in) = $owx_serport->read(48); + + if( $owx_debug > 1){ + my $res = "OWX: Receiving "; + for($i=0;$i<$count_in;$i++){ + $j=int(ord(substr($string_in,$i,1))/16); + $k=ord(substr($string_in,$i,1))%16; + $res.=sprintf "0x%1x%1x ",$j,$k; + } + Log 3, $res; + } + + #-- sleeping for some time + select(undef,undef,undef,0.04); + + #$owx_serport->close(); + return($string_in); +} + +######################################################################################## +# +# OWX_Reset_2480 - Reset the 1-Wire bus (Fig. 4 of Maxim AN192) +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_Reset_2480 ($) { + + my ($hash)=@_; + my $cmd=""; + + 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){ + $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 0; + }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{DeviceName}; + + $owx_serport->baudrate($owx_baud); + $owx_serport->write_settings; + + if( $owx_debug > 1){ + my $res = "OWX: Sending out "; + for($i=0;$iwrite($cmd); + + Log 1, "OWX: Write incomplete $count_out ne ".(length($cmd))."" if ( $count_out != length($cmd) ); + #-- sleeping for some time + select(undef,undef,undef,0.01); + + #-- read the data + my ($count_in, $string_in) = $owx_serport->read(48); + + if( $owx_debug > 1){ + my $res = "OWX: Receiving "; + for($i=0;$i<$count_in;$i++){ + $j=int(ord(substr($string_in,$i,1))/16); + $k=ord(substr($string_in,$i,1))%16; + $res.=sprintf "0x%1x%1x ",$j,$k; + } + Log 3, $res; + } + + #-- sleeping for some time + select(undef,undef,undef,0.01); + + #$owx_serport->close(); + return($string_in); +} + +######################################################################################## +# +# OWX_ReadBit_9097 - Read 1 bit from 1-wire bus (Fig. 5/6 from Maxim AN214) +# +# Parameter hash = hash of bus master +# +# Return bit value +# +######################################################################################## + +sub OWX_ReadBit_9097 ($) { + my ($hash) = @_; + + #-- set baud rate to 115200 and query!!! + my $sp1="\xFF"; + $owx_baud=115200; + my $res=OWX_Query_9097($hash,$sp1); + $owx_baud=9600; + #-- process result + if( substr($res,0,1) eq "\xFF" ){ + return 1; + } else { + return 0; + } +} + +######################################################################################## +# +# OWX_Reset_9097 - Reset the 1-Wire bus (Fig. 4 of Maxim AN192) +# +# Parameter hash = hash of bus master +# +# Return 1 : OK +# 0 : not OK +# +######################################################################################## + +sub OWX_Reset_9097 ($) { + + my ($hash)=@_; + my $cmd=""; + + #-- Reset command \xF0 + $cmd="\xF0"; + #-- write 1-Wire bus + my $res =OWX_Query_9097($hash,$cmd); + #-- TODO: process result + #-- may vary between 0x10, 0x90, 0xe0 + return 1; +} + +######################################################################################## +# +# OWX_Search_9097 - Perform the 1-Wire Search Algorithm on the 1-Wire bus using the existing +# search state. +# +# Parameter hash = hash of bus master, mode=alarm,discover or verify +# +# Return 1 : device found, ROM number in owx_ROM_ID and pushed to list (LastDeviceFlag=0) +# or only in owx_ROM_ID (LastDeviceFlag=1) +# 0 : device not found, or ot searched at all +# +######################################################################################## + +sub OWX_Search_9097 ($$) { + + my ($hash,$mode)=@_; + + my ($sp1,$sp2,$response,$search_direction,$id_bit_number); + + #-- Response search data parsing operates bitwise + $id_bit_number = 1; + my $rom_byte_number = 0; + my $rom_byte_mask = 1; + my $last_zero = 0; + + #-- issue search command + $owx_baud=115200; + $sp2="\x00\x00\x00\x00\xFF\xFF\xFF\xFF"; + $response = OWX_Query_9097($hash,$sp2); + $owx_baud=9600; + #-- issue the normal search command \xF0 or the alarm search command \xEC + #if( $mode ne "alarm" ){ + # $sp1 = 0xF0; + #} else { + # $sp1 = 0xEC; + #} + + #$response = OWX_TouchByte($hash,$sp1); + + #-- clear 8 byte of device id for current search + @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + + while ( $id_bit_number <= 64) { + #loop until through all ROM bytes 0-7 + my $id_bit = OWX_TouchBit_9097($hash,1); + my $cmp_id_bit = OWX_TouchBit_9097($hash,1); + + #print "id_bit = $id_bit, cmp_id_bit = $cmp_id_bit\n"; + + if( ($id_bit == 1) && ($cmp_id_bit == 1) ){ + #print "no devices present at id_bit_number=$id_bit_number \n"; + next; + } + if ( $id_bit != $cmp_id_bit ){ + $search_direction = $id_bit; + } else { + # hä ? if this discrepancy if before the Last Discrepancy + # on a previous next then pick the same as last time + if ( $id_bit_number < $owx_LastDiscrepancy ){ + if (($owx_ROM_ID[$rom_byte_number] & $rom_byte_mask) > 0){ + $search_direction = 1; + } else { + $search_direction = 0; + } + } else { + # if equal to last pick 1, if not then pick 0 + if ($id_bit_number == $owx_LastDiscrepancy){ + $search_direction = 1; + } else { + $search_direction = 0; + } + } + # if 0 was picked then record its position in LastZero + if ($search_direction == 0){ + $last_zero = $id_bit_number; + # check for Last discrepancy in family + if ($last_zero < 9) { + $owx_LastFamilyDiscrepancy = $last_zero; + } + } + } + # print "search_direction = $search_direction, last_zero=$last_zero\n"; + # set or clear the bit in the ROM byte rom_byte_number + # with mask rom_byte_mask + #print "ROM byte mask = $rom_byte_mask, search_direction = $search_direction\n"; + if ( $search_direction == 1){ + $owx_ROM_ID[$rom_byte_number] |= $rom_byte_mask; + } else { + $owx_ROM_ID[$rom_byte_number] &= ~$rom_byte_mask; + } + # serial number search direction write bit + $response = OWX_WriteBit_9097($hash,$search_direction); + # increment the byte counter id_bit_number + # and shift the mask rom_byte_mask + $id_bit_number++; + $rom_byte_mask <<= 1; + #-- if the mask is 0 then go to new rom_byte_number and + if ($rom_byte_mask == 256){ + $rom_byte_number++; + $rom_byte_mask = 1; + } + $owx_LastDiscrepancy = $last_zero; + } + return 1; +} + +######################################################################################## +# +# OWX_TouchBit_9097 - Write/Read 1 bit from 1-wire bus (Fig. 5-8 from Maxim AN 214) +# +# Parameter hash = hash of bus master +# +# Return bit value +# +######################################################################################## + +sub OWX_TouchBit_9097 ($$) { + my ($hash,$bit) = @_; + + my $sp1; + #-- set baud rate to 115200 and query!!! + if( $bit == 1 ){ + $sp1="\xFF"; + } else { + $sp1="\x00"; + } + $owx_baud=115200; + my $res=OWX_Query_9097($hash,$sp1); + $owx_baud=9600; + #-- process result + my $sp2=substr($res,0,1); + if( $sp1 eq $sp2 ){ + return 1; + }else { + return 0; + } +} + +######################################################################################## +# +# OWX_TouchByte_9097 - Write/Read 8 bit from 1-wire bus +# +# Parameter hash = hash of bus master +# +# Return bit value +# +######################################################################################## + +sub OWX_TouchByte_9097 ($$) { + my ($hash,$byte) = @_; + + my $loop; + my $result=0; + 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; + } + #print "-----------------------\n"; + #printf "Sending byte /%02x/\n",$bytein; + #printf "Receiving byte /%02x/\n",$result; + #print "-----------------------\n"; + return $result; +} + +######################################################################################## +# +# OWX_WriteBit_9097 - Write 1 bit to 1-wire bus (Fig. 7/8 from Maxim AN 214) +# +# Parameter hash = hash of bus master +# +# Return bit value +# +######################################################################################## + +sub OWX_WriteBit_9097 ($$) { + my ($hash,$bit) = @_; + + my $sp1; + #-- set baud rate to 115200 and query!!! + if( $bit ==1 ){ + $sp1="\xFF"; + } else { + $sp1="\x00"; + } + $owx_baud=115200; + my $res=OWX_Query_9097($hash,$sp1); + $owx_baud=9600; + #-- process result + if( substr($res,0,1) eq $sp1 ){ + return 1; + } else { + return 0; + } +} + +1; diff --git a/contrib/contrib/1-Wire/21_OWAD.pm b/contrib/contrib/1-Wire/21_OWAD.pm new file mode 100644 index 000000000..bb77b8034 --- /dev/null +++ b/contrib/contrib/1-Wire/21_OWAD.pm @@ -0,0 +1,1033 @@ +######################################################################################## +# +# OWAD.pm +# +# 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 1.11 - March, 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 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 for low alarm +# attr High = measurement 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 "; + + 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]; + } 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()+1, "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<4;$i++) { + #-- name + my $cname = defined($attr{$name}{$owg_fixed[$i]."Name"}) ? $attr{$name}{$owg_fixed[$i]."Name"} : $owg_fixed[$i]."|voltage"; + my @cnama = split(/\|/,$cname); + Log 1, "OWAD: InitializeDevice with insufficient name specification $cname" + if( int(@cnama)!=2 ); + $owg_channel[$i] = $cnama[0]; + #-- unit + my $unit = defined($attr{$name}{$owg_fixed[$i]."Unit"}) ? $attr{$name}{$owg_fixed[$i]."Unit"} : "Volt|V"; + my @unarr= split(/\|/,$unit); + Log 1, "OWAD: InitializeDevice with insufficient unit specification $unit" + if( int(@unarr)!=2 ); + #-- 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 + $hash->{READINGS}{"$owg_channel[$i]"}{TYPE} = defined($cnama[1]) ? $cnama[1] : "unknown"; + $hash->{READINGS}{"$owg_channel[$i]"}{UNIT} = $unarr[0]; + $hash->{READINGS}{"$owg_channel[$i]"}{UNITABBR} = defined($unarr[1]) ? $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<4;$i++){ + $offset = $hash->{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<3 ){ + $value1 .= " "; + $value2 .= ", "; + $value3 .= ", "; + } + } + #-- STATE + $hash->{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 "$a[0] $reading => $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 "$a[0] $reading => $value"; + } + + #-- get interval + if($a[1] eq "interval") { + $value = $hash->{INTERVAL}; + return "$a[0] $reading => $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<4;$i++){ + $value .= sprintf "%s:[%4.2f,%4.2f] ",$owg_channel[$i], + $hash->{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; + $value=OWAD_FormatValues($hash); + #--logging + Log 5, $value; + $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<4;$i++ ){ + $newkeys .= " ".$owg_channel[$i]."Alarm"; + $newkeys .= " ".$owg_channel[$i]."Low"; + $newkeys .= " ".$owg_channel[$i]."High"; + } + return $newkeys ; + } + + #-- check syntax + return "OWAD: Set needs one parameter when setting this value" + if( int(@a)!=3 ); + + #-- check argument + if( !defined($sets{$a[1]}) && !($key =~ m/.*(Alarm|Low|High)/) ){ + return "OWAD: Set with unknown argument $a[1]"; + } + + #-- define vars + my $ret = undef; + my $channel = undef; + my $channo = undef; + my $factor; + my $offset; + my $condx; + my $name = $hash->{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<4;$i++){ + if( $tc eq $owg_channel[$i] ){ + $channo = $i; + $channel = $tc; + last; + } + } + } + return "OWAD: Cannot determine channel from parameter $a[1]" + if( !(defined($channo))); + + #-- set these values depending on interface type + my $interface= $hash->{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 + + $hash->{PRESENT} = 1; + 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) = @_; + + #-- For now, switch on conversion command + my $con=1; + + 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); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #=============== get the voltage reading =============================== + if( $page eq "reading"){ + #-- 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 + $select = sprintf("\x55%c%c%c%c%c%c%c%c\x3C\x0F\x00\xFF\xFF",@owx_ROM_ID); + $res= OWX_Block($master,$select); + 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 reading 8 data bytes and 2 CRC bytes + $select=sprintf("\x55%c%c%c%c%c%c%c%c\xAA\x00\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + @owx_ROM_ID); + #=============== get the alarm reading =============================== + } elsif ( $page eq "alarm" ) { + #-- issue the match ROM command \x55 and the read alarm page command + # \xAA\x10\x00 reading 8 data bytes and 2 CRC bytes + $select=sprintf("\x55%c%c%c%c%c%c%c%c\xAA\x10\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + @owx_ROM_ID); + #=============== get the status reading =============================== + } elsif ( $page eq "status" ) { + #-- issue the match ROM command \x55 and the read status memory page command + # \xAA\x08\x00 reading 8 data bytes and 2 CRC bytes + $select=sprintf("\x55%c%c%c%c%c%c%c%c\xAA\x08\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + @owx_ROM_ID); + #=============== wrong value requested =============================== + } else { + return "OWXAD: Wrong memory page requested"; + } + + #-- reset the bus + OWX_Reset($master); + #-- read the data + $res=OWX_Block($master,$select); + if( $res eq 0 ){ + return "OWXAD: Device $owx_dev not accessible in reading $page page"; + } + + #-- process results + @data=split(//,$res); + if ( (@data != 22) ){ + return "OWXAD: Device $owx_dev returns invalid data"; + } + + #=============== get the voltage reading =============================== + if( $page eq "reading"){ + for( $i=0;$i<4;$i++){ + $owg_val[$i]= int((ord($data[12+2*$i])+256*ord($data[13+2*$i]))/((1<<$owg_resoln[$i])-1) * $owg_range[$i])/1000; + } + #=============== get the alarm reading =============================== + } elsif ( $page eq "alarm" ) { + for( $i=0;$i<4;$i++){ + $owg_vlow[$i] = int(ord($data[12+2*$i])/255 * $owg_range[$i])/1000; + $owg_vhigh[$i] = int(ord($data[13+2*$i])/255 * $owg_range[$i])/1000; + } + #=============== get the status reading =============================== + } elsif ( $page eq "status" ) { + my ($sb1,$sb2); + for( $i=0;$i<4;$i++){ + $sb1 = ord($data[12+2*$i]); + $sb2 = ord($data[12+2*$i+1]); + + #-- normal operation + if( $sb1 && 128) { + #-- put into globals + $owg_mode[$i] = "input"; + $owg_resoln[$i] = ($sb1 & 15) + 1 ; + $owg_range[$i] = ($sb2 & 1) ? 5100 : 2550; + #-- low alarm disabled + if( ($sb2 & 4)==0 ){ + $owg_slow[$i] = 0; + }else { + #-- low alarm enabled and not set + if ( ($sb2 & 16)==0 ){ + $owg_slow[$i] = 1; + #-- low alarm enabled and set + }else{ + $owg_slow[$i] = 2; + } + } + #-- high alarm disabled + if( ($sb2 & 8)==0 ){ + $owg_shigh[$i] = 0; + }else { + #-- high alarm enabled and not set + if ( ($sb2 & 32)==0 ){ + $owg_shigh[$i] = 1; + #-- high alarm enabled and set + }else{ + $owg_shigh[$i] = 2; + } + } + + #-- assemble status string + $owg_status[$i] = $owg_mode[$i].", "; + $owg_status[$i] .= "disabled ," + if ( !($sb2 && 128) ); + $owg_status[$i] .= sprintf "raw range %3.1f V, ",$owg_range[$i]/1000; + $owg_status[$i] .= sprintf "resolution %d bit, ",$owg_resoln[$i]; + $owg_status[$i] .= sprintf "low alarm disabled, " + if( $owg_slow[$i]==0 ); + $owg_status[$i] .= sprintf "low alarm enabled, " + if( $owg_slow[$i]==1 ); + $owg_status[$i] .= sprintf "alarmed low, " + if( $owg_slow[$i]==2 ); + $owg_status[$i] .= sprintf "high alarm disabled" + if( $owg_shigh[$i]==0 ); + $owg_status[$i] .= sprintf "high alarm enabled" + if( $owg_shigh[$i]==1 ); + $owg_status[$i] .= sprintf "alarmed high" + if( $owg_shigh[$i]==2 ); + + } else { + $owg_mode[$i] = "output"; + #-- assemble status string + $owg_status[$i] = $owg_mode[$i].", "; + $owg_status[$i] .= ($sb1 & 64 ) ? "ON" : "OFF"; + } + } + } + return undef +} + +######################################################################################## +# +# OWXAD_SetPage - Set one page of device +# +# Parameter hash = hash of device addressed +# page = "alarm" or "status" +# +######################################################################################## + +sub OWXAD_SetPage($$) { + + my ($hash,$page) = @_; + + 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); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #=============== set the alarm values =============================== + if ( $page eq "test" ) { + #-- issue the match ROM command \x55 and the set alarm page command + # \x55\x10\x00 reading 8 data bytes and 2 CRC bytes + $select=sprintf("\x55%c%c%c%c%c%c%c%c\x55\x10\x00", + @owx_ROM_ID); + 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=sprintf("\x55%c%c%c%c%c%c%c%c\x55\x08\x00", + @owx_ROM_ID); + 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_Block($master,$select); + + #-- process results + if( $res eq 0 ){ + return "OWXAD: Device $owx_dev not accessible for writing"; + } + + return undef; +} + +1; diff --git a/contrib/contrib/1-Wire/21_OWCOUNT.pm b/contrib/contrib/1-Wire/21_OWCOUNT.pm new file mode 100644 index 000000000..af2ff6b25 --- /dev/null +++ b/contrib/contrib/1-Wire/21_OWCOUNT.pm @@ -0,0 +1,706 @@ +######################################################################################## +# +# OWCOUNT.pm BETA Version +# +# 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 +# +# +# SO FAR ONLY external counter inputs A,B are available ! Neither memory content, nor internal counters are questioned. +# +# +# 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 1.11 - March, 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 counter A,B => value for counter +# +# set interval => set period for measurement +# +# 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 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 +# +######################################################################################## +# +# 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; +#-- channel values - always the raw values from the device +my @owg_val; + +my %gets = ( + "id" => "", + "present" => "", + "interval" => "", + #"page" => "", + "counter" => "", +); + +my %sets = ( + "interval" => "" + #"page" => "" +); + +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"; + #Name = channel name + #Offset = an offset added to the reading + #Factor = a 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 "; + + 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) = @_; + + # define OWCOUNT [] [interval] + # e.g.: define flow OWCOUNT 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 "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]; + } 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}; + + #-- Initial readings + @owg_val = (0.0,0.0,0.0,0.0); + + #-- Set channel names, channel units and alarm values + for( my $i=0;$i{READINGS}{"$owg_channel[$i]"}{TYPE} = defined($cnama[1]) ? $cnama[1] : "unknown"; + $hash->{READINGS}{"$owg_channel[$i]"}{UNIT} = $unarr[0]; + $hash->{READINGS}{"$owg_channel[$i]"}{UNITABBR} = defined($unarr[1]) ? $unarr[1] : ""; + $hash->{READINGS}{"$owg_channel[$i]"}{OFFSET} = $offset; + $hash->{READINGS}{"$owg_channel[$i]"}{FACTOR} = $factor; + } + + #-- 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 +# +# Parameter hash = hash of device addressed, fs = format string +# +######################################################################################## + +sub OWCOUNT_FormatValues($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + my ($offset,$factor,$vval); + 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 + if( $factor == 1.0 ){ + $vval = ($owg_val[$i] + $offset)*$factor; + } else { + $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; + + #-- string buildup for return value and STATE + $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]); + + #-- insert comma + if( $i<3 ){ + $value1 .= " "; + $value2 .= ", "; + $value3 .= ", "; + } + } + #-- STATE + $hash->{STATE} = $value2; + + 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,$value2,$value3) = (undef,undef,undef); + my $ret = ""; + my $offset; + my $factor; + + #-- 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 "$a[0] $reading => $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 "$a[0] $reading => $value"; + } + + #-- get interval + if($a[1] eq "interval") { + $value = $hash->{INTERVAL}; + return "$a[0] $reading => $value"; + } + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- get memory page/counter according to interface type + my $interface= $hash->{IODev}->{TYPE}; + + #-- check syntax for getting counter + if( $reading eq "counter" ){ + return "OWCOUNT: get needs parameter when reading counter: " + if( int(@a)<2 ); + #-- channle may be addressed by bare channel name (A..D) or by defined channel name + return "OWCOUNT: invalid counter address, must be A or B" + if( !($a[2] =~ m/[AB]/) ); + my $page = ($a[2] eq "A") ? 14 : 15; + + #-- 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"; + } + + #-- process results + if( defined($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 + $hash->{PRESENT} = 0; + + #-- Get readings, alarms and stati 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; + $value=OWCOUNT_FormatValues($hash); + #--logging + Log 5, $value; + $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(" ", sort keys %sets); + return $newkeys ; + } + + #-- check syntax + return "OWCOUNT: Set needs one parameter when setting this value" + 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 $channel = undef; + my $channo = undef; + my $factor; + my $offset; + my $condx; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + + #-- 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; + } + +} + +######################################################################################## +# +# 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 = OWXAD +# +######################################################################################## +# +# OWXAD_GetPage - Get one memory page + counter from device +# +# Parameter hash = hash of device addressed +# page = "reading", "alarm" or "status" +# +######################################################################################## + +sub OWXCOUNT_GetPage($$) { + my ($hash,$page) = @_; + + #-- For now, switch on conversion command + my $con=1; + + 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); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #=============== 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; + print "getting page Nr. $ta2 $ ta1\n"; + $select=sprintf("\x55%c%c%c%c%c%c%c%c\xA5%c%c", + @owx_ROM_ID,$ta1,$ta2); + #-- reset the bus + OWX_Reset($master); + #-- read the data + $res=OWX_Block($master,$select); + if( $res eq 0 ){ + return "OWX: Device $owx_dev not accessible in reading $page page"; + } + + #-- process results + #print "Have received ".length($res)." bytes\n"; + + #-- get 32 bytes + $select=""; + for( $i=0;$i<42;$i++){ + $select .= "\xFF"; + } + #-- read the data + $res=OWX_Block($master,$select); + + #-- process results + #print "Have received ".length($res)." bytes\n"; + + #-- get 10 bytes + $select=""; + for( $i=0;$i<10;$i++){ + $select .= "\xFF"; + } + #-- read the data + $res=OWX_Block($master,$select); + + #-- reset the bus + OWX_Reset($master); + + #-- process results + #print "Have received ".length($res)." bytes\n"; + @data=split(//,$res); + if ( ($data[4] | $data[5] | $data[6] | $data[7]) ne "\x00" ){ + return "OWXCOUNT: Device $owx_dev returns invalid data"; + } + + #-- for now ignore memory and only use counter + + my $value = ord($data[3])*4096 + ord($data[2])*256 +ord($data[1])*16 + ord($data[0]); + #print "Value received = $value\n"; + if( $page == 14) { + $owg_val[0] = $value; + }elsif( $page == 15) { + $owg_val[1] = $value; + } + 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) = @_; + + 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); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #=============== set the alarm values =============================== + #if ( $page eq "test" ) { + #-- issue the match ROM command \x55 and the set alarm page command + # \x55\x10\x00 reading 8 data bytes and 2 CRC bytes + # $select=sprintf("\x55%c%c%c%c%c%c%c%c\x55\x10\x00", + # @owx_ROM_ID); + # + #=============== wrong page write attempt =============================== + #} else { + return "OWXCOUNT: Wrong memory page write attempt"; + #} + + OWX_Reset($master); + $res=OWX_Block($master,$select); + + #-- process results + if( $res eq 0 ){ + return "OWXCOUNT: Device $owx_dev not accessible for writing"; + } + + return undef; +} + +1; diff --git a/contrib/contrib/1-Wire/21_OWID.pm b/contrib/contrib/1-Wire/21_OWID.pm new file mode 100644 index 000000000..e71d1d23a --- /dev/null +++ b/contrib/contrib/1-Wire/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 1.11 - March, 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 "$a[0] $reading => $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 "$a[0] $reading => $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/contrib/contrib/1-Wire/21_OWLCD.pm b/contrib/contrib/1-Wire/21_OWLCD.pm new file mode 100644 index 000000000..b41d67fd8 --- /dev/null +++ b/contrib/contrib/1-Wire/21_OWLCD.pm @@ -0,0 +1,970 @@ +######################################################################################## +# +# 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 1.11 - March, 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 +# +# 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 +my $lcdcontroller = "KS0073"; +my $lcdlines = 4; +my $lcdchars = 20; + +#-- declare variables +my %gets = ( + "present" => "", + "id" => "", + "gpio" => "", + "counter" => "", + #"memory" => "", + "version" => "", + #"register" => "", + #"data" => "" +); +my %sets = ( + "icon" => "", + "line" => "", + "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); + #-- 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 "$a[0] $reading => $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 "$a[0] $reading => $value"; + } + + #-- get gpio states + if($a[1] eq "gpio") { + $value = OWXLCD_Get($hash,"gpio",0); + return "$a[0] $reading => $value"; + } + + #-- get gpio counters + if($a[1] eq "counter") { + $value = OWXLCD_Get($hash,"counter",0); + return "$a[0] $reading => $value"; + } + + #-- get EEPROM counters + if($a[1] eq "memory") { + $value = OWXLCD_Get($hash,"memory",0); + return "$a[0] $reading => $value"; + } + + #-- get version + if($a[1] eq "version") { + $value = OWXLCD_Get($hash,"version",0); + return "$a[0] $reading => $value"; + } + + #-- get register + if($a[1] eq "register") { + $value = OWXLCD_Get($hash,"register",0); + return "$a[0] $reading => $value"; + } + + #-- get data + if($a[1] eq "data") { + $value = OWXLCD_Get($hash,"data",0); + return "$a[0] $reading => $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 two parameters when setting line value: <#line> " + if( int(@a)<3 ); + $line = ($a[2] =~ m/\d/) ? $a[2] : 0; + $value = $a[3]; + for( $i=4; $i< int(@a); $i++){ + $value .= " ".$a[$i]; + } + #-- 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); + 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; + } + + #-- 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); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #-- issue the match ROM command \x55 + $select = sprintf("\x55%c%c%c%c%c%c%c%c", + @owx_ROM_ID); + #=============== 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_Block($master,$select); + #-- 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 +# page = memory page address +# +######################################################################################## + +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); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #-- issue the match ROM command \x55 + $select = sprintf("\x55%c%c%c%c%c%c%c%c", + @owx_ROM_ID); + #-- issue the match ROM command \x55 and the read scratchpad command \xBE + $select2 = $select."\xBE"; + #=============== 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 EEPROM =============================== + #}elsif ( $cmd eq "memory" ) { + # #-- issue the read EEPROM command \x37 + # $len=16; + # $select .= "\x37"; + #=============== fill scratch with version =============================== + }elsif ( $cmd eq "version" ) { + #-- issue the read version command \x41 + $select .= "\x41"; + $len = 16; + #=============== fill scratch with LCD register =============================== + #}elsif ( $cmd eq "register" ) { + # #-- issue the read LCD register command \x11 + # $select .= "\x11"; + # $len = 16; + #=============== fill scratch with LCD data =============================== + #}elsif ( $cmd eq "data" ) { + # #-- issue the read LCD data command \x13 + # $addr = 0; + # $len = 16; + # #$select .= sprintf("\x13%c",$addr); + # $select .= "\x13\x00\x10"; + #=============== wrong value requested =============================== + } else { + return "OWXLCD: Wrong get attempt"; + } + #-- write to device + OWX_Reset($master); + $res=OWX_Block($master,$select); + #-- 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); + + #-- fill according to expected length + for($i=0;$i<$len;$i++){ + $select2 .= "\xFF"; + } + #-- write to device + OWX_Reset($master); + $res=OWX_Block($master,$select2); + #-- 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); + #my $ress = "OWXLCD: Answer was "; + # for($i=0;$i{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}; + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #-- 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 + my $owx_dev = $hash->{ROM_ID}; + my $owx_rnf = substr($owx_dev,3,12); + my $owx_f = substr($owx_dev,0,2); + + #-- hash of the busmaster + my $master = $hash->{IODev}; + + my ($i,$j,$k); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + #-- issue the match ROM command \x55 + $select=sprintf("\x55%c%c%c%c%c%c%c%c",@owx_ROM_ID); + + #=============== 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_Block($master,$select); + #-- 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 + 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}; + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #-- 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 = sprintf("\x55%c%c%c%c%c%c%c%c\x10\x26",@owx_ROM_ID); + OWX_Reset($master); + $res=OWX_Block($master,$select); + + #-- SEGRAM addres to 0 = \x40, + $select = sprintf("\x55%c%c%c%c%c%c%c%c\x10\x40",@owx_ROM_ID); + #-- 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_Block($master,$select); + + #-- issue the copy scratchpad to LCD command \x48 + $select=sprintf("\x55%c%c%c%c%c%c%c%c\x48",@owx_ROM_ID); + OWX_Reset($master); + $res=OWX_Block($master,$select); + } 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 = sprintf("\x55%c%c%c%c%c%c%c%c\x10\x26",@owx_ROM_ID); + OWX_Reset($master); + $res=OWX_Block($master,$select); + + #-- SEGRAM addres to 0 = \x40 + icon address + $select = sprintf("\x55%c%c%c%c%c%c%c%c\x10%c",@owx_ROM_ID,63+$icon); + OWX_Reset($master); + $res=OWX_Block($master,$select); + + #-- data + $select = sprintf("\x55%c%c%c%c%c%c%c%c\x12%c",@owx_ROM_ID,$data); + OWX_Reset($master); + $res=OWX_Block($master,$select); + } + #-- return to normal state + $select = sprintf("\x55%c%c%c%c%c%c%c%c\x10\x20",@owx_ROM_ID); + OWX_Reset($master); + $res=OWX_Block($master,$select); + #-- 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; + + #-- 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}; + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #-- split if longer than 16 bytes, fill each with blanks + if( length($msg) > 16 ) { + $msgA = substr($msg,0,16); + $msgB = substr($msg,16,length($msg)-16); + for($i = 0;$i<32-length($msg);$i++){ + $msgB .= "\x20"; + } + } else { + $msgA = $msg; + for($i = 0;$i<16-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("\x55%c%c%c%c%c%c%c%c\x4E\%c",@owx_ROM_ID,$line*32).$msgA; + OWX_Reset($master); + $res=OWX_Block($master,$select); + + #-- issue the copy scratchpad to LCD command \x48 + $select=sprintf("\x55%c%c%c%c%c%c%c%c\x48",@owx_ROM_ID); + OWX_Reset($master); + $res3=OWX_Block($master,$select); + + #-- if second string available: + if( defined($msgB) ){ + #-- issue the match ROM command \x55 and the write scratchpad command \x4E + # followed by LCD page address and the text + $select=sprintf("\x55%c%c%c%c%c%c%c%c\x4E\%c",@owx_ROM_ID,$line*32+16).$msgB; + OWX_Reset($master); + $res2=OWX_Block($master,$select); + + #-- issue the copy scratchpad to LCD command \x48 + $select=sprintf("\x55%c%c%c%c%c%c%c%c\x48",@owx_ROM_ID); + OWX_Reset($master); + $res3=OWX_Block($master,$select); + } + + #-- process results + if( ($res eq 0) || ($res2 eq 0) ){ + return "OWLCD: Device $owx_dev not accessible for writing"; + } + + return undef; + +} + +1; diff --git a/contrib/contrib/1-Wire/21_OWTEMP.pm b/contrib/contrib/1-Wire/21_OWTEMP.pm new file mode 100644 index 000000000..70bf0aae9 --- /dev/null +++ b/contrib/contrib/1-Wire/21_OWTEMP.pm @@ -0,0 +1,821 @@ +######################################################################################## +# +# OWTEMP.pm +# +# FHEM module to commmunicate with 1-Wire temperature sensors DS1820, DS18S20, DS18B20, DS1822 +# +# Attention: This module works as a replacement for the standard 21_OWTEMP.pm, +# therefore may communicate with the 1-Wire File System OWFS, +# but also with the newer and more direct OWX module +# +# Prefixes for subroutines of this module: +# OW = General 1-Wire routines (Martin Fischer, Peter Henning) +# OWFS = 1-Wire file system (Martin Fischer) +# OWX = 1-Wire bus master interface (Peter Henning) +# +# Prof. Dr. Peter A. Henning, 2012 +# Martin Fischer, 2011 +# +# Version 1.11 - March, 2012 +# +# Setup bus device in fhem.cfg as +# +# define OWTEMP [] [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, 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 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 = measurement value for low alarm +# attr tempHigh = measurement 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 = OWTEMP +# +######################################################################################## +# +# OWTEMP_Initialize +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWTEMP_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "OWTEMP_Define"; + $hash->{UndefFn} = "OWTEMP_Undef"; + $hash->{GetFn} = "OWTEMP_Get"; + $hash->{SetFn} = "OWTEMP_Set"; + #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 ". + "stateAL stateAH ". + "tempOffset tempUnit:C,Celsius,F,Fahrenheit,K,Kelvin ". + "tempLow tempHigh"; + } + +######################################################################################## +# +# OWTEMP_Define - Implements DefFn function +# +# Parameter hash = hash of device addressed, def = definition string +# +######################################################################################## + +sub OWTEMP_Define ($$) { + my ($hash, $def) = @_; + + # define OWTEMP [] [interval] + # e.g.: define flow OWTEMP 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 "OWTEMP: Wrong syntax, must be define OWTEMP [] [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 "OWTEMP: 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, "OWTEMP: 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, "OWTEMP: Parameter [alarminterval] is obsolete now - must be set with I/O-Device" + if(int(@a) == 6); + } else { + return "OWTEMP: $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 "OWTEMP: 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, "OWTEMP: Warning, no 1-Wire I/O device found for $name." + if(!defined($hash->{IODev}->{NAME})); + $modules{OWTEMP}{defptr}{$id} = $hash; + $hash->{STATE} = "Defined"; + Log 3, "OWTEMP: Device $name defined."; + + #-- Start timer for initialization in a few seconds + InternalTimer(time()+1, "OWTEMP_InitializeDevice", $hash, 0); + + #-- Start timer for updates + InternalTimer(time()+$hash->{INTERVAL}, "OWTEMP_GetValues", $hash, 0); + + return undef; +} + +######################################################################################## +# +# OWTEMP_InitializeDevice - delayed setting of initial readings and channel names +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWTEMP_InitializeDevice($) { + my ($hash) = @_; + + my $name = $hash->{NAME}; + + $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 = -15.0; + $owg_th = 70.0; + + #-- Initialize all the display stuff + OWTEMP_FormatValues($hash); +} + +######################################################################################## +# +# OWTEMP_FormatValues - put together various format strings +# +# Parameter hash = hash of device addressed, fs = format string +# +######################################################################################## + +sub OWTEMP_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 ? + $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, "OWTEMP_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; +} + +######################################################################################## +# +# OWTEMP_Get - Implements GetFn function +# +# Parameter hash = hash of device addressed, a = argument array +# +######################################################################################## + +sub OWTEMP_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 "OWTEMP: Get argument is missing @a" + if(int(@a) != 2); + + #-- check argument + return "OWTEMP: 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 "$a[0] $reading => $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 "$a[0] $reading => $value"; + } else { + return "OWTEMP: Verification not yet implemented for interface $interface"; + } + } + + #-- get interval + if($reading eq "interval") { + $value = $hash->{INTERVAL}; + return "$a[0] $reading => $value"; + } + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- OWX interface + if( $interface eq "OWX" ){ + #-- not different from getting all values .. + $ret = OWXTEMP_GetValues($hash); + #-- OWFS interface + }elsif( $interface eq "OWFS" ){ + $ret = OWFSTEMP_GetValues($hash); + #-- Unknown interface + }else{ + return "OWTEMP: Get with wrong IODev type $interface"; + } + + #-- process results + if( defined($ret) ){ + return "OWTEMP: Could not get values from device $name, return was $ret"; + } + $hash->{PRESENT} = 1; + OWTEMP_FormatValues($hash); + + #-- return the special reading + if ($reading eq "temperature") { + return "OWTEMP: $name.temperature => ". + $hash->{READINGS}{"temperature"}{VAL}; + } elsif ($reading eq "alarm") { + return "OWTEMP: $name.alarm => L ".$hash->{READINGS}{"tempLow"}{VAL}. + " H ".$hash->{READINGS}{"tempHigh"}{VAL}; + } + return undef; +} + +####################################################################################### +# +# OWTEMP_GetValues - Updates the readings from device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWTEMP_GetValues($@) { + my $hash = shift; + + my $name = $hash->{NAME}; + my $value = ""; + my $ret = ""; + + #-- restart timer for updates + RemoveInternalTimer($hash); + InternalTimer(time()+$hash->{INTERVAL}, "OWTEMP_GetValues", $hash, 1); + + #-- reset presence + $hash->{PRESENT} = 0; + + #-- Get values according to interface type + my $interface= $hash->{IODev}->{TYPE}; + if( $interface eq "OWX" ){ + $ret = OWXTEMP_GetValues($hash); + }elsif( $interface eq "OWFS" ){ + $ret = OWFSTEMP_GetValues($hash); + }else{ + return "OWTEMP: GetValues with wrong IODev type $interface"; + } + + #-- process results + if( defined($ret) ){ + return "OWTEMP: Could not get values from device $name"; + } + $hash->{PRESENT} = 1; + $value=OWTEMP_FormatValues($hash); + #--logging + Log 5, $value; + $hash->{CHANGED}[0] = $value; + + DoTrigger($name, undef); + + return undef; +} + +####################################################################################### +# +# OWTEMP_Set - Set one value for device +# +# Parameter hash = hash of device addressed +# a = argument string +# +######################################################################################## + +sub OWTEMP_Set($@) { + my ($hash, @a) = @_; + + #-- for the selector: which values are possible + return join(" ", sort keys %sets) if(@a == 2); + #-- check syntax + return "OWTEMP: Set needs one parameter" + if(int(@a) != 3); + #-- check argument + return "OWTEMP: Set with unknown argument $a[1], choose one of ".join(",", sort keys %sets) + if(!defined($sets{$a[1]})); + + #-- define vars + my $key = $a[1]; + my $value = $a[2]; + my $ret = undef; + my $name = $hash->{NAME}; + my $model = $hash->{OW_MODEL}; + + #-- set new timer interval + if($key eq "interval") { + # check value + return "OWTEMP: Set with short interval, must be > 1" + if(int($value) < 1); + # update timer + $hash->{INTERVAL} = $value; + RemoveInternalTimer($hash); + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_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("OWTEMP: 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 = OWXTEMP_SetValues($hash,@a); + return $ret + if(defined($ret)); + #-- OWFS interface + }elsif( $interface eq "OWFS" ){ + $ret = OWFSTEMP_SetValues($hash,@a); + return $ret + if(defined($ret)); + } else { + return "OWTEMP: Set with wrong IODev type $interface"; + } + OWTEMP_FormatValues($hash); + + Log 4, "OWTEMP: Set $hash->{NAME} $key $value"; + + return undef; +} + +######################################################################################## +# +# OWTEMP_Undef - Implements UndefFn function +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWTEMP_Undef ($) { + my ($hash) = @_; + + delete($modules{OWTEMP}{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 = OWFSTEMP +# +######################################################################################## +# +# OWFSTEMP_GetValues - Get reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWFSTEMP_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; +} + +####################################################################################### +# +# OWFSTEMP_SetValues - Implements SetFn function +# +# Parameter hash = hash of device addressed +# a = argument array +# +######################################################################################## + +sub OWFSTEMP_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 = OWXTEMP +# +######################################################################################## +# +# OWXTEMP_GetValues - Get reading from one device +# +# Parameter hash = hash of device addressed +# +######################################################################################## + +sub OWXTEMP_GetValues($) { + + my ($hash) = @_; + + #-- For default, perform the conversion NOT now + my $con=1; + + #-- ID of the device + my $owx_dev = $hash->{ROM_ID}; + my $owx_rnf = substr($owx_dev,3,12); + my $owx_f = substr($owx_dev,0,2); + + #-- hash of the busmaster + my $master = $hash->{IODev}; + + my ($i,$j,$k); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #-- 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 + my $select=sprintf("\x55%c%c%c%c%c%c%c%c\x44",@owx_ROM_ID); + if( OWX_Block($master,$select) eq 0 ){ + return "OWXTEMP: Device $owx_dev not accessible"; + } + #-- conversion needs some 950 ms - 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 + my $select=sprintf("\x55%c%c%c%c%c%c%c%c\xBE\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF", + @owx_ROM_ID); + + my $res=OWX_Block($master,$select); + #-- process results + if( $res eq 0 ){ + return "OWXTEMP: Device $owx_dev not accessible in 2nd step"; + } + #my $res2 = "====> OWXTEMP Received "; + #for(my $i=0;$i<19;$i++){ + # my $j=int(ord(substr($res,$i,1))/16); + # my $k=ord(substr($res,$i,1))%16; + # $res2.=sprintf "0x%1x%1x ",$j,$k; + #} + #Log 1, $res2; + + #-- process results + my @data=split(//,$res); + #-- this must be different for the different device types + # family = 10 => DS1820, DS18S20 + if( $hash->{OW_FAMILY} eq "10" ) { + if ( (@data == 19) && (ord($data[17])>0) ){ + 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; + + #-- 2's complement form = signed bytes + if( $sign == 0 ){ + $owg_temp = int($lsb/2) + $delta; + } else { + $owg_temp = 128-(int($lsb/2) + $delta); + } + $owg_th = ord($data[12]) > 127 ? 128-ord($data[12]) : ord($data[12]); + $owg_tl = ord($data[13]) > 127 ? 128-ord($data[13]) : ord($data[13]); + return undef; + } else { + return "OWXTEMP: Device $owx_dev returns invalid data"; + } + } elsif ( ($hash->{OW_FAMILY} eq "22") || ($hash->{OW_FAMILY} eq "28") ) { + if ( (@data == 19) && (ord($data[17])>0) ){ + + my $lsb = ord($data[10]); + my $msb = ord($data[11]) & 7; + my $sign = ord($data[11]) & 248; + + #-- 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 "OWXTEMP: Device $owx_dev returns invalid data"; + } + } else { + return "OWXTEMP: Unknown device family $hash->{OW_FAMILY}\n"; + } +} + +####################################################################################### +# +# OWXTEMP_SetValues - Implements SetFn function +# +# Parameter hash = hash of device addressed +# a = argument array +# +######################################################################################## + +sub OWXTEMP_SetValues($@) { + my ($hash, @a) = @_; + + my $name = $hash->{NAME}; + + #-- ID of the device + my $owx_dev = $hash->{ROM_ID}; + my $owx_rnf = substr($owx_dev,3,12); + my $owx_f = substr($owx_dev,0,2); + + #-- hash of the busmaster + my $master = $hash->{IODev}; + + my ($i,$j,$k); + + #-- 8 byte 1-Wire device address + my @owx_ROM_ID =(0,0,0,0 ,0,0,0,0); + #-- from search string to byte id + my $devs=$owx_dev; + $devs=~s/\.//g; + for($i=0;$i<8;$i++){ + $owx_ROM_ID[$i]=hex(substr($devs,2*$i,2)); + } + + #-- define vars + my $key = $a[1]; + my $value = $a[2]; + $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("\x55%c%c%c%c%c%c%c%c\x4E%c%c\x48",@owx_ROM_ID,$thp,$tlp); + my $res=OWX_Block($master,$select); + + if( $res eq 0 ){ + return "OWXTEMP: Device $owx_dev not accessible"; + } + + #-- issue the match ROM command \x55 and the copy scratchpad command \x48 + #$select=sprintf("\x55%c%c%c%c%c%c%c%c",@owx_ROM_ID); + #$res=OWX_Block($hash,$select); + #$res=OWX_WriteBytePower($hash,"\x48"); + + #if( $res eq 0 ){ + # Log 3, "OWXTEMP_SetTemp: Device $romid not accessible in the second step"; + # return 0; + #} + + DoTrigger($name, undef) if($init_done); + return undef; +} + + + +1; diff --git a/contrib/contrib/1-Wire/Schaltplan_Aktives_1-Wire_Interface.png b/contrib/contrib/1-Wire/Schaltplan_Aktives_1-Wire_Interface.png new file mode 100644 index 000000000..5b2bf4613 Binary files /dev/null and b/contrib/contrib/1-Wire/Schaltplan_Aktives_1-Wire_Interface.png differ diff --git a/contrib/contrib/1-Wire/Schaltplan_Passives_1-Wire_Interface.png b/contrib/contrib/1-Wire/Schaltplan_Passives_1-Wire_Interface.png new file mode 100644 index 000000000..0cd477b1d Binary files /dev/null and b/contrib/contrib/1-Wire/Schaltplan_Passives_1-Wire_Interface.png differ diff --git a/contrib/contrib/1-Wire/Schaltplan_USB_1-Wire_Interface.png b/contrib/contrib/1-Wire/Schaltplan_USB_1-Wire_Interface.png new file mode 100644 index 000000000..4c5f08cef Binary files /dev/null and b/contrib/contrib/1-Wire/Schaltplan_USB_1-Wire_Interface.png differ diff --git a/contrib/contrib/11_FHT8V.pm b/contrib/contrib/11_FHT8V.pm new file mode 100755 index 000000000..f5d1c7840 --- /dev/null +++ b/contrib/contrib/11_FHT8V.pm @@ -0,0 +1,247 @@ +############################################# +package main; + +use strict; +use warnings; + +use vars qw(%fht8v_c2b); # would Peter like to access it from outside too? ;-) + +# defptr{XMIT BTN}{DEVNAME} -> Ptr to global defs entry for this device +my %defptr; + +# my %follow; + +sub +FHT8V_Initialize($) +{ + my ($hash) = @_; + +# $hash->{Match} = "^([0-9]{2}:2[0-9A-F]{3} )*([0-9]{2}:2[0-9A-F]{3})\$"; + $hash->{SetFn} = "FHT8V_Set"; + $hash->{DefFn} = "FHT8V_Define"; + $hash->{UndefFn} = "FHT8V_Undef"; + $hash->{AttrList} = "IODev do_not_notify:1,0 dummy:1,0 showtime:1,0 loglevel:0,1,2,3,4,5,6"; + +} + + +################################### +sub FHT8V_valve_position(@) +{ + my ($hash, @a) = @_; + my $na = int(@a); + my $v; + + my $arg2_percent=0; + if ( $na > 3 ) { + $arg2_percent=$a[3] eq "%"; + } + if ( $a[2] =~ m/^[0-9]{1,3}%$/ || $a[2] =~ m/^[0-9]{1,3}$/ && $arg2_percent ) { + my $num; + if ( $arg2_percent ) { + $num=$a[2]; + } else { + $num=substr($a[2],0,-1); + } + return "Out of range." if ( $num > 100 || $num < 0 ); + $num=255 if ( $num == 100 ); + $v=sprintf("%.0f",2.56*$num); + } else { + return "Argument hast invalid value \"$a[2]\"." if ( $a[2] !~ m/^[0-9]{1,3}$/ ); + return "Out of range. Range: 0..255." if ( $a[2] > 255 || $a[2] < 0 ); + $v = $a[2]; + } + + Log GetLogLevel($a[2],2), "FHT8V $a[0]: v: $v"; + + IOWrite($hash, "", sprintf("T".$hash->{XMIT}."%02X26%02X",$hash->{NO}, $v)) # CUL hack + if($hash->{IODev} && $hash->{IODev}->{TYPE} eq "CUL"); + + $hash->{STATE}=sprintf("%d%%", $v*0.390625); + return undef; +} + +sub FHT8V_beep(@) +{ + my ($hash, @a) = @_; + + IOWrite($hash, "", sprintf("T".$hash->{XMIT}."%02X2E00",$hash->{NO})) # CUL hack + if($hash->{IODev} && $hash->{IODev}->{TYPE} eq "CUL"); + + $hash->{STATE}="beep"; + return undef; +} + +sub FHT8V_open(@) +{ + my ($hash, @a) = @_; + + IOWrite($hash, "", sprintf("T".$hash->{XMIT}."%02X2100",$hash->{NO})) # CUL hack + if($hash->{IODev} && $hash->{IODev}->{TYPE} eq "CUL"); + + $hash->{STATE}="open"; + return undef; +} + +sub FHT8V_off(@) +{ + my ($hash, @a) = @_; + + IOWrite($hash, "", sprintf("T".$hash->{XMIT}."%02X2000",$hash->{NO})) # CUL hack + if($hash->{IODev} && $hash->{IODev}->{TYPE} eq "CUL"); + + $hash->{STATE}="off"; + return undef; +} + +sub FHT8V_close(@) +{ + my ($hash, @a) = @_; + + IOWrite($hash, "", sprintf("T".$hash->{XMIT}."%02X2200",$hash->{NO})) # CUL hack + if($hash->{IODev} && $hash->{IODev}->{TYPE} eq "CUL"); + + $hash->{STATE}="close"; + return undef; +} + +sub +FHT8V_assign(@) +{ + my ($hash, @a) = @_; + my $na = int(@a); + my $v = 0; + + if ( $na > 2 ) { + return "Parameter \"".$a[3]."\" defining offset must be numerical." if ( $a[3] !~ /[0-9]+/ ); + $v=int($a[3]); + } + IOWrite($hash, "", sprintf("T".$hash->{XMIT}."%02X2F%02X",$hash->{NO},$v)) # CUL hack + if($hash->{IODev} && $hash->{IODev}->{TYPE} eq "CUL"); + + # not sure if this is nessesary but I saw it in the documentation... + IOWrite($hash, "", sprintf("T".$hash->{XMIT}."%02X2600",$hash->{NO},$v)) # CUL hack + if($hash->{IODev} && $hash->{IODev}->{TYPE} eq "CUL"); + $hash->{STATE}="assigning"; + return undef; +} + +sub +FHT8V_Set($@) +{ + my ($hash, @a) = @_; + my $na = int(@a); + + return "Parameter missing" if ( $na < 2 ); + if ( $_[2] eq "valve" ) { + return FHT8V_valve_position(@_); + } + if ( $_[2] eq "open" ) { + return FHT8V_open(@_); + } + if ( $_[2] eq "close" ) { + return FHT8V_close(@_); + } + if ( $_[2] eq "beep" ) { + return FHT8V_beep(@_); + } + if ( $_[2] eq "assign" ) { + return FHT8V_assign(@_); + } + if ( $_[2] eq "off" ) { + return FHT8V_off(@_); + } + return "Could not set undefined parameter \"".$_[2]."\"."; +} + + +############################# +sub +FHT8V_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + my $na = int(@a); + + my $u = "wrong syntax: define FHT8V housecode " . + "addr"; + + return $u if( $na < 3 ); + return "Define $a[0]: wrong housecode format: specify a 4 digit hex value ". + "or an 8 digit quad value" + if( ($a[2] !~ m/^[a-f0-9]{4}$/i) && ($a[2] !~ m/^[1-4]{8}$/i) ); + + if ( $na > 3 ) { + return "Define $a[0]: wrong valve address format: specify a 2 digit hex value " . + "or a 4 digit quad value" + if( ($a[3] !~ m/^[a-f0-9]{2}$/i) && ($a[3] !~ m/^[1-4]{4}$/i) ); + } + + my $housecode = $a[2]; + $housecode = four2hex($housecode,4) if (length($housecode) == 8); + + my $valve_number = 1; + if ( $na > 3 ) { + my $valve_number = $a[3]; + $valve_number = four2hex($valve_number,2) if (length($valve_number) == 4); + } + + $hash->{XMIT} = lc($housecode); + $hash->{NO} = lc($valve_number); + + my $code = "$housecode $valve_number"; + my $ncode = 1; + my $name = $a[0]; + + $hash->{CODE}{$ncode++} = $code; + $defptr{$code}{$name} = $hash; + + for(my $i = 4; $i < int(@a); $i += 2) { + + return "No address specified for $a[$i]" if($i == int(@a)-1); + + $a[$i] = lc($a[$i]); + if($a[$i] eq "fg") { + return "Bad fg address for $name, see the doc" + if( ($a[$i+1] !~ m/^f[a-f0-9]$/) && ($a[$i+1] !~ m/^44[1-4][1-4]$/)); + } elsif($a[$i] eq "lm") { + return "Bad lm address for $name, see the doc" + if( ($a[$i+1] !~ m/^[a-f0-9]f$/) && ($a[$i+1] !~ m/^[1-4][1-4]44$/)); + } elsif($a[$i] eq "gm") { + return "Bad gm address for $name, must be ff" + if( ($a[$i+1] ne "ff") && ($a[$i+1] ne "4444")); + } else { + return $u; + } + + my $grpcode = $a[$i+1]; + if (length($grpcode) == 4) { + $grpcode = four2hex($grpcode,2); + } + + $code = "$housecode $grpcode"; + $hash->{CODE}{$ncode++} = $code; + $defptr{$code}{$name} = $hash; + } + $hash->{TYPE}="FHT8V"; + AssignIoPort($hash); +} + +############################# +sub +FHT8V_Undef($$) +{ + my ($hash, $name) = @_; + foreach my $c (keys %{ $hash->{CODE} } ) { + $c = $hash->{CODE}{$c}; + + # As after a rename the $name my be different from the $defptr{$c}{$n} + # we look for the hash. + foreach my $dname (keys %{ $defptr{$c} }) { + delete($defptr{$c}{$dname}) if($defptr{$c}{$dname} == $hash); + } + } + return undef; +} + +1; diff --git a/contrib/contrib/21_OWTEMP.pm.fork b/contrib/contrib/21_OWTEMP.pm.fork new file mode 100644 index 000000000..08818e2a4 --- /dev/null +++ b/contrib/contrib/21_OWTEMP.pm.fork @@ -0,0 +1,531 @@ +################################################################ +# +# Copyright notice +# +# (c) 2009 Copyright: Martin Fischer (m_fischer at gmx dot de) +# All rights reserved +# +# This script free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +################################################################ +package main; + +use strict; +use warnings; +use Time::HiRes qw(gettimeofday); +use OW; + +my %gets = ( + "address" => "", + "alias" => "", + "crc8" => "", + "family" => "10", + "id" => "", + "locator" => "", + "power" => "", + "present" => "", +# "r_address" => "", +# "r_id" => "", +# "r_locator" => "", + "temperature" => "", + "temphigh" => "", + "templow" => "", + "type" => "", +); + +my %sets = ( + "alias" => "", + "temphigh" => "", + "templow" => "", + "interval" => "", + "alarminterval" => "", +); + +my %updates = ( + "present" => "", + "temperature" => "", + "templow" => "", + "temphigh" => "", +); + +my %dummy = ( + "crc8" => "4D", + "alias" => "dummy", + "locator" => "FFFFFFFFFFFFFFFF", + "power" => "0", + "present" => "1", + "temphigh" => "75", + "templow" => "10", + "type" => "DS18S20", + "warnings" => "none", +); + +##################################### +sub +OWTEMP_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "OWTEMP_Define"; + $hash->{UndefFn} = "OWTEMP_Undef"; + $hash->{GetFn} = "OWTEMP_Get"; + $hash->{SetFn} = "OWTEMP_Set"; + $hash->{AttrList}= "IODev do_not_notify:0,1 showtime:0,1 model:DS18S20 loglevel:0,1,2,3,4,5"; +} + +##################################### +sub +OWTEMP_UpdateReading($$$$) +{ + my ($hash,$reading,$now,$value) = @_; + + # define vars + my $temp; + + # exit if empty value + return 0 + if(!defined($value) || $value eq ""); + + # trim value + $value =~ s/\s//g + if($reading ne "warnings"); + if($reading eq "temperature") { + $value = sprintf("%.4f",$value); + $temp = $value; + $value = $value . " (".$hash->{OW_SCALE}.")"; + } + + # update readings + $hash->{READINGS}{$reading}{TIME} = $now; + $hash->{READINGS}{$reading}{VAL} = $value; + Log 4, "OWTEMP $hash->{NAME} $reading: $value"; + + return $value; +} + +##################################### +sub +OWTEMP_GetUpdate($$) +{ + my ($hash, $a) = @_; + + # define vars + my $name = $hash->{NAME}; + my $now = TimeNow(); + my $value = ""; + my $temp = ""; + my $ret = ""; + my $count = 0; + + # define warnings + my $warn = "none"; + $hash->{ALARM} = "0"; + + # check for real sensor + if($hash->{OW_ID} ne "none") { + # real sensor + + if(!$hash->{LOCAL} || $a eq "") { + + ##################### + # OW::Get is too slow: do it in the background by fork. After receiving + # the data from the OW module, the child contacts the parent, and calls + # "set childupdate ", which in turn will call this function + # again with a filled CHILDDATA + + if(!$hash->{CHILDDATA}) { + if($hash->{CHILDPID}) { + Log 2, "OWTEMP: Child already forked: timeout too short?"; + return; + } + + return if(($hash->{CHILDPID} = fork)); + + my @ret; + foreach my $r (sort keys %updates) { + my $ret = OW::get("/uncached/".$hash->{OW_PATH}."/".$r); + $ret = "" if(!defined($ret)); + push(@ret, $ret); + last if($ret eq ""); + } + my @port = split(" ", $attr{global}{port}); + my $server = IO::Socket::INET->new(PeerAddr => "localhost:$port[0]"); + Log 0, "OWTEMP: Can't connect to parent\n" if(!$server); + syswrite($server, "set $hash->{NAME} childupdate ".join(":",@ret)."\n"); + exit(0); + + } else { + + ##################### + # Digest the data sent by the CHILD. + my @ret = split(":", $hash->{CHILDDATA}); + delete($hash->{CHILDPID}); + delete($hash->{CHILDDATA}); + foreach my $r (sort keys %updates) { + $ret = shift(@ret); + if($ret eq "") { + # + $hash->{PRESENT} = "0"; + $r = "present"; + $value = "0"; + $ret = OWTEMP_UpdateReading($hash,$r,$now,$value); + $hash->{CHANGED}[$count] = "present: ".$value + } else { + $hash->{PRESENT} = "1"; + $value = $ret; + if($r eq "temperature") { + $temp = sprintf("%.4f",$value); + $temp =~ s/\s//g; + } + $ret = OWTEMP_UpdateReading($hash,$r,$now,$value); + } + last if($hash->{PRESENT} eq "0"); + } + } + } else { + $ret = ""; + $ret = OW::get("/uncached/".$hash->{OW_PATH}."/".$a); + if(!defined($ret)) { + $hash->{PRESENT} = "0"; + $a = "present"; + $value = "0"; + $ret = OWTEMP_UpdateReading($hash,$a,$now,$value); + } else { + $hash->{PRESENT} = "1"; + $value = $ret; + if($a eq "temperature") { + $temp = sprintf("%.4f",$value); + $temp =~ s/\s//g; + $value = $temp; + } + $ret = OWTEMP_UpdateReading($hash,$a,$now,$value); + } + } + } else { + # dummy sensor + $temp = sprintf("%.4f",rand(85)); + $dummy{temperature} = $temp; + $dummy{present} = "1"; + $hash->{PRESENT} = $dummy{present}; + + if(!$hash->{LOCAL} || $a eq "") { + foreach my $r (sort keys %updates) { + $ret = OWTEMP_UpdateReading($hash,$r,$now,$dummy{$r}); + } + } else { + $ret = ""; + $ret = $dummy{$a}; + if($ret ne "") { + $value = $ret; + if($a eq "temperature") { + $temp = sprintf("%.4f",$value); + $temp =~ s/\s//g; + } + $ret = OWTEMP_UpdateReading($hash,$a,$now,$value); + } + } + } + + return 1 + if($hash->{LOCAL} && $a eq "" && $hash->{PRESENT} eq "0"); + + # check for warnings + my $templow = $hash->{READINGS}{templow}{VAL}; + my $temphigh = $hash->{READINGS}{temphigh}{VAL}; + + if($hash->{PRESENT} eq "1") { + if($temp <= $templow) { + # low temperature + $hash->{ALARM} = "1"; + $warn = "templow"; + } elsif($temp >= $temphigh) { + # high temperature + $hash->{ALARM} = "1"; + $warn = "temphigh"; + } + } else { + # set old state + $temp = $hash->{READINGS}{temperature}{VAL}; + ($temp,undef) = split(" ",$temp); + # sensor is missing + $hash->{ALARM} = "1"; + $warn = "not present"; + } + + if(!$hash->{LOCAL} || $a eq "") { + $ret = OWTEMP_UpdateReading($hash,"warnings",$now,$warn); + } + + $hash->{STATE} = "T: ".$temp." ". + "L: ".$templow." ". + "H: ".$temphigh." ". + "P: ".$hash->{PRESENT}." ". + "A: ".$hash->{ALARM}." ". + "W: ".$warn; + + # inform changes + # state + $hash->{CHANGED}[$count++] = $hash->{STATE}; + # present + $hash->{CHANGED}[$count++] = "present: ".$hash->{PRESENT} + if(defined($hash->{PRESENT}) && $hash->{PRESENT} ne ""); + # temperature + $hash->{CHANGED}[$count++] = "temperature: ".$temp." (".$hash->{OW_SCALE}.")" + if(defined($temp) && $temp ne ""); + # temperature raw + $hash->{CHANGED}[$count++] = "tempraw: ".$temp + if(defined($temp) && $temp ne ""); + # low temperature + $hash->{CHANGED}[$count++] = "templow: ".$templow + if(defined($templow) && $templow ne ""); + # high temperature + $hash->{CHANGED}[$count++] = "temphigh: ".$temphigh + if(defined($temphigh) && $temphigh ne ""); + # warnings + $hash->{CHANGED}[$count++] = "warnings: ".$warn + if(defined($warn) && $warn ne ""); + + + if(!$hash->{LOCAL}) { + # update timer + RemoveInternalTimer($hash); + # check alarm + if($hash->{ALARM} eq "0") { + $hash->{INTERVAL} = $hash->{INTV_CHECK}; + } else { + $hash->{INTERVAL} = $hash->{INTV_ALARM}; + } + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 1); + } else { + return $value; + } + + if(!$hash->{LOCAL}) { + DoTrigger($name, undef) if($init_done); + } + + return $hash->{STATE}; +} + +##################################### +sub +OWTEMP_Get($@) +{ + my ($hash, @a) = @_; + + # check syntax + return "argument is missing @a" + if(int(@a) != 2); + # check argument + return "Unknown argument $a[1], choose one of ".join(",", sort keys %gets) + if(!defined($gets{$a[1]})); + + # define vars + my $value; + + # get value + $hash->{LOCAL} = 1; + $value = OWTEMP_GetUpdate($hash,$a[1]); + delete $hash->{LOCAL}; + + my $reading = $a[1]; + + if(defined($hash->{READINGS}{$reading})) { + $value = $hash->{READINGS}{$reading}{VAL}; + } + + return "$a[0] $reading => $value"; +} + +##################################### +sub +OWTEMP_Set($@) +{ + my ($hash, @a) = @_; + + # check syntax + return "set needs one parameter" + if(int(@a) != 3); + # check arguments + return "Unknown argument $a[1], choose one of ".join(",", sort keys %sets) + if(!defined($sets{$a[1]}) && $a[1] ne "childupdate"); + + # define vars + my $key = $a[1]; + my $value = $a[2]; + my $ret; + + if($key eq "childupdate") { + $hash->{CHILDDATA} = $value; + OWTEMP_GetUpdate($hash,undef); + return undef; + } + + # set new timer + if($key eq "interval" || $key eq "alarminterval") { + $key = "INTV_CHECK" + if($key eq "interval"); + $key = "INTV_ALARM" + if($key eq "alarminterval"); + # update timer + $hash->{$key} = $value; + RemoveInternalTimer($hash); + # check alarm + if($hash->{ALARM} eq "0") { + $hash->{INTERVAL} = $hash->{INTV_CHECK}; + } else { + $hash->{INTERVAL} = $hash->{INTV_ALARM}; + } + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 1); + } + + # set warnings + if($key eq "templow" || $key eq "temphigh") { + # check range + return "wrong value: range -55°C - 125°C" + if(int($value) < -55 || int($value) > 125); + } + + # set value + Log 4, "OWTEMP set $hash->{NAME} $key $value"; + + # check for real sensor + if($hash->{OW_ID} ne "none") { + # real senson + $ret = OW::put($hash->{OW_PATH}."/$key",$value); + } else { + # dummy sensor + $dummy{$key} = $value; + } + + # update readings + if($key ne "interval" || $key ne "alarminterval") { + $hash->{LOCAL} = 1; + $ret = OWTEMP_GetUpdate($hash,$key); + delete $hash->{LOCAL}; + } + + return undef; +} + +##################################### +sub +OWTEMP_Define($$) +{ + my ($hash, $def) = @_; + + # define OWTEMP [interval] [alarminterval] + # e.g.: define flow OWTEMP 332670010800 300 + + my @a = split("[ \t][ \t]*", $def); + + # check syntax + return "wrong syntax: define OWTEMP [interval] [alarminterval]" + if(int(@a) < 2 && int(@a) > 5); + # check ID format + return "Define $a[0]: missing ID or wrong ID format: specify a 12 digit value or set it to none for demo mode" + if(lc($a[2]) ne "none" && lc($a[2]) !~ m/^[0-9|a-f]{12}$/); + + # define vars + my $name = $a[0]; + my $id = $a[2]; + my $interval = 300; + my $alarminterval = 300; + my $scale = ""; + my $ret = ""; + + # overwrite default intervals if set by define + if(int(@a)==4) { $interval = $a[3]; } + if(int(@a)==5) { $interval = $a[3]; $alarminterval = $a[4] } + + # define device internals + $hash->{ALARM} = 0; + $hash->{INTERVAL} = $interval; + $hash->{INTV_CHECK} = $interval; + $hash->{INTV_ALARM} = $alarminterval; + $hash->{OW_ID} = $id; + $hash->{OW_FAMILY} = $gets{family}; + $hash->{OW_PATH} = $hash->{OW_FAMILY}.".".$hash->{OW_ID}; + $hash->{PRESENT} = 0; + + $modules{OWTEMP}{defptr}{$a[2]} = $hash; + + # assign IO port + AssignIoPort($hash); + return "No I/O device found. Please define a OWFS device first." + if(!defined($hash->{IODev}->{NAME})); + + # get scale from I/O device + $scale = $attr{$hash->{IODev}->{NAME}}{"temp-scale"}; + # define scale for temperature values + $scale = "Celsius" if ($scale eq "C"); + $scale = "Fahrenheit" if ($scale eq "F"); + $scale = "Kelvin" if ($scale eq "K"); + $scale = "Rankine" if ($scale eq "R"); + $hash->{OW_SCALE} = $scale; + + $hash->{STATE} = "Defined"; + + # define dummy values for testing + if($hash->{OW_ID} eq "none") { + my $now = TimeNow(); + $dummy{address} = $hash->{OW_FAMILY}.$hash->{OW_ID}.$dummy{crc8}; + $dummy{family} = $hash->{OW_FAMILY}; + $dummy{id} = $hash->{OW_ID}; + $dummy{temperature} = "80.0000 (".$hash->{OW_SCALE}.")"; + foreach my $r (sort keys %gets) { + $hash->{READINGS}{$r}{TIME} = $now; + $hash->{READINGS}{$r}{VAL} = $dummy{$r}; + Log 4, "OWTEMP $hash->{NAME} $r: ".$dummy{$r}; + } + } + + $hash->{STATE} = "Initialized"; + + # initalize + $hash->{LOCAL} = 1; + $ret = OWTEMP_GetUpdate($hash,""); + delete $hash->{LOCAL}; + + # exit if sensor is not present + return "Define $hash->{NAME}: Sensor is not reachable. Check first your 1-wire connection." + if(defined($ret) && $ret eq 1); + + if(!$hash->{LOCAL}) { + if($hash->{ALARM} eq "0") { + $hash->{INTERVAL} = $hash->{INTV_CHECK}; + } else { + $hash->{INTERVAL} = $hash->{INTV_ALARM}; + } + InternalTimer(gettimeofday()+$hash->{INTERVAL}, "OWTEMP_GetUpdate", $hash, 0); + } + + return undef; +} + +##################################### +sub +OWTEMP_Undef($$) +{ + my ($hash, $name) = @_; + + delete($modules{OWTEMP}{defptr}{$hash->{NAME}}); + RemoveInternalTimer($hash); + + return undef; +} + +1; diff --git a/contrib/contrib/64_ESA.pm b/contrib/contrib/64_ESA.pm new file mode 100644 index 000000000..fbbc57b4e --- /dev/null +++ b/contrib/contrib/64_ESA.pm @@ -0,0 +1,160 @@ +############################################## +# (c) by STefan Mayer (stefan(at)clumsy.ch) # +# # +# please feel free to contact me for any # +# changes, improvments, suggestions, etc # +# # +############################################## + +package main; + +use strict; +use warnings; + +my %codes = ( + "19fa" => "ESA2000_LED", +); + + +##################################### +sub +ESA_Initialize($) +{ + my ($hash) = @_; + +# S0119FA011E00007D6E003100000007C9 ESA2000_LED + + $hash->{Match} = "^S................................\$"; + $hash->{DefFn} = "ESA_Define"; + $hash->{UndefFn} = "ESA_Undef"; + $hash->{ParseFn} = "ESA_Parse"; + $hash->{AttrList} = "IODev do_not_notify:0,1 showtime:0,1 model:esa2000-led loglevel:0,1,2,3,4,5,6 ignore:0,1"; +} + +##################################### +sub +ESA_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "wrong syntax: define ESA CODE" if(int(@a) != 3); + $a[2] = lc($a[2]); + return "Define $a[0]: wrong CODE format: specify a 4 digit hex value" + if($a[2] !~ m/^[a-f0-9][a-f0-9][a-f0-9][a-f0-9]$/); + + + $hash->{CODE} = $a[2]; + $modules{ESA}{defptr}{$a[2]} = $hash; + AssignIoPort($hash); + return undef; +} + +##################################### +sub +ESA_Undef($$) +{ + my ($hash, $name) = @_; + delete($modules{ESA}{defptr}{$hash->{CODE}}) + if(defined($hash->{CODE}) && + defined($modules{ESA}{defptr}{$hash->{CODE}})); + return undef; +} + +##################################### +sub +ESA_Parse($$) +{ + my ($hash, $msg) = @_; + +# 0123456789012345678901234567890123456789 +# S0119FA011E00007D6E003100000007C9F9 ESA2000_LED + $msg = lc($msg); + my $seq = substr($msg, 1, 2); + my $cde = substr($msg, 3, 4); + my $dev = substr($msg, 7, 4); + my $val = substr($msg, 11, 22); + + Log 5, "ESA msg $msg"; + Log 5, "ESA seq $seq"; + Log 5, "ESA device $dev"; + Log 5, "ESA code $cde"; + + my $type = ""; + foreach my $c (keys %codes) { + $c = lc($c); + if($cde =~ m/$c/) { + $type = $codes{$c}; + last; + } + } + + if(!defined($modules{ESA}{defptr}{$dev})) { + Log 3, "Unknown ESA device $dev, please define it"; + $type = "ESA" if(!$type); + return "UNDEFINED ${type}_$dev ESA $dev"; + } + + my $def = $modules{ESA}{defptr}{$dev}; + my $name = $def->{NAME}; + return "" if(IsIgnored($name)); + + my (@v, @txt); + + if($type eq "ESA2000_LED") { + + @txt = ( "repeat", "sequence", "total_ticks", "actual_ticks", "ticks_kwh", "raw", "total_kwh", "actual_kwh" ); + + # Codierung Hex + $v[0] = int(hex($seq) / 128) ? "+" : "-"; # repeated + $v[1] = hex($seq) % 128; + $v[2] = hex(substr($val,0,8)); + $v[3] = hex(substr($val,8,4)); + $v[4] = hex(substr($val,18,4)) ^ 25; # XOR 25, whyever bit 1,4,5 are swapped?!?! + + $v[5] = sprintf("CNT: %d%s CUM: %d CUR: %d TICKS: %d", + $v[1], $v[0], $v[2], $v[3], $v[4]); + $v[6] = $v[2]/$v[4]; # calculate kW + $v[7] = $v[3]/$v[4]; # calculate kW + $val = sprintf("CNT: %d%s CUM: %0.3f CUR: %0.3f TICKS: %d", + $v[1], $v[0], $v[6], $v[7], $v[4]); + + +# $v[0] = "$v[0] (Repeated)"; +# $v[1] = "$v[1] (Sequence)"; +# $v[2] = "$v[2] (Total)"; +# $v[3] = "$v[3] (Actual)"; +# $v[4] = "$v[4] (T/kWh)"; + + } else { + + Log 3, "ESA Device $dev (Unknown type: $type)"; + return ""; + + } + + my $now = TimeNow(); + + my $max = int(@txt); + + if ( $def->{READINGS}{"sequence"}{VAL} ne $v[1] ) { + Log GetLogLevel($name,4), "ESA $name: $val"; + for( my $i = 0; $i < $max; $i++) { + $def->{READINGS}{$txt[$i]}{TIME} = $now; + $def->{READINGS}{$txt[$i]}{VAL} = $v[$i]; + $def->{CHANGED}[$i] = "$txt[$i]: $v[$i]"; + } + $def->{READINGS}{type}{TIME} = $now; + $def->{READINGS}{type}{VAL} = $type; + + $def->{STATE} = $val; + $def->{CHANGED}[$max++] = $val; + } else { + Log GetLogLevel($name,4), "(ESA/DISCARDED $name: $val)"; + return "($name)"; + } + + return $name; +} + +1; diff --git a/contrib/contrib/70_NT5000.pm b/contrib/contrib/70_NT5000.pm new file mode 100755 index 000000000..30db53037 --- /dev/null +++ b/contrib/contrib/70_NT5000.pm @@ -0,0 +1,1098 @@ +######################################################################################## +# +# NT5000.pm +# +# FHEM module to read the data from a Sunways NT5000 solar inverter +# +# Prof. Dr. Peter A. Henning, 2011 +# +# Version 1.0 - February 21, 2012 +# +# Setup as: +# define nt5000 NT5000 +# +# where nt5000 may be replaced by any name string and +# is a serial (USB) device or the keyword "emulator". +# In the latter case, a 4.5 kWP solar installation is simulated +# +# Additional attributes are defined in fhem.cfg as +# attr nt5000 room Solaranlage +# Area of solar installation +# attr nt5000 Area 32.75 +# Peak Solar Power +# attr nt5000 PSP 4.5 +# Months with erroneous readings - see line 83 ff +# attr nt5000 MERR +# Expected yields per month / year +# attr nt5000 Wx_M1 150 +# attr nt5000 Wx_M2 250 +# attr nt5000 Wx_M3 350 +# attr nt5000 Wx_M4 450 +# attr nt5000 Wx_M5 600 +# attr nt5000 Wx_M6 600 +# attr nt5000 Wx_M7 600 +# attr nt5000 Wx_M8 600 +# attr nt5000 Wx_M9 450 +# attr nt5000 Wx_M10 350 +# attr nt5000 Wx_M11 250 +# attr nt5000 Wx_M12 150 +# attr nt5000 Wx_Y 4800 +# +######################################################################################## +# +# This programm is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +######################################################################################## +package main; + +use strict; +use warnings; +use Device::SerialPort; + +#-- Prototypes to make komodo happy +use vars qw{%attr %defs}; +sub Log($$); + +#-- Line counter +my $cline=0; + +#-- These we may get on request +my %gets = ( + "reading" => "R", + "month" => "M", + "year" => "Y", + "serial" => "S", + "proto" => "P" +); + +#-- These occur in a pulldown menu as settable values +my %sets = ( + "time" => "T" +); + +#-- These we may get on request +my %attrs = ( + "Wyx" => "R", +); + + +######################################################################################## +# +# NT5000_Initialize +# +# Parameter hash +# +######################################################################################## + +sub NT5000_Initialize ($) { + my ($hash) = @_; + + $hash->{DefFn} = "NT5000_Define"; + $hash->{GetFn} = "NT5000_Get"; + $hash->{SetFn} = "NT5000_Set"; + # Area = Area of solar panels, to calculate expected output from solar irradiation + # PSP = Peak Solar Power of installation + # MERR = List of month entries that failed. Reason: Defective loggers in the NT5000 itself + # sometimes "jump" ahead in the monthly setting. Maybe singular problem of author ? + # Every pseudo-month-entry in this list means that its yield is ADDED to the FOLLOWING month + # e.g. MERR = 4 => Month value for April is wrong and should be added to the value from May, + # which is the following one. + # WxM1 .. WxM12 = Expected yield from January .. December + # WxY = Expected yield per year + $hash->{AttrList}= "Area PSP MERR ". + "Wx_M1 Wx_M2 Wx_M3 Wx_M4 Wx_M5 Wx_M6 Wx_M7 Wx_M8 Wx_M9 Wx_M10 Wx_M11 Wx_M12 ". + "Wx_Y ". + "loglevel:0,1,2,3,4,5,6"; +} + +####################################################################################### +######################################################################################## +# +# NT5000_Define - Implements DefFn function +# +# Parameter hash, definition string +# +######################################################################################## + +sub NT5000_Define($$) { + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + return "Define the serial device as a parameter, use none or emulator for a fake device" + if(@a != 3); + $hash->{STATE} = "Initialized"; + + my $dev = $a[2]; + + Log 1, "NT5000 device is none, commands will be echoed only" + if($dev eq "none"); + + Log 1, "NT5000 with emulator mode" + if($dev eq "emulator"); + + if( ($dev ne "none") && ($dev ne "emulator")) { + Log 3, "NT5000 opening device $dev"; + my $nt5000_serport = new Device::SerialPort ($dev); + return "NT5000 Can't open $dev: $!" if(!$nt5000_serport); + Log 2, "NT5000 opened device $dev"; + $hash->{USBDev} = $nt5000_serport; + sleep(1); + $nt5000_serport->close(); + + } + + $hash->{DeviceName} = $dev; + $hash->{Timer} = 60; # call every 60 seconds + $hash->{Cmd} = "reading"; # get all data, min/max unchange + $hash->{SerialNumber} = ""; + $hash->{Protocol} = ""; + $hash->{Firmware} = ""; + $hash->{STATE} = "offline"; + my $tn = TimeNow(); + + #-- InternalTimer blocks if init_done is not true + my $oid = $init_done; + $init_done = 1; + NT5000_GetStatus($hash); + $init_done = $oid; + return undef; +} + +######################################################################################## +# +# NT5000_Get - Implements GetFn function +# +# Parameter hash, argument array +# +######################################################################################## + +sub NT5000_Get ($@) { +my ($hash, @a) = @_; + +return "NT5000_Get needs exactly one parameter" if(@a != 2); +my $name = $hash->{NAME}; +my $v; + +if($a[1] eq "reading") + { + $v = NT5000_GetLine($hash,"reading"); + if(!defined($v)) + { + Log GetLogLevel($name,2), "NT5000_Get $a[1] error"; + return "$a[0] $a[1] => Error"; + } + $v =~ s/[\r\n]//g; # Delete the NewLine + $hash->{READINGS}{$a[1]}{VAL} = $v; + $hash->{READINGS}{$a[1]}{TIME} = TimeNow(); + } +elsif($a[1] eq "month") + { + $v = NT5000_GetLine($hash,"month"); + if(!defined($v)) + { + Log GetLogLevel($name,2), "NT5000_Get $a[1] error"; + return "$a[0] $a[1] => Error"; + } + $v =~ s/[\r\n]//g; # Delete the NewLine + $hash->{READINGS}{$a[1]}{VAL} = $v; + $hash->{READINGS}{$a[1]}{TIME} = TimeNow(); + } + elsif($a[1] eq "year") + { + $v = NT5000_GetLine($hash,"year"); + if(!defined($v)) + { + Log GetLogLevel($name,2), "NT5000_Get $a[1] error"; + return "$a[0] $a[1] => Error"; + } + $v =~ s/[\r\n]//g; # Delete the NewLine + $hash->{READINGS}{$a[1]}{VAL} = $v; + $hash->{READINGS}{$a[1]}{TIME} = TimeNow(); + } +else + { + return "NT5000_Get with unknown argument $a[1], choose one of " . join(",", sort keys %gets); + } + +Log GetLogLevel($name,3), "NT5000_Get $a[1] $v"; +return "$a[0] $a[1] => $v"; +} + +######################################################################################## +# +# NT5000_Set - Implements SetFn function +# +# Parameter hash, a = argument array +# +######################################################################################## + +sub NT5000_Set ($@) { + my ($hash, @a) = @_; + my $name = shift @a; + my $res; + + #-- for the selector: which values are possible + return join(" ", sort keys %sets) if(@a != 2); + return "NT5000_Set: With unknown argument $a[0], choose one of " . join(" ", sort keys %sets) + if(!defined($sets{$a[0]})); + + #-- Set time value + if( $a[0] eq "time" ){ + #-- only values >= 15 secs allowed + if( $a[1] >= 15){ + $res = "not yet implemented"; + } else { + $res = "not yet implemented"; + } + Log GetLogLevel($name,3), "NT5000_Set $name ".join(" ",@a)." => $res"; + DoTrigger($name, undef) if($init_done); + return "NT5000_Set => $name ".join(" ",@a)." => $res"; + } +} + +######################################################################################## +# +# NT5000 - GetStatus - Called in regular intervals to obtain current reading +# +# Parameter hash +# +######################################################################################## + +sub NT5000_GetStatus ($) { + my ($hash) = @_; + my $name = $hash->{NAME}; + + # Call us in n minutes again. + InternalTimer(gettimeofday()+ $hash->{Timer}, "NT5000_GetStatus", $hash,1); + + # Obtain the current reading + my $result = NT5000_GetLine($hash, "reading"); + + # If one of these applies we must assume that the inverter is offline (no retry !) + # Logging only if this is a change from the previous state + if( !defined($result) ) { + Log GetLogLevel($name,1), "NT5000 cannot be read, inverter offline" if( $hash->{STATE} ne "offline" ); + #Log 3, "NT5000 cannot be read, inverter offline"; + $hash->{STATE} = "offline"; + return $hash->{STATE}; + } elsif( length($result) < 13 ){ + Log GetLogLevel($name,1), "NT5000 returns incomplete line, inverter offline" if( $hash->{STATE} ne "starting" ); + #Log 3, "NT5000 returns incomplete line"; + $hash->{STATE} = "starting"; + return $hash->{STATE}; + }else { + # we have obtained a reading: inverter is online + #Log 3, "NT5000 has answered 13 bytes"; + my $tn = TimeNow(); + my @names = ("Udc", "Idc", "Pdc", "Uac", "Iac", "Pac", "Temp", "S", "Wd", "Wtot", "Eta"); + + if( !($hash->{STATE} =~ m/.*kW/) ) { + # we have turned online recently + Log GetLogLevel($name,2), "NT5000 inverter is online"; + $hash->{STATE} = "starting"; + # Obtain the serial number and protocol + my $serial = NT5000_GetLine($hash, "serial"); + $serial =~ s/^.*S://; + $serial =~ s/[\r\n ]//g; + $hash->{SerialNumber} = "$serial"; + my $proto = NT5000_GetLine($hash, "proto"); + $proto =~ s/^.*P://; + $proto =~ s/[\r\n ]//g; + $hash->{Firmware} = substr($proto,0,1).".".substr($proto,1,1); + $hash->{Protocol} = substr($proto,2,1).".".substr($proto,4,2); + + # Obtain monthly readings in 70 seconds - only once + InternalTimer(gettimeofday()+ 20, "NT5000_GetMonth", $hash,1); + + # Obtain yearly readings in 10 seconds - only once + InternalTimer(gettimeofday()+ 40, "NT5000_GetYear", $hash,1); + + my $resmod ="header: "; + #Put a header line into the log file + for(my $i = 0; $i < int(@names); $i++) { + $resmod .= $names[$i]." "; + } + $hash->{CHANGED}[$main::cline++] = "$resmod"; + }; + + #-- Log level 5 + Log GetLogLevel($name,5), "NT5000 online result = $result"; + + # All data items in one line saves a lot of place + my $resmod = $result; + #$resmod =~ s/;/ /g; + $hash->{CHANGED}[$main::cline++] = "reading: $resmod"; + + #-- split result for writing into hash + my @data = split(' ',$result); + $hash->{STATE} = sprintf("%5.3f kW",$data[5]); + for(my $i = 0; $i < int(@names); $i++) { + # This puts individual pairs into the tabular view + $hash->{READINGS}{$names[$i]}{VAL} = $data[$i]; + $hash->{READINGS}{$names[$i]}{TIME} = $tn; + } + + DoTrigger($name, undef) if($init_done); + + $result =~ s/;/ /g; + } + + return $hash->{STATE}; +} + +######################################################################################## +# +# NT5000_GetMonth - Read monthly data from inverter +# +# Parameter hash +# +######################################################################################## + +sub NT5000_GetMonth ($) { +my ($hash) = @_; +my $name = $hash->{NAME}; + + #-- Obtain the monthly reading + my $result = NT5000_GetLine($hash, "month"); + $result =~ s/^.*M://; + $result =~ s/[\r\n ]//g; + Log GetLogLevel($name,3), "NT5000 monthly result = $result"; + $result=~ s/,/./g; + my @data = split(";", $result); + my $day = $data[0]; + + #-- Expected yield for month + my ($sec,$min,$hour,$dayn,$month,$year,$wday,$yday,$isdst) = localtime(time); + my $mex = "Wx_M".($month+1); + my $wex = $attr{$name}{$mex}; + my $wac = 0; + my $wre; + + my @names = ("W_D1","W_D2","W_D3","W_D4","W_D5","W_D6","W_D7","W_D8","W_D9","W_D10", + "W_D11","W_D12","W_D13","W_D14","W_D15","W_D16","W_D17","W_D18","W_D19","W_D20", + "W_D21","W_D22","W_D23","W_D24","W_D25","W_D26","W_D27","W_D28","W_D29","W_D30","W_D31"); + + my $yearn = $year+1900; + my $monn = $month+1; + + #-- we are not sure, if this is logged now or in the next minute, hence an "or" in the regexp + $hash->{LASTM}=sprintf("%4d-%02d-%02d_%02d:(%02d|%02d)",$yearn,$monn,$day,$hour,$min,$min+1); + + for(my $i = 0; $i < $day; $i++) + { + + my $dayn = $i+1; + my $daten = $yearn."-".$monn."-".$dayn."_23:59:59"; + $wac += $data[$day-$i]; + if( $wex ) + { + $wre = int(1000*$wac/$wex)/10 if ($wex>0 ); + }; + # Put one item per line into the log file + # +1 necessary - otherwise will be overridden by the changes in the daily readings + $main::cline++; + $hash->{CHANGED}[$main::cline++] = "$names[$i]: $daten $data[$day-$i] $wac $wre"; + # This puts individual pairs into the tabular view + #$hash->{READINGS}{$names[$i]}{TIME} = $tn; + #$hash->{READINGS}{$names[$i]}{VAL} = $data[$day-$i]; + }; +} + +######################################################################################## +# +# NT5000_GetYear - Read yearly data from inverter +# +# Parameter hash +# +######################################################################################## + +sub NT5000_GetYear ($) { +my ($hash) = @_; +my $name = $hash->{NAME}; + +#-- Obtain the yearly reading + my $result = NT5000_GetLine($hash, "year"); + $result =~ s/^.*Y://; + $result =~ s/[\r\n ]//g; + Log GetLogLevel($name,3), "NT5000 yearly result = $result"; + $result=~ s/,/./g; + my @data = split(";", $result); + + #-- Expected yield for year + my ($sec,$min,$hour,$dayn,$month,$year,$wday,$yday,$isdst) = localtime(time); + my $wex = $attr{$name}{Wx_Y}; + my $wac = 0; + my $wre; + + my @names = ("W_M01","W_M02","W_M03","W_M04","W_M05","W_M06","W_M07","W_M08","W_M09","W_M10", + "W_M11","W_M12"); + + my $yearn = $year+1900; + #-- we are not sure, if this is logged now or in the next minute, hence an "or" in the regexp + $hash->{LASTY}=sprintf("%4d-%02d-%02d_%02d:(%02d|%02d)",$yearn,$month+1,$dayn,$hour,$min,$min+1); + + for(my $i = 0; $i <= $month; $i++) { + my $monn = $i+1; + my $daten = $yearn."-".$monn."-28_23:59:59"; + my $mex = "Wx_M".($monn); + my $mmex = $attr{$name}{$mex}; + $wac += $data[$month+1-$i]; + if( $wex ) + { + $wre = int(1000.0*$wac/$wex)/10 if ($wex > 0); + }; + #-- Put one item per line into the log file + # +1 necessary - otherwise will be overridden by the changes in the daily readings + $hash->{CHANGED}[$main::cline++] = "$names[$i]: $daten $data[$month+1-$i] $mmex $wac $wre"; + #-- This puts individual pairs into the tabular view + # $hash->{READINGS}{$names[$i]}{TIME} = $tn; + # $hash->{READINGS}{$names[$i]}{VAL} = $data[$i]; + }; + } + +######################################################################################## +# +# NT5000_GetLine - Read data from inverter +# +# Parameter hash, a = string parameter to define what will be asked +# +######################################################################################## + +sub NT5000_GetLine ($$) { + my ($hash,$a) = @_; + my $name = $hash->{NAME}; + + return "NT5000_GetLine: Unknown argument $a, choose one of " . join(",", sort keys %gets) + if(!defined($gets{$a})); + + my $dev = $hash->{DeviceName}; + + #-- Inverter data + my $rError = "\x00\x01\x01\x01"; + my $rOnline1 = "\x00\x01\x02\x01"; + my $rMon1 = "\x00\x01\x03\x01"; + my $rYear1 = "\x00\x01\x04\x01"; + my $rTime = "\x00\x01\x06\x01"; + my $rSerial = "\x00\x01\x08\x01"; + my $rProFW = "\x00\x01\x09\x01"; + my $sYY = "\x00\x01\x50"; + my $sLL = "\x00\x01\x51"; + my $sDD = "\x00\x01\x52"; + my $sHH = "\x00\x01\x53"; + my $sMM = "\x00\x01\x54"; + + my @invBuffer; + + #------------------------ current readings ------------------------------------- + if( $a eq "reading" ) + { + #Log 3, "Asking for online data"; + my $invReturn = NT5000_5to13($hash,$dev,$rOnline1); + #-- test if this is an offline case + if( !($invReturn) ) + { + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) + { + return undef; + } + #-- Process data + my $udc = ord($invBuffer[0])*2.8+100; + my $idc = ord($invBuffer[1])*0.08; + my $uac = ord($invBuffer[2])+100.0; + my $iac = ord($invBuffer[3])*0.120; + my $t = ord($invBuffer[4])-40.0; + my $s = ord($invBuffer[5])*6.0; + my $pdc = int($udc*$idc)/1000; + my $pac = int($uac*$iac)/1000; + my $wd = (ord($invBuffer[6])* 256 + ord($invBuffer[7]))/1000; + my $wtot= ord($invBuffer[8])* 256 + ord($invBuffer[9]); + #-- Calculate eta + my $name= $hash->{NAME}; + my $a = $attr{$name}{Area}; + my $eta; + if ( $s && $a ) { + if( ($a>0)&&($s>0) ) { + $eta = int(100000*$pac/($s*$a))/100; + } else { + $eta = 0; + }; + } else { + $eta=0; + } + return sprintf "%3.1f %2.2f %1.3f %3.1f %2.2f %1.3f %2.0f %4.0f %2.3f %5.1f %1.2f", + $udc,$idc,$pdc,$uac,$iac,$pac,$t,$s,$wd,$wtot,$eta; +#------------------------ montly readings ------------------------------------- + } elsif( $a eq "month" ) { + my $i=1; + #-- Get the first block anyhow + my $ica=$i%256; + my $cmd2=sprintf("%s%c",substr($rMon1,0,3),$ica); + my $invReturn = NT5000_5to13($hash,$dev,$cmd2); + #-- test if this is an offline case + if( !($invReturn) ){ + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) { + return undef; + } + #-- Process data + my $day = ord($invBuffer[1]); + my $result="M:$day;"; + for( my $j=0; $j<3; $j++ ) { + $result .= ((ord($invBuffer[2+4*$j])* 256 + ord($invBuffer[3+4*$j]))/1000).";" if( ($day-$j) > 0); + } + #-- Get further blocks if necessary + for( $i=2; $i<=($day+2)/3; $i++) { + $ica=$i%256; + $cmd2=sprintf("%s%c",substr($rMon1,0,3),$ica); + $invReturn = NT5000_5to13($hash,$dev,$cmd2); + #-- test if this is an offline case + if( !($invReturn) ) { + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) { + return undef; + } + for( my $j=0; $j<3; $j++ ) { + $result .= ((ord($invBuffer[2+4*$j])* 256 + ord($invBuffer[3+4*$j]))/1000).";" if( ($day-($i-1)*3-$j) > 0); + } + }; + return "$result\n"; +#------------------------ yearly readings ------------------------------------- + } elsif( $a eq "year" ) { + my $i=1; + #-- We read the full data, e.g. current month (cm) .. cm-12 + my @pmval; + #-- Get the first block + my $ica=$i%256; + my $cmd2=sprintf("%s%c",substr($rYear1,0,3),$ica); + my $invReturn = NT5000_5to13($hash,$dev,$cmd2); + #-- test if this is an offline case + if( !($invReturn) ) { + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) { + return undef; + } + #-- Process data for current month (cm) .. cm-4 + my $month = ord($invBuffer[1]); + for( my $j=0; $j<5; $j++ ) { + #-- value for pseudo-month + push(@pmval,(ord($invBuffer[2+2*$j])* 256 + ord($invBuffer[3+2*$j]))/10); + } + #-- Get the second block + $i++; + $ica=$i%256; + $cmd2=sprintf("%s%c",substr($rYear1,0,3),$ica); + $invReturn = NT5000_5to13($hash,$dev,$cmd2); + #-- test if this is an offline case + if( !($invReturn) ) { + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) { + return undef; + } + #-- Process data for cm-5 .. cm-10 + for( my $j=0; $j<6; $j++ ) { + #-- value for pseudo-month + push(@pmval,(ord($invBuffer[2*$j])* 256 + ord($invBuffer[1+2*$j]))/10); + }; + #-- Get the third block + $i++; + $ica=$i%256; + $cmd2=sprintf("%s%c",substr($rYear1,0,3),$ica); + $invReturn = NT5000_5to13($hash,$dev,$cmd2); + #-- test if this is an offline case + if( !($invReturn) ) { + return undef; + } + @invBuffer=split(//,$invReturn); + #-- test again if this is an offline case + if( @invBuffer < 13 ) { + return undef; + } + #-- Process data for cm-11 .. cm-12 + for( my $j=0; $j<2; $j++ ) { + #-- value for pseudo-month + push(@pmval,(ord($invBuffer[2*$j])* 256 + ord($invBuffer[1+2*$j]))/10); + }; + #-- Now we have to correct for those erroneous jumps of the internal data logger + # of the NT5000 + # The first one is never wrong, belongs to the current month + my @val = ($pmval[0]); + my $result; + # which and how many pseudo-month-entries do we have ? + my $merr = $attr{$name}{MERR}; + # none, we my return HERE + if( !defined($merr) ) { + @val= ($pmval[0]); + for( my $j=1; $j<=$month; $j++ ){ + push(@val,$pmval[$j]); + } + $result = "Y:$month;".join(';',@val); + return $result; + }; + # oops, correction has to be done + my @merrs = split(',',$merr); + my $merrno = @merrs; + #-- For the year we therefore need the first $month+$merrno entries + + my $pm; + my $mlim = $month+$merrno; + for( my $j=1; $j<$mlim; $j++ ) { + $pm = $month-$j; + my $listed = 0; + #-- check if this is in the list + for( my $k=0; $k<$merrno; $k++ ) { + # yes, it is + if( $merrs[$k]==$pm ) { + $listed = 1; + } + } + # yes, it is indeed + if( $listed==1) { + # add data to the last entry in @val + $val[@val-1]+=$pmval[$j]; + # no, it is not + } else { + # append value to array + push(@val,$pmval[$j]); + } + }; + #-- Compare the results + #Log 3, "YEAR PSEUDO ".join(';',@pmval); + #Log 3, "YEAR CORR ".join(';',@val); + $result = "Y:$month;".join(';',@val); + return $result; + }elsif( $a eq "serial" ) { + my $r1 = NT5000_5to13($hash,$dev,$rSerial); + return "S:".substr($r1,0,12)."\n" + }elsif( $a eq "proto" ){ + my $r1 = NT5000_5to13($hash,$dev,$rProFW); + return "P:".substr($r1,0,6)."\n" + }else { + print "OHOH => NT5000_GetLine mit Argument $a\n"; + } +} + +######################################################################################## +# +# NT5000_5to13 - Read 13 bytes from the inverter or the emulator +# +# Parameter: hash,dev = none,emulator or a serial port definition +# cmd = 5 byte parameter to query the device properly +# +######################################################################################## +sub +NT5000_5to13($$$) +{ + +my $retry = 0; +my ($hash,$dev,$cmd) = @_; + +my $result; +my ($i,$j,$k); + +if( $dev eq "none" ) #no inverter attached + { + return "\x00\x01\x02\x03\x04\x05\x06\x07\x07\x09\x0a\x0b\x0c"; + + } elsif ( $dev eq "emulator" ) #emulator attached + { + #-- read from emulator + + #-- calculate checksum + my $CS = unpack("%32C*", $cmd); + $CS=$CS%256; + my $cmd2=sprintf("%s%c",$cmd,$CS); + #-- control + #print "Sending out:\n"; + #for(my $i=0;$i<5;$i++) + # { my $j=int(ord(substr($cmd2,$i,1))/16); + # my $k=ord(substr($cmd2,$i,1))%16; + # print "byte $i = 0x$j$k\n"; + # } + + my $result = NT5000_emu(5,$cmd); + #print "[I] Answer 13 bytes received\n"; + #for($i=0;$i<13;$i++) + # { $j=int(ord($invBuffer[$i])/16); + # $k=ord($invBuffer[$i])%16; + # print "byte $i = 0x$j$k\n"; + # } + return($result); + + } else # here we do the real thing + { + #Just opening the old device does not reaaly work. + #my $serport = $hash->{USBDev}; + my $serport = new Device::SerialPort ($dev); + if(!$serport) { + Log 1, "NT5000: Can't open $dev: $!"; + return undef; + } + #Log 3, "NT5000 opened"; + $serport->reset_error(); + $serport->baudrate(9600) || die "failed setting baudrate"; + $serport->databits(8) || die "failed setting databits"; + $serport->parity('none') || die "failed setting parity"; + $serport->stopbits(1) || die "failed setting stopbits"; + $serport->handshake('none') || die "failed setting handshake"; + $serport->write_settings || die "no settings"; + + #my $rm = "NT5000 timeout reading the answer"; + + #-- calculate checksum + my $CS = unpack("%32C*", $cmd); + $CS=$CS%256; + my $cmd2=sprintf("%s%c",$cmd,$CS); + #-- control + #print "Sending out:\n"; + #for(my $i=0;$i<5;$i++) + # { my $j=int(ord(substr($cmd2,$i,1))/16); + # my $k=ord(substr($cmd2,$i,1))%16; + # print "byte $i = 0x$j$k\n"; + # } + + my $count_out = $serport->write($cmd2); + Log 3, "NT5000 write failed\n" unless ($count_out); + Log 3, "NT5000 write incomplete $count_out ne ".(length($cmd2))."\n" if ( $count_out != 5 ); + #Log 3, "write complete $count_out \n" if ( $count_out == 5 ); + #-- sleeping 0.03 seconds + select(undef,undef,undef,0.05); + my ($count_in, $string_in) = $serport->read(13); + #Log 3, "NT5000 read unsuccessful, $count_in bytes \n" unless ($count_in == 13); + #Log 3, "read complete $count_in \n" if ( $count_in == 13 ); + #-- sleeping 0.03 seconds + select(undef,undef,undef,0.05); + $serport->close(); + return($string_in); + } + +} + +######################################################################################## +# +# NT5000_emu - Emulator section - to be used, if the real solar inverter is not attached. +# +######################################################################################## + +sub NT5000_emu { +#-- For configuration purpose: when does the sun come up, and when does it go down +my $start = 6 + 55.0/60; +my $stop = 21 + 31.0/60; + +#-- Inverter data + +my $rError = "\x00\x01\x01\x01"; +my $rOnline1 = "\x00\x01\x02\x01"; +my $rMon1 = "\x00\x01\x03\x01"; +my $rYear1 = "\x00\x01\x04\x01"; +my $rTime = "\x00\x01\x06\x01"; +my $rSerial = "\x00\x01\x08\x01"; +my $rProFW = "\x00\x01\x09\x01"; +my $sYY = "\x00\x01\x50"; +my $sLL = "\x00\x01\x51"; +my $sDD = "\x00\x01\x52"; +my $sHH = "\x00\x01\x53"; +my $sMM = "\x00\x01\x54"; + +#-- Timer data +my ($sec,$min,$hour,$day,$month,$year,$wday,$yday,$isdst) = localtime(time); + +#-- parse incoming parameters +my ($count,$buf1)=@_; + +#-- default: do not send data +my $senddata=0; +my ($i,$j,$k); +my (@buf3,@buf3a); + +#-- No bytes received +if( $count == 0) + { + Log 3, "[NT5000 emulator] Zero bytes received, count=0"; + return undef; + } +#-- no sun yet +if( (($hour+$min/60.0-$start)<0) || (($stop-$hour-$min/60.0)<0) ) + { + Log 3, "[NT5000 emulator] No Sun !"; + return undef; + } +#-- 5 bytes received +elsif( $count == 5) + { + my $buf2 = substr($buf1,0,4); + my $buf4 = substr($buf1,0,3); + + #---- Error + if( $buf2 eq $rError ) + { + Log 3, "[NT5000 emulator] Request for error list received"; + my @buf3=($year,$month,$day,$hour, + $min,0,0,0,0,0,0,0); + $senddata=1; + } + + #---- Online block 1 + elsif( $buf2 eq $rOnline1 ) + { + #Log 3 "[NT5000 mulator] Request for online data block 1 received"; + my ($wd,$wdl,$wdh,$wtot,$wtotl,$wtoth,$uac,$udc,$iac,$idc,$pac,$t,$s)=(0,0,0,0,0,0,0,0,0,0,0,0,0); + #-- shift into full day + my $q = ($hour+$min/60.0-$start)/($stop-$start); + if( ($q>0) && ($q<1.0) ) + { + #-- produce fake data + $wd= int( 4500/3.14*($stop-$start)*(1-cos($q*3.14)) ); + $wdl=$wd%256; + $wdh=int(($wd-$wdl)/256); + $wtot=1000+int($wd/1000); + $wtotl=$wtot%256; + $wtoth=int(($wtot-$wtotl)/256); + + $uac=int( 230-100 + 0.5 ); + $udc=int( (600-100)/2.8 + 0.5 ); + $iac=int( 4500/230*sin($q * 3.14)/0.12 + 0.5 ); + $idc=int( 4550/600*sin($q * 3.14)/0.08 + 0.5 ); + + $pac=int(4500*sin($q*3.14))/1000.0; + + $t=10+40; + $s=int(100/6); + } + @buf3=($udc,$idc,$uac,$iac,$t,$s,$wdh,$wdl,$wtoth,$wtotl,0,0); + $senddata=1; + } + + #---- Monthly data block + elsif( substr($buf2,0,3) eq substr($rMon1,0,3) ) + { + my $ica = substr($buf2,3,1); + my $blocknr = ord($ica)%16; + # Log 3, "[NT5000 emulator] Request for monthly data block $blocknr received"; + my ($mon1,$day1,$wdh1,$wdl1,$mon2,$day2,$wdh2,$wdl2,$mon3,$day3,$wdh3,$wdl3)=(0,0,0,0,0,0,0,0,0,0,0,0,0); + + #-- produce fake data + $mon1 = $month; + $day1 = $day+3-$blocknr*3; + my $wd = (19.001 + 2*($day1 % 2))*1000.0; + $wdl1 = $wd%256; + $wdh1 = int(($wd-$wdl1)/256); + $mon2 = $mon1; + $day2 = $day1-1; + if( $day2 < 1 ) + { + $mon2-- if($mon2 > 0); + $day2 = 31; + } + $wd = (19.001 + 2*($day2 % 2))*1000.0; + $wdl2 = ($wd)%256; + $wdh2 = int(($wd-$wdl2)/256); + $mon3 = $mon2; + $day3 = $day2-1; + if( $day3 < 1 ) + { + $mon3-- if($mon3 > 0); + $day3 = 31; + } + $wd = (19.001 + 2*($day3 % 2))*1000.0; + $wdl3=$wd%256; + $wdh3=int(($wd-$wdl3)/256); + + @buf3=($mon1,$day1,$wdh1,$wdl1,$mon2,$day2,$wdh2,$wdl2,$mon3,$day3,$wdh3,$wdl3); + $senddata=1; + } + + #---- Yearly data block + elsif( substr($buf2,0,3) eq substr($rYear1,0,3) ) + { + my $ica = substr($buf2,3,1); + my $blocknr = ord($ica)%16; + Log 3, "[NT5000 emulator] Request for yearly data block $blocknr received"; + my ($wmh1,$wml1,$wmh2,$wml2,$wmh3,$wml3,$wmh4,$wml4,$wmh5,$wml5)=(0,0,0,0,0,0,0,0,0,0); + my @pwm=(1500,2500,3500,4500,6000,6000,6000,6000,4500,3500,2500,1500); + + #-- produce fake data + my $ip; + @buf3=(0,0,0,0,0,0,0,0,0,0,0,0); + if( $blocknr==1){ + $buf3[1]=$month; + for( my $i=0;$i<5;$i++) + { + my $ip=$month-$i; + if( $ip<0 ) + { + $ip += 12; + }; + my $wd=$pwm[$ip]; + my $wdl = ($wd)%256; + my $wdh = int(($wd-$wdl)/256); + $buf3[2*$i+2]=$wdh; + $buf3[2*$i+3]=$wdl; + } + }elsif( $blocknr==2){ + for( my $i=0;$i<6;$i++) + { + my $ip=$month-5-$i; + if( $ip<0 ) + { + $ip += 12; + }; + my $wd=$pwm[$ip]; + my $wdl = ($wd)%256; + my $wdh = int(($wd-$wdl)/256); + $buf3[2*$i]=$wdh; + $buf3[2*$i+1]=$wdl; + } + }else{ + for( my $i=0;$i<2;$i++) + { + my $ip=$month-11-$i; + if( $ip<0 ) + { + $ip += 12; + }; + my $wd=$pwm[$ip]; + my $wdl = ($wd)%256; + my $wdh = int(($wd-$wdl)/256); + $buf3[2*$i]=$wdh; + $buf3[2*$i+1]=$wdl; + } + } + $senddata=1; + } + + #---- Time data + elsif( $buf2 eq $rTime ) + { + Log 3, "[NT5000 emulator] Request for time data received"; + @buf3=($year,$month,$day,$hour, + $min,0,0,0,0,0,0,0,0); + $senddata=1; + } + + #---- Serial number + elsif( $buf2 eq $rSerial ) + { + Log 3, "[NT5000 emulator] Request for serial number received"; + @buf3 = (ord('1'),ord('5'),ord('3'),ord('3'),ord('A'),ord('5'),ord('0'),ord('1'),ord('2'),ord('3'),ord('4'),ord('5')); + $senddata=1; + } + + #---- Protocol and Firmware + elsif( $buf2 eq $rProFW ) + { + Log 3, "[NT5000 emulator] Request for protocol version received"; + @buf3=(ord('1'),ord('1'),ord('1'),ord('-'),ord('2'),ord('3'),0,0,0,0,0,0); + $senddata=1; + } + #---- Set year + elsif( $buf4 eq $sYY ) + { + $year=ord(substr($buf2,3,1)); + Log 3, "[NT5000 eulator] Setting year to $year"; + } + #---- Set month + elsif( $buf4 eq $sLL ) + { + $month=ord(substr($buf2,3,1)); + Log 3, "[NT5000 emulator] Setting month to $month"; + } + #---- Set day + elsif( $buf4 eq $sDD ) + { + $day=ord(substr($buf2,3,1)); + Log 3, "[NT5000 emulator] Setting day to $day"; + } + #---- Set hour + elsif( $buf4 eq $sHH ) + { + $hour=ord(substr($buf2,3,1))-1; + Log 3, "[NT5000 emulator] Setting hour to $hour"; + } + #---- Set minute + elsif( $buf4 eq $sMM ) + { + $min=ord(substr($buf2,3,1))-1; + Log 3, "[NT5000 emulator] Setting minute to $min"; + } + #---- show content + else + { + Log 3, "[NT5000 emulator] Unknown request of 5 bytes received"; + for($i=0;$i<5;$i++) + { $j=int(ord(substr($buf2,$i,1))/16); + $k=ord(substr($buf2,$i,1))%16; + print "byte $i = 0x$j$k\n"; + } + } + #---- Other number of bytes received + } else + { + Log 3, "[NT5000 emulator] $count bytes received"; + for($i=0;$i<$count;$i++) + { $j=int(ord(substr($buf1,$i,1))/16); + $k=ord(substr($buf1,$i,1))%16; + print "byte $i = 0x$j$k\n"; + } + } + #-- Here we are really sending data back to the main program + if( $senddata==1 ) { + #-- calculate checksum + my $CS=0; + for($i=0,$i<=11,$i++) + { + $CS+=$buf3[$i]; + } + $CS=$CS%256; + my $data = sprintf("%c%c%c%c%c%c%c%c%c%c%c%c%c", + $buf3[0],$buf3[1],$buf3[2],$buf3[3], + $buf3[4],$buf3[5],$buf3[6],$buf3[7], + $buf3[8],$buf3[9],$buf3[10],$buf3[11],$CS); + + #-- control + #print "Sending out:"; + #for($i=0;$i<13;$i++) + # { $j=int(ord(substr($data,$i,1))/16); + # $k=ord(substr($data,$i,1))%16; + # print "byte $i = 0x$j$k\n"; + # } + $senddata=0; + return($data); + } + else + { + return undef; + } +} + + + +1; diff --git a/contrib/contrib/70_SolarView.pm b/contrib/contrib/70_SolarView.pm new file mode 100644 index 000000000..09ff46014 --- /dev/null +++ b/contrib/contrib/70_SolarView.pm @@ -0,0 +1,354 @@ +############################################################################## +# +# 70_SolarView.pm +# +# A FHEM module to read power/energy values from solarview. +# +# written 2012 by Tobe Toben +# +# $Id$ +# +############################################################################## +# +# SolarView is a powerful datalogger for photovoltaic systems that runs on +# an AVM Fritz!Box (and also on x86 systems). For details see the SV homepage: +# http://www.amhamberg.de/solarview_fritzbox.aspx +# +# SV supports many different inverters. To read the SV power values using +# this module, a TCP-Server must be enabled for SV by adding the parameter +# "-TCP " to the startscript (see the SV manual). +# +# usage: +# define SolarView [ []] +# +# If is positive, new values are read every seconds. +# If is 0, new values are read whenever a get request is called +# on . The default for is 300 (i.e. 5 minutes). +# +# get +# +# where is one of currentPower, totalEnergy, totalEnergyDay, +# totalEnergyMonth, totalEnergyYear, UDC, IDC, UDCB, IDCB, UDCC, IDCC, +# gridVoltage, gridCurrent and temperature. +# +############################################################################## +# +# Copyright notice +# +# (c) 2012 Tobe Toben +# +# This script 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. +# +# 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. +# +# This copyright notice MUST APPEAR in all copies of the script! +# +############################################################################## + +package main; + +use strict; +use warnings; + +use IO::Socket::INET; + +my @gets = ('totalEnergyDay', # kWh + 'totalEnergyMonth', # kWh + 'totalEnergyYear', # kWh + 'totalEnergy', # kWh + 'currentPower', # W + 'UDC', 'IDC', 'UDCB', # V, A, V + 'IDCB', 'UDCC', 'IDCC', # A, V, A + 'gridVoltage', 'gridCurrent', # V, A + 'temperature'); # oC + +sub +SolarView_Initialize($) +{ + my ($hash) = @_; + + $hash->{DefFn} = "SolarView_Define"; + $hash->{UndefFn} = "SolarView_Undef"; + $hash->{GetFn} = "SolarView_Get"; + $hash->{AttrList} = "loglevel:0,1,2,3,4,5"; +} + +sub +SolarView_Define($$) +{ + my ($hash, $def) = @_; + + my @args = split("[ \t]+", $def); + + if (int(@args) < 4) + { + return "SolarView_Define: too few arguments. Usage:\n" . + "define SolarView [ []]"; + } + + $hash->{Host} = $args[2]; + $hash->{Port} = $args[3]; + $hash->{Interval} = int(@args) >= 5 ? int($args[4]) : 300; + $hash->{Timeout} = int(@args) >= 6 ? int($args[5]) : 4; + + # config variables + $hash->{Invalid} = -1; # default value for invalid readings + $hash->{Sleep} = 0; # seconds to sleep before connect + $hash->{Debounce} = 50; # minimum level for debouncing (0 to disable) + $hash->{Rereads} = 2; # number of retries when reading curPwr of 0 + $hash->{NightOff} = 'yes'; # skip connection to SV at night? + $hash->{UseSVNight} = 'yes'; # use the on/off timings from SV (else: SUNRISE_EL) + $hash->{UseSVTime} = ''; # use the SV time as timestamp (else: TimeNow()) + + # internal variables + $hash->{Debounced} = 0; + + $hash->{STATE} = 'Initializing'; + + my $timenow = TimeNow(); + + for my $get (@gets) + { + $hash->{READINGS}{$get}{VAL} = $hash->{Invalid}; + $hash->{READINGS}{$get}{TIME} = $timenow; + } + + SolarView_Update($hash); + + Log 2, "$hash->{NAME} will read from solarview at $hash->{Host}:$hash->{Port} " . + ($hash->{Interval} ? "every $hash->{Interval} seconds" : "for every 'get $hash->{NAME} ' request"); + + return undef; +} + +sub +SolarView_Update($) +{ + my ($hash) = @_; + + if ($hash->{Interval} > 0) { + InternalTimer(gettimeofday() + $hash->{Interval}, "SolarView_Update", $hash, 0); + } + + # if NightOff is set and there has been a successful + # reading before, then skip this update "at night" + # + if ($hash->{NightOff} and SolarView_IsNight($hash) and + $hash->{READINGS}{currentPower}{VAL} != $hash->{Invalid}) + { + $hash->{STATE} = '0 W, '.$hash->{READINGS}{totalEnergyDay}{VAL}.' kWh (Night)'; + return undef; + } + + sleep($hash->{Sleep}) if $hash->{Sleep}; + + Log 4, "$hash->{NAME} tries to contact solarview at $hash->{Host}:$hash->{Port}"; + + my $success = 0; + my %readings = (); + my $timenow = TimeNow(); + my $rereads = $hash->{Rereads}; + + eval { + local $SIG{ALRM} = sub { die 'timeout'; }; + alarm $hash->{Timeout}; + + READ_SV: + my $socket = IO::Socket::INET->new(PeerAddr => $hash->{Host}, + PeerPort => $hash->{Port}, + Timeout => $hash->{Timeout}); + + if ($socket and $socket->connected()) + { + $socket->autoflush(1); + print $socket "00*\r\n"; + my $res = <$socket>; + close($socket); + + if ($res and $res =~ /^\{(00,[\d\.,]+)\},/) + { + my @vals = split(/,/, $1); + + if ($hash->{UseSVTime}) + { + $timenow = sprintf("%04d-%02d-%02d %02d:%02d:00", + $vals[3], $vals[2], $vals[1], $vals[4], $vals[5]); + } + + for my $i (6..19) + { + if (defined($vals[$i])) + { + $readings{$gets[$i - 6]} = 0 + $vals[$i]; + } + } + + if ($rereads and $readings{currentPower} == 0) + { + sleep(1); + $rereads = $rereads - 1; + goto READ_SV; + } + + alarm 0; + + # if Debounce is enabled (>0), then skip one! drop of + # currentPower from 'greater than Debounce' to 'Zero' + # + if ($hash->{Debounce} > 0 and + $hash->{Debounce} < $hash->{READINGS}{currentPower}{VAL} and + $readings{currentPower} == 0 and not $hash->{Debounced}) + { + # revert to the previous value + $readings{currentPower} = $hash->{READINGS}{currentPower}{VAL}; + $hash->{Debounced} = 1; + } else { + $hash->{Debounced} = 0; + } + + $success = 1; + } + } + }; + + alarm 0; + + if ($success) + { + for my $get (@gets) + { + # update and notify readings if they have changed + if ($hash->{READINGS}{$get}{VAL} != $readings{$get}) + { + $hash->{READINGS}{$get}{VAL} = $readings{$get}; + $hash->{READINGS}{$get}{TIME} = $timenow; + # + push @{$hash->{CHANGED}}, "$get: $readings{$get}"; + } + } + DoTrigger($hash->{NAME}, undef) if ($init_done); + } + + $hash->{STATE} = $hash->{READINGS}{currentPower}{VAL}.' W, '.$hash->{READINGS}{totalEnergyDay}{VAL}.' kWh'; + + if ($success) { + Log 4, "$hash->{NAME} got fresh values from solarview"; + } else { + $hash->{STATE} .= ' (Fail)'; + Log 4, "$hash->{NAME} was unable to get fresh values from solarview"; + } + + return undef; +} + +sub +SolarView_Get($@) +{ + my ($hash, @args) = @_; + + return 'SolarView_Get needs two arguments' if (@args != 2); + + SolarView_Update($hash) unless $hash->{Interval}; + + my $get = $args[1]; + my $val = $hash->{Invalid}; + + if (defined($hash->{READINGS}{$get})) { + $val = $hash->{READINGS}{$get}{VAL}; + } else { + return "SolarView_Get: no such reading: $get"; + } + + Log 3, "$args[0] $get => $val"; + + return $val; +} + +sub +SolarView_Undef($$) +{ + my ($hash, $args) = @_; + + RemoveInternalTimer($hash) if $hash->{Interval}; + + return undef; +} + +sub +SolarView_IsNight($) +{ + my ($hash) = @_; + + my $isNight = 0; + + my ($sec,$min,$hour,$mday,$mon) = localtime(time); + + # reset totalEnergyX if needed + if ($hour == 0) + { + my $timenow = TimeNow(); + + $hash->{READINGS}{totalEnergyDay}{VAL} = 0; + $hash->{READINGS}{totalEnergyDay}{TIME} = $timenow; + # + if ($mday == 1) + { + $hash->{READINGS}{totalEnergyMonth}{VAL} = 0; + $hash->{READINGS}{totalEnergyMonth}{TIME} = $timenow; + # + if ($mon == 0) + { + $hash->{READINGS}{totalEnergyYear}{VAL} = 0; + $hash->{READINGS}{totalEnergyYear}{TIME} = $timenow; + } + } + } + + if ($hash->{UseSVNight}) + { + # These are the on/off timings from Solarview, see + # http://www.amhamberg.de/solarview-fb_Installieren.pdf + # + if ($mon == 0) { # Jan + $isNight = ($hour < 7 or $hour > 17); + } elsif ($mon == 1) { # Feb + $isNight = ($hour < 7 or $hour > 18); + } elsif ($mon == 2) { # Mar + $isNight = ($hour < 6 or $hour > 19); + } elsif ($mon == 3) { # Apr + $isNight = ($hour < 5 or $hour > 20); + } elsif ($mon == 4) { # May + $isNight = ($hour < 5 or $hour > 21); + } elsif ($mon == 5) { # Jun + $isNight = ($hour < 5 or $hour > 21); + } elsif ($mon == 6) { # Jul + $isNight = ($hour < 5 or $hour > 21); + } elsif ($mon == 7) { # Aug + $isNight = ($hour < 5 or $hour > 21); + } elsif ($mon == 8) { # Sep + $isNight = ($hour < 6 or $hour > 20); + } elsif ($mon == 9) { # Oct + $isNight = ($hour < 7 or $hour > 19); + } elsif ($mon == 10) { # Nov + $isNight = ($hour < 7 or $hour > 17); + } elsif ($mon == 11) { # Dec + $isNight = ($hour < 8 or $hour > 16); + } + } else { # we use SUNRISE_EL + $isNight = not isday(); + } + + return $isNight; +} + +1; + diff --git a/contrib/contrib/86_FS10.pm b/contrib/contrib/86_FS10.pm new file mode 100644 index 000000000..29aaa710b --- /dev/null +++ b/contrib/contrib/86_FS10.pm @@ -0,0 +1,166 @@ +############################################## +package main; + +use strict; +use warnings; +use Device::SerialPort; +use IO::Socket::INET; + +my $fs10data = ""; +my $pcwsdsocket; + + +##################################### +sub +FS10_Initialize($) +{ + my ($hash) = @_; + + # Consumer + $hash->{DefFn} = "FS10_Define"; + $hash->{AttrList}= "model:FS10 loglevel:0,1,2,3,4,5,6"; +} + +##################################### +sub +FS10_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + Log 3, "FS10 Define: $a[0] $a[1] $a[2] $a[3]"; + + return "Define the host and portnr as a parameter i.e. 127.0.0.1 4711" + if(@a != 4); + + $hash->{Timer} = 600; + $hash->{Host} = $a[2]; + $hash->{Port} = $a[3]; + $hash->{STATE} = "Initialized"; + + my $dev = $a[2]; + Log 1, "FS10 device is none, commands will be echoed only" + if($dev eq "none"); + + $hash->{DeviceName} = $dev; + + FS10_GetStatus($hash); + return undef; +} + + +##################################### +sub +FS10_GetStatus($) +{ + my ($hash) = @_; + my $buf; + #my $banner; + my $reqcmd; + my $fs10time; + my $dt; + my $x; + my $result = ""; + + Log 3, "FS10_GetStatus"; + + # Call us in 5 minutes again. + InternalTimer(gettimeofday()+300, "FS10_GetStatus", $hash, 0); + + my $dnr = $hash->{DEVNR}; + my $name = $hash->{NAME}; + my $host = $hash->{Host}; + my $port = $hash->{Port}; + my %vals; + my $pcwsd ="$host:$port"; + my $pcwsdsocket = IO::Socket::INET->new( $pcwsd ) + or return "FS10 Can't bind to pcwsd" if(!$pcwsdsocket); + + my $banner = $pcwsdsocket->getline(); + my @x = split(" ", $banner); + my @y; + my $fs10name; + + for(my $i = 0; $i < 8; $i++) #Outdoor + { + $fs10name ="Ta$i"; + $reqcmd = "get od2temp $i\r\n"; + $pcwsdsocket->print($reqcmd); + $buf = $pcwsdsocket->getline(); + $result = "$result $buf"; + + @x = split(" ", $buf); + $fs10time = FmtDateTime($x[1]); + + $hash->{CHANGED}[$i] = "Ta$i: $x[0]"; + $hash->{READINGS}{$fs10name}{TIME} = $fs10time; + $hash->{READINGS}{$fs10name}{VAL} = $x[0]; + } + + $fs10name="Ti"; + $reqcmd = "get idtemp 7\r\n"; + $pcwsdsocket->print($reqcmd); + $buf = $pcwsdsocket->getline(); + @x = split(" ", $buf); + $fs10time = FmtDateTime($x[1]); + + $hash->{CHANGED}[8] = "Ti: $x[0]"; + $hash->{READINGS}{$fs10name}{TIME} = $fs10time; + $hash->{READINGS}{$fs10name}{VAL} = $x[0]; + + $fs10name="Rain"; + $reqcmd = "get rain 7\r\n"; + $pcwsdsocket->print($reqcmd); + $buf = $pcwsdsocket->getline(); + @x = split(" ", $buf); + $fs10time = FmtDateTime($x[1]); + + $hash->{CHANGED}[9] = "Rain: $x[0]"; + $hash->{READINGS}{$fs10name}{TIME} = $fs10time; + $hash->{READINGS}{$fs10name}{VAL} = $x[0]; + + $fs10name="Sun"; + $reqcmd = "get bright 7\r\n"; + $pcwsdsocket->print($reqcmd); + $buf = $pcwsdsocket->getline(); + @x = split(" ", $buf); + $fs10time = FmtDateTime($x[1]); + + $hash->{CHANGED}[10] = "Sun: $x[0]"; + $hash->{READINGS}{$fs10name}{TIME} = $fs10time; + $hash->{READINGS}{$fs10name}{VAL} = $x[0]; + + $fs10name="Windspeed"; + $reqcmd = "get wspd 7\r\n"; + $pcwsdsocket->print($reqcmd); + $buf = $pcwsdsocket->getline(); + @x = split(" ", $buf); + $fs10time = FmtDateTime($x[1]); + + $hash->{CHANGED}[11] = "Windspeed: $x[0]"; + $hash->{READINGS}{$fs10name}{TIME} = $fs10time; + $hash->{READINGS}{$fs10name}{VAL} = $x[0]; + + close($pcwsdsocket); + + $result =~ s/[\r\n]//g; + DoTrigger($name, undef) if($init_done); + + $hash->{STATE} = "$result"; + Log 3,"FS10 Result: $result"; + return $hash->{STATE}; +} + +##################################### +sub +FS10Log($$) +{ + my ($a1, $a2) = @_; + + #define n31 notify fs10 {FS10Log("@", "%")} + #define here notify action + + Log 2,"FS10 $a1 = $a2 old: $oldvalue{$a1}{TIME}=> $oldvalue{$a1}{VAL});"; +} + +1; diff --git a/contrib/contrib/90_EIBUPDOWN.pm b/contrib/contrib/90_EIBUPDOWN.pm new file mode 100644 index 000000000..68fcc0ad0 --- /dev/null +++ b/contrib/contrib/90_EIBUPDOWN.pm @@ -0,0 +1,211 @@ +############################################## +package main; + +use strict; +use warnings; + +my %eib_c2b1 = ( + "alloff" => "00", + "off" => "01", + "on" => "00", + "up" => "01", + "down" => "00", + "up-for-timer" => "01", + "down-for-timer" => "00", +); + +my %eib_c2b2 = ( + "alloff" => "00", + "off" => "00", + "on" => "01", + "up" => "00", + "down" => "01", + "up-for-timer" => "00", + "down-for-timer" => "01", +); + + +my %readonly = ( + "dummy" => 1, +); + +my $eib_simple ="alloff off on up down up-for-timer down-for-timer"; +my %models = ( +); + +sub +EIBUPDOWN_Initialize($) +{ + my ($hash) = @_; + + $hash->{Match} = "^B.*"; + $hash->{SetFn} = "EIBUPDOWN_Set"; + $hash->{StateFn} = "EIBUPDOWN_SetState"; + $hash->{DefFn} = "EIBUPDOWN_Define"; + $hash->{UndefFn} = "EIBUPDOWN_Undef"; + $hash->{ParseFn} = "EIBUPDOWN_Parse"; + $hash->{AttrList} = "IODev do_not_notify:1,0 ignore:0,1 dummy:1,0 showtime:1,0 model:EIB loglevel:0,1,2,3,4,5,6"; + +} + + +############################# +sub +EIBUPDOWN_Define($$) +{ + my ($hash, $def) = @_; + my @a = split("[ \t][ \t]*", $def); + + my $u = "wrong syntax: define EIBUPDOWN "; + + return $u if(int(@a) < 4); + return "Define $a[0]: wrong up group name format: specify as 0-255/0-255/0-255" + if( ($a[2] !~ m/^[0-9]{1,3}\/[0-9]{1,3}\/[0-9]{1,3}$/i)); + + return "Define $a[0]: wrong down group name format: specify as 0-255/0-255/0-255" + if( ($a[3] !~ m/^[0-9]{1,3}\/[0-9]{1,3}\/[0-9]{1,3}$/i)); + + my $groupname_up = eibupdown_name2hex($a[2]); + my $groupname_down = eibupdown_name2hex($a[3]); + + $hash->{GROUP_UP} = lc($groupname_up); + $hash->{GROUP_DOWN} = lc($groupname_down); + + my $code = "$groupname_up$groupname_down"; + my $ncode = 1; + my $name = $a[0]; + + $hash->{CODE}{$ncode++} = $code; + $modules{EIB}{defptr}{$code}{$name} = $hash; + + AssignIoPort($hash); +} + +############################# +sub +EIBUPDOWN_Undef($$) +{ + my ($hash, $name) = @_; + + foreach my $c (keys %{ $hash->{CODE} } ) { + $c = $hash->{CODE}{$c}; + + # As after a rename the $name may be different from the $defptr{$c}{$n} + # we look for the hash. + foreach my $dname (keys %{ $modules{EIB}{defptr}{$c} }) { + delete($modules{EIB}{defptr}{$c}{$dname}) + if($modules{EIB}{defptr}{$c}{$dname} == $hash); + } + } + return undef; +} + +##################################### +sub +EIBUPDOWN_SetState($$$$) +{ + my ($hash, $tim, $vt, $val) = @_; + + $val = $1 if($val =~ m/^(.*) \d+$/); + return "Undefined value $val" if(!defined($eib_c2b1{$val})); + return undef; +} + +################################### +sub +EIBUPDOWN_Set($@) +{ + my ($hash, @a) = @_; + my $ret = undef; + my $na = int(@a); + + return "no set value specified" if($na < 2 || $na > 3); + return "Readonly value $a[1]" if(defined($readonly{$a[1]})); + + my $c_off = $eib_c2b1{"alloff"}; + my $c_up = $eib_c2b1{$a[1]}; + my $c_down = $eib_c2b2{$a[1]}; + if(!defined($c_off) || !defined($c_up) || !defined($c_down)) { + return "Unknown argument $a[1], choose one of " . + join(" ", sort keys %eib_c2b1); + } + + my $v = join(" ", @a); + Log GetLogLevel($a[0],2), "EIB set $v"; + (undef, $v) = split(" ", $v, 2); # Not interested in the name... + + # first of all switch off all channels + # just for being sure + IOWrite($hash, "B", "w" . $hash->{GROUP_UP} . $c_off); + select(undef,undef,undef,0.5); + IOWrite($hash, "B", "w" . $hash->{GROUP_DOWN} . $c_off); + select(undef,undef,undef,0.5); + + # now switch on the right channel + if($c_up ne $c_off) { + IOWrite($hash, "B", "w" . $hash->{GROUP_UP} . $c_up); + } + elsif($c_down ne $c_off) { + IOWrite($hash, "B", "w" . $hash->{GROUP_DOWN} . $c_down); + } + + ########################################### + # Delete any timer for on-for_timer + if($modules{EIB}{ldata}{$a[0]}) { + CommandDelete(undef, $a[0] . "_timer"); + delete $modules{EIB}{ldata}{$a[0]}; + } + + ########################################### + # Add a timer if any for-timer command has been chosen + if($a[1] =~ m/for-timer/ && $na == 3) { + my $dur = $a[2]; + my $to = sprintf("%02d:%02d:%02d", $dur/3600, ($dur%3600)/60, $dur%60); + $modules{EIB}{ldata}{$a[0]} = $to; + Log 4, "Follow: +$to set $a[0] alloff"; + CommandDefine(undef, $a[0] . "_timer at +$to set $a[0] alloff"); + } + + ########################## + # Look for all devices with the same code, and set state, timestamp + my $code = "$hash->{GROUP_UP}$hash->{GROUP_DOWN}"; + my $tn = TimeNow(); + foreach my $n (keys %{ $modules{EIB}{defptr}{$code} }) { + + my $lh = $modules{EIB}{defptr}{$code}{$n}; + $lh->{CHANGED}[0] = $v; + $lh->{STATE} = $v; + $lh->{READINGS}{state}{TIME} = $tn; + $lh->{READINGS}{state}{VAL} = $v; + } + return $ret; +} + +sub +EIBUPDOWN_Parse($$) +{ + my ($hash, $msg) = @_; + + Log(5,"EIBUPDOWN_Parse is not defined. msg: $msg"); + +} + +############################# +sub +eibupdown_name2hex($) +{ + my $v = shift; + my $r = $v; + Log(5, "name2hex: $v"); + if($v =~ /^([0-9]{1,2})\/([0-9]{1,2})\/([0-9]{1,3})$/) { + $r = sprintf("%01x%01x%02x",$1,$2,$3); + } + elsif($v =~ /^([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,3})$/) { + $r = sprintf("%01x%01x%02x",$1,$2,$3); + } + + return $r; +} + + +1; diff --git a/contrib/contrib/91_DbLog.pm b/contrib/contrib/91_DbLog.pm new file mode 100755 index 000000000..9d4b32ac9 --- /dev/null +++ b/contrib/contrib/91_DbLog.pm @@ -0,0 +1,97 @@ +############################################## +# Example for logging KS300 data into a DB. +# +# Prerequisites: +# - The DBI and the DBD:: modules must be installed. +# - a Database is created/configured +# - a db table: create table FHZLOG (TIMESTAMP varchar(20), TEMP varchar(5), +# HUM varchar(3), WIND varchar(4), RAIN varchar(8)); +# - Change the content of the dbconn variable below +# - extend your FHEM config file with +# notify .*H:.* {DbLog("@","%")} +# - copy this file into the /FHEM and restart fhem.pl +# +# If you want to change this setup, your starting point is the DbLog function + +my $dbconn = "Oracle:DBNAME:user:password"; + +package main; +use strict; +use warnings; +use DBI; + +my $dbh; + +sub DbDo($); +sub DbConnect(); + + +################################################################ +sub +DbLog_Initialize($) +{ + my ($hash) = @_; + + # Lets connect here, so we see the error at startup + DbConnect(); +} + +################################################################ +sub +DbLog($$) +{ + my ($a1, $a2) = @_; + + # a2 is like "T: 21.2 H: 37 W: 0.0 R: 0.0 IR: no" + my @a = split(" ", $a2); + my $tm = TimeNow(); + + DbDo("insert into FHZLOG (TIMESTAMP, TEMP, HUM, WIND, RAIN) values " . + "('$tm', '$a[1]', '$a[3]', '$a[5]', '$a[7]')"); +} + + +################################################################ +sub +DbConnect() +{ + return 1 if($dbh); + Log 5, "Connecting to database $dbconn"; + my @a = split(":", $dbconn); + $dbh = DBI->connect("dbi:$a[0]:$a[1]", $a[2], $a[3]); + if(!$dbh) { + Log 1, "Can't connect to $a[1]: $DBI::errstr"; + return 0; + } + Log 5, "Connection to db $a[1] established"; + return 1; +} + +################################################################ +sub +DbDo($) +{ + my $str = shift; + + return 0 if(!DbConnect()); + Log 5, "Executing $str"; + my $sth = $dbh->do($str); + if(!$sth) { + Log 2, "DB: " . $DBI::errstr; + $dbh->disconnect; + $dbh = 0; + return 0 if(!DbConnect()); +#retry + $sth = $dbh->do($str); + if($sth) + { + Log 2, "Retry ok: $str"; + return 1; + } +# + return 0; + } + return 1; +} + +1; diff --git a/contrib/contrib/95_FLOORPLAN/95_FLOORPLAN.pm b/contrib/contrib/95_FLOORPLAN/95_FLOORPLAN.pm new file mode 100644 index 000000000..ada1463f3 --- /dev/null +++ b/contrib/contrib/95_FLOORPLAN/95_FLOORPLAN.pm @@ -0,0 +1,611 @@ +################################################################################ +# 95 FLOORPLAN +# Feedback: http://groups.google.com/group/fhem-users +# Define Custom Floorplans +# Released : 26.02.2012 +# Version : 1.01 +# Revisions: +# 0001: Released to testers +# 0002: use local FP_select and FP_submit after clash with FHEMWEB update +# 0003: FP_arrange_default repaired +# 0004: WebApp-enabled links in floorplanlist, fixed message 'use of uninitialized value' (FW_pO - $FP_name) +# 0005: Change arrange-mode: When selected, display device-name instead of selection +# 0006: kicked out various routines previously copied from FHEMWEB - now using FW_*-versions thanks to addtl. global variables $FW_RET, $FW_wname, $FW_subdir, %FW_pos +# 0007: Added fp_default +# 0008: Changed name of background-picture from to fp_ to avoid display of picture in device-list at fhem-menu 'Everything' +# -> general release +# 0009: updated selection of add-device-list: suppress CUL$ only (instead of CUL.*) +# 0010: Added Style3, fp_stylesheetPrefix, fp_noMenu (Mar 13, 2012) +# 0011: Added Style4, code beautification, css review, minor $text2-fix (SVN 1342) +# 0012: Added startscreen-text when no floorplans defined, fixed startscreen-stylesheet, added div for bg-img, added arrangeByMouse (1368) +# 0013: implemented redirectCmd, fixed minor -error in html-output, fp_arrange for single web-devices, fp_arrange detail (Mar 23, 2012) +# +################################################################ +# +# Copyright notice +# +# (c) 2012 Copyright: Ulrich Maass +# All rights reserved +# +# This script free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# The GNU General Public License can be found at +# http://www.gnu.org/copyleft/gpl.html. +# A copy is found in the textfile GPL.txt and important notices to the license +# from the author is found in LICENSE.txt distributed with these scripts. +# +# This script is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +################################################################################ +# Usage +# define FLOORPLAN +# +# Step-by-Step HowTo - mind all is case sensitive: +# Step 1: +# define FLOORPLAN +# Example: define Groundfloor FLOORPLAN +# +# Step 2: +# store picture fp_.png in your modpath. This will be used as background-picture. +# Example: fhem/FHEM/Groundfloor.png +# +# Step 3: +# Activate 'Arrange-Mode' to have user-friendly fields to move items: +# attr fp_arrange 1 +# Delete this attribute when you're done with setup +# To make objects display, they will thereby get assigned +# attr fp_ ,,