2013-12-22 14:51:57 +00:00
#########################################################################
2016-01-25 18:55:16 +00:00
# $Id$
2013-12-22 14:51:57 +00:00
# fhem Modul f<> r Waterkotte W<> rmepumpe mit Res<65> mat CD4 Steuerung
# Vorlage: Modul WHR962, diverse Foreneintr<74> ge sowie Artikel <20> ber Auswertung der
# W<> rmepumpe mit Linux / Perl im Linux Magazin aus 2010
# insbesondere:
2015-08-24 18:51:46 +00:00
# http://www.haustechnikdialog.de/Forum/t/6144/Waterkotte-5017-3-an-den-Computer-anschliessen?page=2 (Speicheradressen-Liste)
# http://www.ip-symcon.de/forum/threads/2092-ComPort-und-Waterkotte-abfragen (Protokollbeschreibung)
# http://www.haustechnikdialog.de/Forum/t/6144/Waterkotte-5017-3-an-den-Computer-anschliessen?page=4 (Beispiel Befehls-Strings)
#
# This file is part of fhem.
#
# Fhem 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.
#
# Fhem 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.
#
# You should have received a copy of the GNU General Public License
# along with fhem. If not, see <http://www.gnu.org/licenses/>.
#
##############################################################################
# Changelog:
#
# 2013-11-1 initial version
# 2013-12-25 bug fixes, GPL comment added, loglevel reading removed
# 2014-4-7 performance tuning, read handling with binary string instead of hex string
# 2014-5-22 decode mode - heating, cooling, warm water on / off
# 2015-7-23 added utf8 encoding of state string, Added name to Log3 calls
#
2013-12-22 14:51:57 +00:00
package main ;
use strict ;
use warnings ;
2015-08-24 18:51:46 +00:00
use Time::HiRes qw( gettimeofday ) ;
use Encode qw( decode encode ) ;
2013-12-22 14:51:57 +00:00
#
# list of Readings / values that can explicitely be requested
# from the WP with the GET command
2015-08-24 18:51:46 +00:00
my % WKRCD4_gets = (
"Hzg-TempBasisSoll" = > "Hzg-TempBasisSoll" ,
"WW-Temp-Soll" = > "Temp-WW-Soll"
2013-12-22 14:51:57 +00:00
) ;
# list of Readings / values that can be written to the WP
2015-08-24 18:51:46 +00:00
my % WKRCD4_sets = (
"Hzg-TempBasisSoll" = > "Hzg-TempBasisSoll"
2013-12-22 14:51:57 +00:00
) ;
# Definition of the values that can be read / written
# with the relative address, number of bytes and
2015-08-24 18:51:46 +00:00
# fmat to be used in sprintf when formatting the value
2013-12-22 14:51:57 +00:00
# unp to be used in pack / unpack commands
# min / max for setting values
#
my % frameReadings = (
'Versions-Nummer' = > { addr = > 0x0000 , bytes = > 0x0002 , unp = > 'n' } ,
'Temp-Aussen' = > { addr = > 0x0008 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-Ruecklauf-Soll' = > { addr = > 0x0014 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-Ruecklauf' = > { addr = > 0x0018 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-Vorlauf' = > { addr = > 0x001C , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-WW-Soll' = > { addr = > 0x0020 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-WW' = > { addr = > 0x0024 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-Raum' = > { addr = > 0x0028 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-WQuelle-Ein' = > { addr = > 0x0030 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-WQuelle-Aus' = > { addr = > 0x0034 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-Verdampfer' = > { addr = > 0x0038 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-Kondensator' = > { addr = > 0x003C , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Temp-Saugleitung' = > { addr = > 0x0040 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Druck-Verdampfer' = > { addr = > 0x0048 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Druck-Kondensator' = > { addr = > 0x004C , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Hzg-TempEinsatz' = > { addr = > 0x00F4 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' , min = > 15.0 , max = > 20.0 } ,
'Hzg-TempBasisSoll' = > { addr = > 0x00F8 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' , min = > 20.0 , max = > 24.0 } ,
'Hzg-KlSteilheit' = > { addr = > 0x00FC , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' , min = > 15.0 , max = > 30.0 } ,
'Hzg-KlBegrenz' = > { addr = > 0x0100 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Hzg-TempRlSoll' = > { addr = > 0x0050 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Hzg-TempRlIst' = > { addr = > 0x0054 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Hzg-TmpRaumSoll' = > { addr = > 0x0105 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Hzg-RaumEinfluss' = > { addr = > 0x0109 , bytes = > 0x0001 , unp = > 'C' } ,
'Hzg-ExtAnhebung' = > { addr = > 0x010A , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' , min = > - 5.0 , max = > 5.0 } ,
'Hzg-Zeit-Ein' = > { addr = > 0x010E , bytes = > 0x0003 , fmat = > '%3$02d:%2$02d:%1$02d' , unp = > 'CCC' } ,
'Hzg-Zeit-Aus' = > { addr = > 0x0111 , bytes = > 0x0003 , fmat = > '%3$02d:%2$02d:%1$02d' , unp = > 'CCC' } ,
'Hzg-AnhebungEin' = > { addr = > 0x0114 , bytes = > 0x0003 , fmat = > '%3$02d:%2$02d:%1$02d' , unp = > 'CCC' } ,
'Hzg-AnhebungAus' = > { addr = > 0x0117 , bytes = > 0x0003 , fmat = > '%3$02d:%2$02d:%1$02d' , unp = > 'CCC' } ,
'Hzg-St2Begrenz' = > { addr = > 0x011A , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Hzg-Hysterese' = > { addr = > 0x011E , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' , min = > 1.0 , max = > 3.0 } ,
'Hzg-PumpenNachl' = > { addr = > 0x0122 , bytes = > 0x0001 , unp = > 'C' , min = > 0 , max = > 120 } ,
'Klg-Abschaltung' = > { addr = > 0x0123 , bytes = > 0x0001 , unp = > 'C' } ,
'Klg-Temp-Einsatz' = > { addr = > 0x0124 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Klg-TeBasisSoll' = > { addr = > 0x0128 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Klg-KlSteilheit' = > { addr = > 0x012C , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Klg-KlBegrenz' = > { addr = > 0x0130 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Klg-KlSollwert' = > { addr = > 0x0058 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Klg-Temp-Rl' = > { addr = > 0x005C , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Ww-Abschaltung' = > { addr = > 0x0134 , bytes = > 0x0001 } ,
'Ww-Zeit-Ein' = > { addr = > 0x0135 , bytes = > 0x0003 , fmat = > '%3$02d:%2$02d:%1$02d' , unp = > 'CCC' } ,
'Ww-Zeit-Aus' = > { addr = > 0x0138 , bytes = > 0x0003 , fmat = > '%3$02d:%2$02d:%1$02d' , unp = > 'CCC' } ,
'Ww-Temp-Ist' = > { addr = > 0x0060 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Ww-Temp-Soll' = > { addr = > 0x013b , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' , min = > 35 , max = > 55 } ,
'Ww-Hysterese' = > { addr = > 0x0143 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' , min = > 5 , max = > 10 } ,
'Uhrzeit' = > { addr = > 0x0064 , bytes = > 0x0003 , fmat = > '%3$02d:%2$02d:%1$02d' , unp = > 'CCC' } ,
'Datum' = > { addr = > 0x0067 , bytes = > 0x0003 , fmat = > '%02d.%02d.%02d' , unp = > 'CCC' } ,
'BetrStundenKompressor' = > { addr = > 0x006A , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'BetrStundenHzgPu' = > { addr = > 0x006E , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'BetrStundenWwPu' = > { addr = > 0x0072 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'BetrStundenSt2' = > { addr = > 0x0076 , bytes = > 0x0004 , fmat = > '%0.1f' , unp = > 'f<' } ,
'Zeit' = > { addr = > 0x0064 , bytes = > 0x0006 , fmat = > '%4$02d.%5$02d.%6$02d %3$02d:%2$02d:%1$02d' , unp = > 'CCCCCC' } ,
2015-08-24 18:51:46 +00:00
'SetBetriebsMode' = > { addr = > 0x014E , bytes = > 0x0003 , unp = > 'N' , pack = > 'xC*' } ,
'Display-Zeile-1' = > { addr = > 0x008E , bytes = > 0x0002 , unp = > 'n' } ,
'Display-Zeile-2' = > { addr = > 0x0090 , bytes = > 0x0001 , unp = > 'C' } ,
'Status-Gesamt' = > { addr = > 0x00D2 , bytes = > 0x0001 , unp = > 'C' } ,
2013-12-22 14:51:57 +00:00
'Status-Heizung' = > { addr = > 0x00D4 , bytes = > 0x0003 , unp = > 'B24' } ,
'Status-Kuehlung' = > { addr = > 0x00DA , bytes = > 0x0003 , unp = > 'B24' } ,
'Mode-Heizung' = > { addr = > 0x00DF , bytes = > 0x0001 , unp = > 'B8' } ,
'Mode-Kuehlung' = > { addr = > 0x00E0 , bytes = > 0x0001 , unp = > 'B8' } ,
2015-08-24 18:51:46 +00:00
'Mode-Warmwasser' = > { addr = > 0x00E1 , bytes = > 0x0001 , unp = > 'B8' } ,
'Heizung' = > { addr = > 0x00DF , bytes = > 0x0001 , unp = > 'b' } ,
'Kuehlung' = > { addr = > 0x00E0 , bytes = > 0x0001 , unp = > 'b' } ,
'Warmwasser' = > { addr = > 0x00E1 , bytes = > 0x0001 , unp = > 'b' }
2013-12-22 14:51:57 +00:00
) ;
#
# FHEM module intitialisation
# defines the functions to be called from FHEM
#########################################################################
sub WKRCD4_Initialize ($)
{
2015-08-24 18:51:46 +00:00
my ( $ hash ) = @ _ ;
require "$attr{global}{modpath}/FHEM/DevIo.pm" ;
$ hash - > { ReadFn } = "WKRCD4_Read" ;
$ hash - > { ReadyFn } = "WKRCD4_Ready" ;
$ hash - > { DefFn } = "WKRCD4_Define" ;
$ hash - > { UndefFn } = "WKRCD4_Undef" ;
$ hash - > { SetFn } = "WKRCD4_Set" ;
$ hash - > { GetFn } = "WKRCD4_Get" ;
$ hash - > { AttrList } =
"do_not_notify:1,0 " . $ readingFnAttributes ;
2013-12-22 14:51:57 +00:00
}
#
# Define command
# init internal values, open device,
# set internal timer to send read command / wakeup
2015-08-24 18:51:46 +00:00
######################################################################### #
2013-12-22 14:51:57 +00:00
sub WKRCD4_Define ($$)
{
2015-08-24 18:51:46 +00:00
my ( $ hash , $ def ) = @ _ ;
my @ a = split ( "[ \t][ \t]*" , $ def ) ;
return "wrong syntax: define <name> WKRCD4 [devicename\@speed|none] [interval]"
if ( @ a < 3 ) ;
DevIo_CloseDev ( $ hash ) ;
my $ name = $ a [ 0 ] ;
my $ dev = $ a [ 2 ] ;
my $ interval = 60 ;
if ( $ dev eq "none" ) {
Log3 undef , 1 , "$name: device is none, commands will be echoed only" ;
return undef ;
}
if ( int ( @ a ) == 4 ) {
$ interval = $ a [ 3 ] ;
if ( $ interval < 20 ) {
return "interval too small, please use something > 20, default is 60" ;
}
}
$ hash - > { buffer } = "" ;
$ hash - > { DeviceName } = $ dev ;
$ hash - > { INTERVAL } = $ interval ;
$ hash - > { SerialRequests } = 0 ;
$ hash - > { SerialGoodReads } = 0 ;
$ hash - > { SerialBadReads } = 0 ;
# send wakeup string (read 2 values preceeded with AT)
$ hash - > { LastRequestAdr } = 8 ;
$ hash - > { LastRequestLen } = 4 ;
$ hash - > { LastRequest } = gettimeofday ( ) ;
my $ ret = DevIo_OpenDev ( $ hash , 0 , "WKRCD4_Wakeup" ) ;
# initial read after 3 secs, there timer is set to interval for update and wakeup
InternalTimer ( gettimeofday ( ) + 3 , "WKRCD4_GetUpdate" , $ hash , 0 ) ;
return $ ret ;
2013-12-22 14:51:57 +00:00
}
#
# undefine command when device is deleted
#########################################################################
sub WKRCD4_Undef ($$)
{
2015-08-24 18:51:46 +00:00
my ( $ hash , $ arg ) = @ _ ;
DevIo_CloseDev ( $ hash ) ;
RemoveInternalTimer ( $ hash ) ;
return undef ;
2013-12-22 14:51:57 +00:00
}
#
# Encode the data to be sent to the device (0x10 gets doubled)
#########################################################################
sub Encode10 (@) {
2015-08-24 18:51:46 +00:00
my @ a = ( ) ;
for my $ byte ( @ _ ) {
push @ a , $ byte ;
push @ a , $ byte if $ byte == 0x10 ;
}
return @ a ;
2013-12-22 14:51:57 +00:00
}
#
# create a command for the WP as byte array
#########################################################################
sub WPCMD ( $ $ $ $ ; @ )
{
2015-08-24 18:51:46 +00:00
my ( $ hash , $ cmd , $ addr , $ len , @ value ) = @ _ ;
my $ name = $ hash - > { NAME } ;
my @ frame = ( ) ;
if ( $ cmd eq "read" ) {
@ frame = ( 0x01 , 0x15 , Encode10 ( $ addr >> 8 , $ addr % 256 ) , Encode10 ( $ len >> 8 , $ len % 256 ) ) ;
} elsif ( $ cmd eq "write" ) {
@ frame = ( 0x01 , 0x13 , Encode10 ( $ addr >> 8 , $ addr % 256 ) , Encode10 ( @ value ) ) ;
} else {
Log3 $ name , 3 , "$name: undefined cmd ($cmd) in WPCMD" ;
return 0 ;
}
my $ crc = CRC16 ( @ frame ) ;
return ( 0xff , 0x10 , 0x02 , @ frame , 0x10 , 0x03 , $ crc >> 8 , $ crc % 256 , 0xff ) ;
2013-12-22 14:51:57 +00:00
}
#
# GET command
#########################################################################
sub WKRCD4_Get ($@)
{
2015-08-24 18:51:46 +00:00
my ( $ hash , @ a ) = @ _ ;
return "\"get WKRCD4\" needs at least an argument" if ( @ a < 2 ) ;
my $ name = shift @ a ;
my $ attr = shift @ a ;
my $ arg = join ( "" , @ a ) ;
if ( ! $ WKRCD4_gets { $ attr } ) {
my @ cList = keys % WKRCD4_gets ;
return "Unknown argument $attr, choose one of " . join ( " " , @ cList ) ;
}
# get Hash pointer for the attribute requested from the global hash
my $ properties = $ frameReadings { $ WKRCD4_gets { $ attr } } ;
if ( ! $ properties ) {
return "No Entry in frameReadings found for $attr" ;
}
# get details about the attribute requested from its hash
my $ addr = $ properties - > { addr } ;
my $ bytes = $ properties - > { bytes } ;
Log3 $ name , 4 , sprintf ( "$name: Get will read %02x bytes starting from %02x for $attr" , $ bytes , $ addr ) ;
# create command for WP
my $ cmd = pack ( 'C*' , WPCMD ( $ hash , 'read' , $ addr , $ bytes ) ) ;
# set internal variables to track what is happending
$ hash - > { LastRequestAdr } = $ addr ;
$ hash - > { LastRequestLen } = $ bytes ;
$ hash - > { LastRequest } = gettimeofday ( ) ;
$ hash - > { SerialRequests } + + ;
Log3 $ name , 4 , "$name: Get -> Call DevIo_SimpleWrite: " . unpack ( 'H*' , $ cmd ) ;
DevIo_SimpleWrite ( $ hash , $ cmd , 0 ) ;
return sprintf ( "Read %02x bytes starting from %02x" , $ bytes , $ addr ) ;
2013-12-22 14:51:57 +00:00
}
2015-08-24 18:51:46 +00:00
2013-12-22 14:51:57 +00:00
#
# SET command
#########################################################################
sub WKRCD4_Set ($@)
{
2015-08-24 18:51:46 +00:00
my ( $ hash , @ a ) = @ _ ;
return "\"set WKRCD4\" needs at least an argument" if ( @ a < 2 ) ;
my $ name = shift @ a ;
my $ attr = shift @ a ;
my $ arg = join ( "" , @ a ) ;
if ( ! defined ( $ WKRCD4_sets { $ attr } ) ) {
my @ cList = keys % WKRCD4_sets ;
return "Unknown argument $attr, choose one of " . join ( " " , @ cList ) ;
}
# get Hash pointer for the attribute requested from the global hash
my $ properties = $ frameReadings { $ WKRCD4_sets { $ attr } } ;
if ( ! $ properties ) {
return "No Entry in frameReadings found for $attr" ;
}
# get details about the attribute requested from its hash
my $ addr = $ properties - > { addr } ;
my $ bytes = $ properties - > { bytes } ;
my $ min = $ properties - > { min } ;
my $ max = $ properties - > { max } ;
my $ unp = $ properties - > { unp } ;
2013-12-22 14:51:57 +00:00
return "a numerical value between $min and $max is expected, got $arg instead"
if ( $ arg !~ m/^[\d.]+$/ || $ arg < $ min || $ arg > $ max ) ;
2015-08-24 18:51:46 +00:00
# convert string to value needed for command
my $ vp = pack ( $ unp , $ arg ) ;
my @ value = unpack ( 'C*' , $ vp ) ;
Log3 $ name , 4 , sprintf ( "$name: Set will write $attr: %02x bytes starting from %02x with %s (%s) packed with $unp" , $ bytes , $ addr , unpack ( 'H*' , $ vp ) , unpack ( $ unp , $ vp ) ) ;
my $ cmd = pack ( 'C*' , WPCMD ( $ hash , 'write' , $ addr , $ bytes , @ value ) ) ;
# set internal variables to track what is happending
$ hash - > { LastRequestAdr } = $ addr ;
$ hash - > { LastRequestLen } = $ bytes ;
$ hash - > { LastRequest } = gettimeofday ( ) ;
$ hash - > { SerialRequests } + + ;
Log3 $ name , 4 , "Set -> Call DevIo_SimpleWrite: " . unpack ( 'H*' , $ cmd ) ;
DevIo_SimpleWrite ( $ hash , $ cmd , 0 ) ;
return sprintf ( "Wrote %02x bytes starting from %02x with %s (%s)" , $ bytes , $ addr , unpack ( 'H*' , $ vp ) , unpack ( $ unp , $ vp ) ) ;
2013-12-22 14:51:57 +00:00
}
#########################################################################
# called from the global loop, when the select for hash->{FD} reports data
sub WKRCD4_Read ($)
{
2015-08-24 18:51:46 +00:00
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
# read from serial device
my $ buf = DevIo_SimpleRead ( $ hash ) ;
return "" if ( ! defined ( $ buf ) ) ;
$ hash - > { buffer } . = $ buf ;
Log3 $ name , 5 , "$name: read buffer content: " . unpack ( 'H*' , $ hash - > { buffer } ) ;
# did we already get a full frame?
if ( $ hash - > { buffer } !~ /\xff\x10\x02(.{2})(.*)\x10\x03(.{2})\xff(.*)/s )
{
Log3 $ name , 5 , "$name: read NoMatch: " . unpack ( 'H*' , $ hash - > { buffer } ) ;
return "" ;
}
my $ msg = unpack ( 'H*' , $ 1 ) ;
my @ aframe = unpack ( 'C*' , $ 1 . $ 2 ) ;
my $ crc = unpack ( 'S>' , $ 3 ) ;
my $ rest = $ 4 ;
$ hash - > { buffer } = $ rest ;
Log3 $ name , 4 , "$name: read match msg: $msg CRC $crc" ;
Log3 $ name , 5 , "$name: read frame is " . unpack ( 'H*' , pack ( 'C*' , @ aframe ) ) . ", Rest " . unpack ( 'H*' , $ rest ) ;
# calculate CRC and compare with CRC from read
my $ crc2 = CRC16 ( @ aframe ) ;
if ( $ crc != $ crc2 ) {
Log3 $ name , 3 , "$name: read Bad CRC from WP: $crc, berechnet: $crc2" ;
Log3 $ name , 4 , "$name: read Frame was " . unpack ( 'H*' , pack ( 'C*' , @ aframe ) ) ;
$ hash - > { SerialBadReads } + + ;
@ aframe = ( ) ;
return "" ;
} ;
Log3 $ name , 4 , "$name: read CRC Ok." ;
$ hash - > { SerialGoodReads } + + ;
# reply to read request ?
if ( $ msg eq "0017" ) {
my @ data ;
for ( my $ i = 0 , my $ offset = 2 ; $ offset <= $# aframe ; $ offset + + , $ i + + )
{
# remove duplicate 0x10 (frames are encoded like this)
if ( ( $ aframe [ $ offset ] == 16 ) && ( $ aframe [ $ offset + 1 ] == 16 ) ) { $ offset + + ; }
$ data [ $ i ] = $ aframe [ $ offset ] ;
}
Log3 $ name , 4 , "$name: read -> Parse with relative request start " . $ hash - > { LastRequestAdr } . " Len " . $ hash - > { LastRequestLen } ;
# extract values from data
parseReadings ( $ hash , @ data ) ;
} elsif ( $ msg eq "0011" ) {
# reply to write
} else {
Log3 $ name , 3 , "$name: read got unknown Msg type " . $ msg . " in " . $ hash - > { buffer } ;
}
@ aframe = ( ) ;
return "" ;
2013-12-22 14:51:57 +00:00
}
#
# copied from other FHEM modules
#########################################################################
sub WKRCD4_Ready ($)
{
2015-08-24 18:51:46 +00:00
my ( $ hash ) = @ _ ;
2013-12-22 14:51:57 +00:00
2015-08-24 18:51:46 +00:00
return DevIo_OpenDev ( $ hash , 1 , undef )
if ( $ hash - > { STATE } eq "disconnected" ) ;
2013-12-22 14:51:57 +00:00
2015-08-24 18:51:46 +00:00
# This is relevant for windows/USB only
my $ po = $ hash - > { USBDev } ;
my ( $ BlockingFlags , $ InBytes , $ OutBytes , $ ErrorFlags ) = $ po - > status ;
return ( $ InBytes > 0 ) ;
2013-12-22 14:51:57 +00:00
}
#
# send wakeup /at least my waterkotte WP doesn't respond otherwise
#########################################################################
sub WKRCD4_Wakeup ($)
{
2015-08-24 18:51:46 +00:00
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
$ hash - > { SerialRequests } + + ;
$ hash - > { LastRequestAdr } = 8 ;
$ hash - > { LastRequestLen } = 4 ;
$ hash - > { LastRequest } = gettimeofday ( ) ;
my $ cmd = "41540D100201150008000410037EA010020115003000041003FDC3100201150034000410037D90" ;
DevIo_SimpleWrite ( $ hash , $ cmd , 1 ) ;
Log3 $ name , 5 , "$name: sent wakeup string: " . $ cmd . " done." ;
return undef ;
2013-12-22 14:51:57 +00:00
}
#
# request new data from WP
###################################
sub WKRCD4_GetUpdate ($)
{
2015-08-24 18:51:46 +00:00
my ( $ hash ) = @ _ ;
my $ name = $ hash - > { NAME } ;
InternalTimer ( gettimeofday ( ) + $ hash - > { INTERVAL } , "WKRCD4_GetUpdate" , $ hash , 1 ) ;
InternalTimer ( gettimeofday ( ) + $ hash - > { INTERVAL } / 2 , "WKRCD4_Wakeup" , $ hash , 1 ) ;
$ hash - > { SerialRequests } + + ;
my $ cmd = pack ( 'C*' , WPCMD ( $ hash , 'read' , 0 , 0x170 ) ) ;
$ hash - > { LastRequestAdr } = 0 ;
$ hash - > { LastRequestLen } = 0x170 ;
$ hash - > { LastRequest } = gettimeofday ( ) ;
DevIo_SimpleWrite ( $ hash , $ cmd , 0 ) ;
Log3 $ name , 5 , "$name: GetUpdate -> Call DevIo_SimpleWrite: " . unpack ( 'H*' , $ cmd ) ;
return 1 ;
2013-12-22 14:51:57 +00:00
}
#
# calculate CRC16 for communication with the WP
#####################################################################################################
sub CRC16
{
my $ CRC = 0 ;
my $ POLY = 0x800500 ;
for my $ byte ( @ _ , 0 , 0 ) {
$ CRC |= $ byte ;
for ( 0 .. 7 ) {
$ CRC << = 1 ;
if ( $ CRC & 0x1000000 ) { $ CRC ^= $ POLY ; }
$ CRC & = 0xffffff ;
}
}
return $ CRC >> 8 ;
}
#
# get Values out of data read
#####################################################################################################
sub parseReadings
{
my ( $ hash , @ data ) = @ _ ;
2015-08-24 18:51:46 +00:00
my $ name = $ hash - > { NAME } ;
2013-12-22 14:51:57 +00:00
2015-08-24 18:51:46 +00:00
my $ reqStart = $ hash - > { LastRequestAdr } ;
my $ reqLen = $ hash - > { LastRequestLen } ;
2013-12-22 14:51:57 +00:00
2015-08-24 18:51:46 +00:00
# get enough bytes?
2013-12-22 14:51:57 +00:00
if ( @ data >= $ reqLen )
{
2015-08-24 18:51:46 +00:00
readingsBeginUpdate ( $ hash ) ;
# go through all possible readings from global hash
2013-12-22 14:51:57 +00:00
while ( my ( $ reading , $ property ) = each ( % frameReadings ) )
{
2015-08-24 18:51:46 +00:00
my $ addr = $ property - > { addr } ;
my $ bytes = $ property - > { bytes } ;
# is reading inside data we got?
2013-12-22 14:51:57 +00:00
if ( ( $ addr >= $ reqStart ) &&
2015-08-24 18:51:46 +00:00
( $ addr + $ bytes <= $ reqStart + $ reqLen ) )
2013-12-22 14:51:57 +00:00
{
2015-08-24 18:51:46 +00:00
my $ Idx = $ addr - $ reqStart ;
# get relevant slice from data array
my @ slice = @ data [ $ Idx .. $ Idx + $ bytes - 1 ] ;
# convert according to rules in global hash or defaults
my $ pack = ( $ property - > { pack } ) ? $ property - > { pack } : 'C*' ;
my $ unpack = ( $ property - > { unp } ) ? $ property - > { unp } : 'H*' ;
my $ fmat = ( $ property - > { fmat } ) ? $ property - > { fmat } : '%s' ;
#my $value = sprintf ($fmat, unpack ($unpack, pack ($pack, @slice))) . " packed with $pack, unpacked with $unpack, (hex " . unpack ('H*', pack ('C*', @slice)) . ") format $fmat";
my $ value = sprintf ( $ fmat , unpack ( $ unpack , pack ( $ pack , @ slice ) ) ) ;
readingsBulkUpdate ( $ hash , $ reading , $ value ) ;
Log3 $ name , 4 , "$name: parse set reading $reading to $value" if ( @ data <= 20 ) ;
2013-12-22 14:51:57 +00:00
}
}
2015-08-24 18:51:46 +00:00
my $ Status = "WP idle" ;
if ( ReadingsVal ( $ name , "Heizung" , 0 ) ) {
$ Status = sprintf ( "Heizung %s" , ReadingsVal ( $ name , "Temp-Vorlauf" , 0 ) ) ;
} elsif ( ReadingsVal ( $ name , "Kuehlung" , 0 ) ) {
$ Status = sprintf ( "K<> hlung %s" , ReadingsVal ( $ name , "Temp-Vorlauf" , 0 ) ) ;
} elsif ( ReadingsVal ( $ name , "Kuehlung" , 0 ) ) {
$ Status = sprintf ( "Warmwasser %s" , ReadingsVal ( $ name , "Temp-WW" , 0 ) ) ;
}
$ Status = encode ( "utf8" , $ Status ) ;
readingsBulkUpdate ( $ hash , "Status" , $ Status ) ;
readingsEndUpdate ( $ hash , 1 ) ;
}
2013-12-22 14:51:57 +00:00
else
{
2015-08-24 18:51:46 +00:00
Log3 $ name , 3 , "$name: parse - data len smaller than requested ($reqLen) : " . unpack ( 'H*' , pack ( 'C*' , @ data ) ) ;
2013-12-22 14:51:57 +00:00
return 0 ;
}
}
1 ;