| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315 |
- # $Id: 79_BDKM.pm 12770 2016-12-14 08:39:57Z arnoaugustin $
- ##############################################################################
- #
- # 79_BDKM.pm
- #
- # 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.
- #
- # BDKM 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/>.
- #
- # Written by Arno Augustin
- ##############################################################################
- package main;
- use strict;
- use POSIX;
- use warnings;
- #use Blocking;
- #use HttpUtils;
- use Encode;
- use JSON;
- use Time::HiRes qw(gettimeofday);
- use Digest::MD5 qw(md5 md5_hex md5_base64);
- use base qw( Exporter );
- use MIME::Base64;
- use LWP::UserAgent;
- use Crypt::Rijndael;
- my @BaseDirs = qw(
- /
- /dhwCircuits
- /gateway
- /heatingCircuits
- /heatSources
- /notifications
- /recordings
- /solarCircuits
- /system
- );
- #@BaseDirs = qw(/system/sensors/temperatures /dhwCircuits);
- my %WdToNum = qw(Mo 1 Tu 2 We 3 Th 4 Fr 5 Sa 6 Su 7);
- my @RC300DEFAULTS =
- # ID:POLL EVERY x CYCLE:MINDELTA:READINGNAME
- # all gateway IDs are polled (gathered) once on startup
- #*:1:0:* poll every cycle, difference 0 => update on difference 0 (allways)
- #*:1::* poll every cycle, no difference set => update on change only
- #*:0::* poll on startup only and update reading on change only
- #*:1:0.5:* poll every cycle, difference set to 0.5 => update only if difference to last read is >= 0.5
- #*:15::* poll on startup and every 15th cylce, update reading if changed
- #*:::* update reading on (get/set) only if value changed
- #*::0:* update reading on (get/set) always
- #* ID only, no ":", poll every cycle, update reading allways (same as *:1:0:*)
- qw(/dhwCircuits/dhw1/actualTemp:1:0.2:WaterTemp
- /dhwCircuits/dhw1/currentSetpoint:1::WaterDesiredTemp
- /dhwCircuits/dhw1/operationMode:1::WaterMode
- /dhwCircuits/dhw1/status:0::WaterStatus
- /dhwCircuits/dhw1/switchPrograms/A/1-Mo:0:0:WaterProgram-1-Mo
- /dhwCircuits/dhw1/switchPrograms/A/2-Tu:0:0:WaterProgram-2-Tu
- /dhwCircuits/dhw1/switchPrograms/A/3-We:0:0:WaterProgram-3-We
- /dhwCircuits/dhw1/switchPrograms/A/4-Th:0:0:WaterProgram-4-Th
- /dhwCircuits/dhw1/switchPrograms/A/5-Fr:0:0:WaterProgram-5-Fr
- /dhwCircuits/dhw1/switchPrograms/A/6-Sa:0:0:WaterProgram-6-Sa
- /dhwCircuits/dhw1/switchPrograms/A/7-Su:0:0:WaterProgram-7-Su
- /dhwCircuits/dhw1/temperatureLevels/high:1::WaterDayTemp
- /dhwCircuits/dhw1/waterFlow:::waterFlow
- /dhwCircuits/dhw1/workingTime:::WaterWorkingTime
- /gateway/DateTime:0:0:DateTime
- /gateway/instAccess:0:0:InstAccess
- /gateway/uuid:::Uuid
- /gateway/versionFirmware:::FirmwareVersion
- /heatSources/ChimneySweeper:::ChimneySweeper
- /heatSources/flameCurrent:::FlameCurrent
- /heatSources/gasAirPressure:0:0:GasAirPressure
- /heatSources/hs1/energyReservoir:::EnergyReservoir
- /heatSources/hs1/flameStatus:::FlameStatus
- /heatSources/hs1/fuel/caloricValue:0:0:CaloricValue
- /heatSources/hs1/fuel/density:0:0:FuelDensity
- /heatSources/hs1/fuelConsmptCorrFactor:0:0:FuelConsmptCorrFactor
- /heatSources/hs1/info:::HeatSourceInfo
- /heatSources/hs1/nominalFuelConsumption:0:0:FuelConsumption
- /heatSources/hs1/reservoirAlert:0:0:ReservoirAlert
- /heatSources/hs1/supplyTemperatureSetpoint:0:0:SupplyTemperatureSetpoint
- /heatSources/hs1/type:::HeatSourceType
- /heatSources/info:::HeatSourceInfo
- /heatSources/numberOfStarts:0:0:NumberOfStarts
- /heatSources/systemPressure:20:0.2:SystemPressure
- /heatSources/workingTime/centralHeating:0:0:CentralHeatingWorkingTime
- /heatSources/workingTime/secondBurner:0:0:SecondBurnerWorkingTime
- /heatSources/workingTime/totalSystem:0:0:SystemWorkingTime
- /heatingCircuits/hc1/activeSwitchProgram:0:0:ActiveSwitchProgram
- /heatingCircuits/hc1/actualSupplyTemperature:0:0:HC1SupplyTemp
- /heatingCircuits/hc1/currentRoomSetpoint:1::RoomDesiredTemp
- /heatingCircuits/hc1/fastHeatupFactor:0:0:HeatupFactor
- /heatingCircuits/hc1/manualRoomSetpoint:10::RoomManualDesiredTemp
- /heatingCircuits/hc1/operationMode:10::HeatMode
- /heatingCircuits/hc1/pumpModulation:1:10:PumpModulation
- /heatingCircuits/hc1/status:0:0:Status
- /heatingCircuits/hc1/switchPrograms/A/1-Mo:0:0:ProgramA1-Mo
- /heatingCircuits/hc1/switchPrograms/A/2-Tu:0:0:ProgramA2-Tu
- /heatingCircuits/hc1/switchPrograms/A/3-We:0:0:ProgramA3-We
- /heatingCircuits/hc1/switchPrograms/A/4-Th:0:0:ProgramA4-Th
- /heatingCircuits/hc1/switchPrograms/A/5-Fr:0:0:ProgramA5-Fr
- /heatingCircuits/hc1/switchPrograms/A/6-Sa:0:0:ProgramA6-Sa
- /heatingCircuits/hc1/switchPrograms/A/7-Su:0:0:ProgramA7-Su
- /heatingCircuits/hc1/switchPrograms/B/1-Mo:0:0:ProgramB1-Mo
- /heatingCircuits/hc1/switchPrograms/B/2-Tu:0:0:ProgramB2-Tu
- /heatingCircuits/hc1/switchPrograms/B/3-We:0:0:ProgramB3-We
- /heatingCircuits/hc1/switchPrograms/B/4-Th:0:0:ProgramB4-Th
- /heatingCircuits/hc1/switchPrograms/B/5-Fr:0:0:ProgramB5-Fr
- /heatingCircuits/hc1/switchPrograms/B/6-Sa:0:0:ProgramB6-Sa
- /heatingCircuits/hc1/switchPrograms/B/7-Su:0:0:ProgramB7-Su
- /heatingCircuits/hc1/temperatureLevels/comfort2:10::ComfortTemp
- /heatingCircuits/hc1/temperatureLevels/eco:10::EcoTemp
- /heatingCircuits/hc1/temporaryRoomSetpoint:1::RoomTemporaryDesiredTemp
- /notifications:0:0:Notifications
- /system/brand:0:0:SystemBrand
- /system/bus:::BusType
- /system/healthStatus:10::Health
- /system/heatSources/hs1/actualModulation:1::PowerModulation
- /system/heatSources/hs1/actualPower:1::Power
- /system/holidayModes/hm1/assignedTo:0:0:Holiday1Assign
- /system/holidayModes/hm1/dhwMode:0:0:Holiday1WaterMode
- /system/holidayModes/hm1/hcMode:0:0:Holiday1HeatMode
- /system/holidayModes/hm1/startStop:0:0:Holiday1
- /system/holidayModes/hm2/assignedTo:0:0:Holiday2Assign
- /system/holidayModes/hm2/dhwMode:0:0:Holiday2WaterMode
- /system/holidayModes/hm2/hcMode:0:0:Holiday2HeatMode
- /system/holidayModes/hm2/startStop:0:0:Holiday2
- /system/holidayModes/hm3/assignedTo:0:0:Holiday3Assign
- /system/holidayModes/hm3/dhwMode:0:0:Holiday3WaterMode
- /system/holidayModes/hm3/hcMode:0:0:Holiday3HeatMode
- /system/holidayModes/hm3/startStop:0:0:Holiday3
- /system/holidayModes/hm4/assignedTo:0:0:Holiday4Assign
- /system/holidayModes/hm4/dhwMode:0:0:Holiday4WaterMode
- /system/holidayModes/hm4/hcMode:0:0:Holiday4HeatMode
- /system/holidayModes/hm4/startStop:0:0:Holiday4
- /system/holidayModes/hm5/assignedTo:0:0:Holiday5Assign
- /system/holidayModes/hm5/dhwMode:0:0:Holiday5WaterMode
- /system/holidayModes/hm5/hcMode:0:0:Holiday5HeatMode
- /system/holidayModes/hm5/startStop:0:0:Holiday5
- /system/info:::SystemInfo
- /system/minOutdoorTemp:0:0:MinOutdoorTemp
- /system/sensors/temperatures/outdoor_t1:1:0.5:OutdoorTemp
- /system/sensors/temperatures/return:1:0.5:ReturnTemp
- /system/sensors/temperatures/supply_t1:1:0.5:SupplyTemp
- /system/sensors/temperatures/supply_t1_setpoint:1:0.5:DesiredSupplyTemp
- /system/systemType:::SystemType
- );
- # I don't know anything about RC30 and RC35 - feel free to fill with knowledge:
- my @RC30DEFAULTS =
- qw(/gateway/DateTime:0:0:DateTime
- );
- my @RC35DEFAULTS =
- qw(/gateway/DateTime:0:0:DateTime
- );
- # extra valid value not in range (set by gateway)
- my %extra_value=
- qw(/heatingCircuits/hc1/fastHeatupFactor 0
- /heatingCircuits/hc1/temporaryRoomSetpoint -1
- /gateway/DateTime now);
- sub BDKM_Define($$);
- sub BDKM_Undefine($$);
- sub BDKM_Initialize($)
- {
- my ($hash) = @_;
-
- $hash->{STATE} = "Init";
- $hash->{DefFn} = "BDKM_Define";
- $hash->{UndefFn} = "BDKM_Undefine";
- $hash->{SetFn} = "BDKM_Set";
- $hash->{GetFn} = "BDKM_Get";
- $hash->{AttrFn} = "BDKM_Attr";
- $hash->{DeleteFn} = "BDKM_Undefine";
- $hash->{AttrList} =
- "BaseInterval " .
- "InterPollDelay " .
- "PollIds:textField-long " .
- "HttpTimeout " .
- $readingFnAttributes;
- return undef;
- }
- sub BDKM_Define($$)
- {
- my ($hash, $def) = @_;
- my @a = split(/\s+/, $def);
- my $name = $a[0];
- my $salt = "";
- my $cryptkey="";
- my $usage="usage: \"define <devicename> BDKM <IPv4-address|hostname> <GatewayPassword> <PrivatePassword> <md5salt>\" or\n".
- "\"define <devicename> BDKM <IPv4-address|hostname> <AES-Key (see:https://ssl-account.com/km200.andreashahn.info)>\"";
- (@a == 4 or @a ==6) or return "$name $usage";
- $hash->{NAME} = $name;
- $hash->{STATE} = "define";
- my $ip = $a[2];
- ($ip =~ m/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/ and
- $1<256 and $2<256 and $3<256 and $4<256) or
- ($ip =~ m/(?=^.{1,253}$)(^(((?!-)[a-zA-Z0-9-]{1,63}(?<!-))|((?!-)[a-zA-Z0-9-]{1,63}(?<!-)\.)+[a-zA-Z]{2,63})$)/) or
- return "$name IP or hostname invalid, $usage";
-
- if(@a == 6) {
- my @passwd = ($a[3],$a[4]); # gateway,private passwd
-
- $salt=$a[5];
-
- if(!($passwd[0] =~ /-/)) { # must be base64
- my $i;
- foreach $i ((0,1)) {
- $_ = decode_base64($passwd[$i]);
- s/[\r\n]//g;
- $passwd[$i] = $_;
- }
- }
- $passwd[0] =~ tr/-//d;
- if(length($passwd[0]) != 16 or
- length($passwd[1]) == 0) {
- Log3 $name, 1, "$name please check passwords";
- return "$name ERROR gateway password needs format ".
- "\"aaaa-bbbb-cccc-dddd\"\n".
- "password may be encoded base64 to make it less human readable";
- }
- $salt = pack('H*',$salt);
- $cryptkey = md5($passwd[0].$salt).md5($salt.$passwd[1]);
- } elsif (@a == 4) {
- # complete AES-Key from define. Can be generated here:
- # https://ssl-account.com/km200.andreashahn.info
- $cryptkey = pack('H*',$a[3]);
- }
-
- Log3 $name, 3, "$name using AES-Key: ".unpack("H*",$cryptkey)."\n";
- $hash->{CRYPT} =
- Crypt::Rijndael->new($cryptkey, Crypt::Rijndael::MODE_ECB() );
- $hash->{NAME} = $name;
- $hash->{IP} = $ip;
- $hash->{SEQUENCE} = 0;
- $hash->{POLLIDS} = {}; # from attr PollIds
- $hash->{UPDATES} = []; # Ids to check for update reading after polling
- $hash->{REALTOUSER} = {}; # Hash to transform real IDs to readings
- $hash->{USERTOREAL} = {}; # Hash to readings to real IDs
- $hash->{IDS} = {}; # Hash containing IDS of first full poll
- $hash->{VERSION} = '$Id: 79_BDKM.pm 12770 2016-12-14 08:39:57Z arnoaugustin $';
- # init attrs to defaults:
- map {BDKM_Attr("del",$name,$_)} qw(BaseInterval InterPollDelay ReadBackDelay HttpTimeout);
- # delay start to have a chance that all attrs are set
- BDKM_Timer($hash,10,"BDKM_doSequence");
- return undef;
- }
- sub BDKM_Attr(@)
- {
- my ($cmd,$name,$attr,$val) = @_;
- my $hash = $defs{$name};
- my $error = "$name: ERROR attribute $attr ";
- my $del = $cmd =~ /del/;
- local $_;
- defined $val or $val="";
-
- if ($attr eq "BaseInterval") {
- $del and $val = 120; # default
- if($val !~ /^\d+$/ or $val < 30) {
- return $error."needs interger value >= 30";
- } else {
- $hash->{BASEINTERVAL} = $val;
- }
- } elsif ($attr eq "InterPollDelay") {
- $del and $val = 0; # default
- if($val !~ /^\d+$/) {
- return $error."needs interger value";
- } else {
- $hash->{INTERPOLLDELAY} = $val/1000;
- }
- } elsif($attr eq "ReadBackDelay") {
- $del and $val = 500;
- if($val !~ /^\d+$/ or $val < 100 or $val > 2000) {
- return $error."needs interger value (milliseconds) between 100 and 2000";
- } else {
- $hash->{READBACKDELAY} = $val;
- }
- } elsif ($attr eq "HttpTimeout") {
- $del and $val = 10; # default
- $val =~ /^([0-9]+|[0-9]+\.?[0.9]+)$/ or return $error."needs numeric value";
- $hash->{HTTPTIMEOUT} = $val;
- } elsif($attr eq "PollIds") {
- $hash->{POLLIDS} = {}; # no more polling
- $hash->{UPDATES} = []; # no updates for possibly running poll
- $del and return undef;
- $hash->{REALTOUSER} = {};
- $hash->{USERTOREAL} = {};
- my @ids=();
- # add defaults if set
- $val =~ s|RC300DEFAULTS||g and push(@ids, @RC300DEFAULTS);
- $val =~ s|RC35DEFAULTS||g and push(@ids, @RC35DEFAULTS);
- $val =~ s|RC30DEFAULTS||g and push(@ids, @RC30DEFAULTS);
- push(@ids,split(/\s+/s,$val));
- my $err = $error."needs space separated valid gateway IDs like\n".
- "/system/sensors/temperatures/return:2:0.5:ReturnTemp\nor just\n".
- "/system/sensors/temperatures/return:::\nor just\n".
- "/system/sensors/temperatures/return\n";
- foreach (@ids) {
- s|\s+||gs;
- /[A-z]/ or next;
- my($id,$modulo,$delta,$replace);
- if(m|(^/[A-z0-9\-_/]+[A-z0-9])$|) { # no ":"
- # poll id every cycle, no delta check (allways update), no replacement
- ($id,$modulo,$delta,$replace) = ($1,1,0,"");
- } else { # colon separated extras id:modulo:delta:replace
- unless(($id,$modulo,$delta,$replace) =
- m|(^/[A-z0-9\-_/]+[A-z0-9]):[-]*(\d*):([0-9]*\.?[0-9]*):(.*)$|) {
- return $err;
- }
- ($modulo eq "" or $modulo =~ '-') and $modulo = -1;
- }
- # check pathes:
- my $ok = 0;
- foreach my $dir (@BaseDirs) {
- $dir eq "/" and next;
- !index($id,$dir,0) and $ok=1 and last;
- }
- $ok or return $error."$id is not a valid gateway ID";
-
- defined $hash->{POLLIDS}{$id} and
- Log3 $hash, 4, "$name attr PollIds - Overwritig definition of $id";
-
- $delta eq "" or $delta += 0.0;
- $hash->{POLLIDS}{$id}{MODULO} =int($modulo);
- $hash->{POLLIDS}{$id}{DELTA} = $delta;
- if($replace) {
- $hash->{REALTOUSER}{$id}=$replace;
- $hash->{USERTOREAL}{$replace}=$id;
- } else {
- # remove replacements for IDs if overwritten.
- if(defined $hash->{REALTOUSER}{$id}) {
- delete $hash->{REALTOUSER}{$id};
- map {
- $hash->{USERTOREAL}{$_} eq $id and delete $hash->{USERTOREAL}{$_}
- } keys %{$hash->{USERTOREAL}};
- }
- }
- }
- }
-
- return undef;
- }
- sub BDKM_doSequence($)
- {
- my ($hash) = @_;
- # BDKM_doSequence is never called directly. It's only triggered by its own timer.
- # restart timer for next sequence
- BDKM_Timer($hash,$hash->{BASEINTERVAL},"BDKM_doSequence");
- # only start polling if we are not polling (e.g. due to network promlems)
- if($hash->{ISPOLLING}) {
- Log3 $hash, 3, $hash->{NAME}." ERROR: trying to start new sequence while previous not finished";
- Log3 $hash, 3, $hash->{NAME}." Gateway not responding? BaseInterval too short? InterPollDelay too high?";
- return;
- }
- $hash->{ISPOLLING}=1;
- my $seq = $hash->{SEQUENCE};
- my $h = $hash->{POLLIDS};
- Log3 $hash, 4, "$hash->{NAME} starting polling sequence #".$seq;
- if(!$seq) { # do full poll and init $hash->{IDS}
- @{$hash->{JOBQUEUE}} = @BaseDirs;
- # update only modulos >= 0
- @{$hash->{UPDATES}} =
- sort grep {$h->{$_}{MODULO} >= 0} keys(%$h);
- } else {
- $h or return; # no ids to poll
- # only poll known IDs which are in turn
- @{$hash->{UPDATES}} =
- sort grep {$h->{$_}{MODULO} > 0 and $seq % $h->{$_}{MODULO} == 0 and defined $hash->{IDS}{$_}} keys(%$h);
- # JOBQUEUE: remove special switchPrograms and transform to the real base reading:
- my %seen=();
- @{$hash->{JOBQUEUE}} = BDKM_MapSwitchPrograms($hash->{UPDATES});
- }
- Log3 $hash, 6, $hash->{NAME}." jobqueue is:".join(" ",@{$hash->{JOBQUEUE}})."\n";
- Log3 $hash, 6, $hash->{NAME}." elements to update after polling: ".join(" ",@{$hash->{UPDATES}})."\n";
- readingsSingleUpdate($hash, "state", "polling", 0);
- BDKM_JobQueueNextId($hash);
- }
- sub BDKM_JobQueueNextId($)
- {
- my ($hash) = @_;
-
- if(@{$hash->{JOBQUEUE}}) { # still ids to poll
- my $id = (@{$hash->{JOBQUEUE}})[0];
- Log3 $hash, 5, "$hash->{NAME} reading $id";
- # get next type
- BDKM_HttpGET($hash,$id,\&BDKM_JobQueueNextIdHttpDone);
- } else {
- BDKM_UpdateReadings($hash,$hash->{UPDATES});
- $hash->{SEQUENCE}++;
- $hash->{ISPOLLING}=0;
- Log3 $hash, 4, $hash->{NAME}." update ".join(" ",@{$hash->{UPDATES}})."\n";
- readingsSingleUpdate($hash, "state", "idle", 0);
- }
- }
- sub BDKM_JobQueueNextIdHttpDone($)
- {
- my ($param, $err, $data) = @_;
- my $hash = $param->{hash};
- my $name = $hash ->{NAME};
- my $json;
- if($err) {
- readingsSingleUpdate($hash, "state",
- "reading ids ERROR - retrying every 60s", 1);
- Log3 $name, 2, "$name communication ERROR in state $hash->{STATE}: $err";
- # try again in 60s
- BDKM_Timer($hash,60,"BDKM_JobQueueNextId");
- return;
- }
- my $hth= $param->{httpheader};
- $hth =~ s/[\r\n]//g;
- Log3 $name, 5, "$name HTTP done @{$hash->{JOBQUEUE}}[0],$hth";
- # did this type, remove from job queue:
- my $id = shift(@{$hash->{JOBQUEUE}});
- ($json,$data) = BDKM_decode_http_data($hash,$data);
- if($json) {
- if (!$hash->{SEQUENCE} and $json and $json->{type} eq "refEnum") { # init only
- # new type
- foreach my $item (@{$json->{references}}) {
- my $entry = $item->{id};
- #exists $hash->{IGNOREIDS}{$entry} and next; # ignore
- # push to job queue
- push(@{$hash->{JOBQUEUE}},$entry);
- }
- } else {
- BDKM_update_id_from_json($hash,$json);
- }
- } else {
- if(!$hash->{SEQUENCE}) {
- if($id ne "/") {
- $hash->{IDS}{$id}{RAWDATA} = 1;
- $hth =~ s|HTTP/...|HTTP|;
- $hth =~ s/\s+/_/g;
- $hth =~ /200/ or $hash->{IDS}{$id}{HTTPHEADER} = $hth;
- $hth =~ /200/ or $data = $hth;
- }
- Log3 $hash, 4, "$name $id: $data";
- }
- }
- if($hash->{INTERPOLLDELAY}) {
- BDKM_Timer($hash,$hash->{INTERPOLLDELAY},"BDKM_JobQueueNextId");
- } else {
- BDKM_JobQueueNextId($hash);
- }
- return;
- }
- sub BDKM_UpdateReadings($$)
- {
- my ($hash,$listref) = @_;
- readingsBeginUpdate($hash);
- foreach my $id (@$listref) {
- my $val = $hash->{IDS}{$id}{VALUE};
- defined $val or next;
- Log3 $hash, 5, "Check reading update for $id $val";
- my $reading = defined $hash->{REALTOUSER}{$id} ?
- $hash->{REALTOUSER}{$id} : $id;
- my $rdval = $hash->{READINGS}{$reading}{VAL};
- if(defined($rdval) and defined $hash->{POLLIDS}{$id}) {
- my $delta = $hash->{POLLIDS}{$id}{DELTA};
- # same as last - skip
- $delta eq "" and $rdval eq $val and next;
- # difference too small - skip
- $delta and abs($rdval-$val) < $delta and next;
- }
- Log3 $hash, 4, "$hash->{NAME} update reading $reading $val";
- readingsBulkUpdate($hash,$reading,$val);
- }
- readingsEndUpdate($hash,1);
- }
- sub BDKM_Undefine($$)
- {
- my ($hash, $def) = @_;
- my $name = $hash->{NAME};
- BDKM_RemoveTimer($hash);
- return undef;
- }
- sub BDKM_GetInfo
- {
- my ($hash, $matches) = @_;
- no warnings 'uninitialized';
- my $fmt="%-50.50s %-25.25s %-23.23s %s %-30.30s %-10.10s %-10.10s\n";
- my $header =sprintf($fmt,
- "Gateway ID", "FHEM Reading (Alias)", "Last Value Read", "TW",
- "Valid Values", "Poll", "Rd.Update");
- my $llll = ("-" x length($header))."\n";
- my @ids;
- if($matches) {
- # loop over all possible IDs and aliases and check if
- # they match given regexp inputs
- my %seen=();
- map {
- my $regex = qr/$_/;
- map {
- if($_ =~ $regex) { # match on input to INFO
- my $realid = defined $hash->{USERTOREAL}{$_} ?
- $hash->{USERTOREAL}{$_} : $_;
- !$seen{$realid}++ and push(@ids,$realid);
- }
- } (keys %{$hash->{IDS}}, keys %{$hash->{USERTOREAL}});
- } split(/\s+/,$matches);
- } else {
- # use all IDs
- @ids = keys %{$hash->{IDS}};
- }
- my @lines= sort map {
- my $id=$_;
- my $h=$hash->{IDS}{$id};
- my $p=$hash->{POLLIDS}{$id};
- my $m = $p->{MODULO};
- my $d = $p->{DELTA};
- my $u = $h->{UNIT};
- my $type = substr($h->{TYPE},0,1);
- my $flags = ($type ? $type : " ").($h->{WRITEABLE} ? '+' : '-');
-
- my $a = defined $h->{RAWDATA} ? $h->{HTTPHEADER} : $h->{ALLOWED};
- $u =~ s/µ/u/g;
- $a =~ s/ /,/g;
- sprintf($fmt,
- $id,
- $hash->{REALTOUSER}{$id},
- $h->{VALUE}.($u ? " $u":""),
- $flags,
- defined $a ? $a :
- $h->{MIN} ? "[$h->{MIN}:$h->{MAX}]" : "",
- (!defined $m or $m < 0) ? "" :
- $m == 0 ? "once" :
- $m == 1 ? "always" :
- $m >1 ? "every $m" : "",
- (!defined $d or $d eq "") ? "on change" :
- $d == 0 ? "always" : "Δ >= $d"
- );
- } @ids;
- my $footer= $matches ? "" :
- q(* The table shows all known gateway IDs. A "+" sign in the W column means the ID is writeable.
- Long entries may be cut due to formating.
- Ranges for Valid Values ranges are shown as: [from:to]
- When no JSON data can be fetched the HTTP error is shown.
- Temperatures are normaly allowed to set in 0.5 C steps only.
- On startup all IDs are gathered once but do not automatically generate a fhem reading.
- IDs which shoud generate readings not only with the set/get command need to be defined with the "PollIds" attribute.
- Poll:
- always => ID is polled every cycle (PollIds setting *:1:*:*)
- every X => ID is only polled every Xth cycle (PollIds setting *:X:*:*)
- once => After gathering process on startup this ID is checked for reading update (PollIds setting *:0:*:*)
- '' => update checks only on get/set command (PollIds setting *::*:* or not set)
- Redings Udate:
- always => Reading Update is always done on value update (PollIds setting *:*:0:*)
- Δ >= X => Reading Update is done when difference to last reading was at least X (PollIds setting *:*:X:*)
- on change => Reading Update is done when value has changed to last reading (PollIds setting *:*::*)
- );
- return "\n".$header.$llll.join("",@lines).$llll.$footer;
- }
- sub BDKM_Set($@)
- {
- my ( $hash, $name, $id, @values) = @_;
-
- if(!defined $hash->{IDS}{$id} and !defined $hash->{USERTOREAL}{$id}) {
- no warnings 'uninitialized';
- # only print aliased commands:
- my @writeable=sort grep {
- $hash->{IDS}{$hash->{USERTOREAL}{$_}}{WRITEABLE}
- } keys %{$hash->{USERTOREAL}};
- my @cmds=map {
- my @vals=();
- my $realid=$hash->{USERTOREAL}{$_};
- defined $extra_value{$realid} and push (@vals,$extra_value{$realid});
- my $h=$hash->{IDS}{$realid};
- if ($realid =~ /HeatupFactor/) {
- push(@vals,(10,20,30,40,50,60,70,80,90,100));
- } elsif(defined $h->{ALLOWED}) {
- $h->{TYPE} ne "arrayData" and push(@vals,split(/\s+/,$h->{ALLOWED}));
- } elsif (defined $h->{MAX}) {
- if($h->{UNIT} eq "C") {
- for(my $i=$h->{MIN}; $i <= $h->{MAX}; $i+=0.5) {
- push(@vals,$i);
- }
- }
- }
- if (@vals) {
- $_.=":".join(',',@vals);
- } else {
- $_;
- }
- } @writeable;
- return "Unknown argument $id, choose one of ".join(" ",@cmds);
- }
- @values or
- return "usage: set $hash->{NAME} <ID> <value ...>";
- my $value=join(" ",@values);
- my $ret;
- $ret = BDKM_SetId($hash, $id, $value);
- $ret !~ /Unable to set/ and return $ret;
- BDKM_msleep(2000);
- $ret = BDKM_SetId($hash, $id, $value);
- return $ret;
- }
- sub BDKM_HttpTest
- {
- my ($hash,$id,$method,$data) = @_;
- my $param = {
- url => "http://" . $hash->{IP} . $id,
- hash => $hash,
- data => $data,
- method => $method,
- header => "agent: PortalTeleHeater/2.2.3\r\nUser-Agent: TeleHeater/2.2.3\r\nAccept: application/json",
- };
-
- $param->{timeout} = 3;
- my @a= HttpUtils_BlockingGet($param); #returns ($err, $data)
- $param->{hash}=0;
-
- }
- sub BDKM_SetId($@)
- {
- my ($hash,$id,$value) = @_;
- my $name=$hash->{NAME};
- defined $hash->{USERTOREAL}{$id} and $id = $hash->{USERTOREAL}{$id};
- # set getway time to host time:
- $id eq "/gateway/DateTime" and $value eq "now" and
- $value=strftime("%Y-%m-%dT%H:%M:%S", localtime);
- my $data;
- my $err;
- if($value =~ /\s+test$/ or defined $hash->{IDS}{$id}{RAWDATA}) {
- # we dont know anything about that...yet
- # try raw data send
- $value =~ s/\s+test$//g;
- $id =~ /firmware/i and return; # better...if we don't know what we do.
-
- Log3 $name, 3, "$name set rawpost $id value $value";
- $data = BDKM_Encrypt($hash,$value);
- Log3 $name, 3, "$name http PUT $id encrypted data $data";
- my $a;
- ($data,$err,$a) = BDKM_HttpTest($hash,$id,"PUT",$data);
- return "+1+$data+2+$err+3+\n";
- } elsif($value =~ /\s+raw$/) {
- $value =~ s/\s+raw$//g;
- Log3 $name, 3, "$name set raw $id value $value";
- $data = BDKM_Encrypt($hash,$value);
- ($data,$err) = BDKM_HttpPUT($hash,$id,$data);
- return $data.$err;
- } else {
- Log3 $name, 3, "$name set raw $id value $value";
- my $rawdata = BDKM_GetId($hash,$id,"raw") or
- return "unable to set $id because the value can not be read";
-
- defined $hash->{IDS}{$id}{VALUE} or return "ID $id is unknown";
-
- my $h=$hash->{IDS}{$id};
- my $type=$h->{TYPE};
-
- defined $h->{WRITEABLE} and $h->{WRITEABLE} or return "ID $id is not writeable";
- my $allowed=defined $h->{ALLOWED} ? $h->{ALLOWED} : "";
-
- my $json={};
- if($type eq "floatValue") {
- $value =~ s/\"//;
- $value =~ /^-?\d+\.?\d*$/ or return "$id needs a float/integer value";
- Log3 $name, 3, "$name $id set floatValue $value";
- my $ok = (defined $extra_value{$id} and $extra_value{$id} eq $value);
- !$ok and defined $h->{MIN} and defined $h->{MAX} and
- ($value < $h->{MIN} || $value > $h->{MAX}) and
- return "allowed values for $id are: interger/float in range $h->{MIN} to $h->{MAX}";
- $json->{value} = ($value + 0.0); # make number from it!
- } elsif ($type eq "stringValue") {
- Log3 $name, 3, "$name $id set stringValue $value";
- $allowed and $allowed !~ /$value/ and
- return "allowed values for $id are: one of $allowed";
- $json->{value} = $value;
- Log3 $name, 3, "$name set $id float value $value";
- } elsif ($type eq "arrayData") { # RC300 only for /system/holidayModes/hm[1-5]/assignedTo
- Log3 $name, 3, "$name $id set arrayData $value";
- my @a=split(/\s+/,$value);
- foreach(@a) {
- $allowed and $allowed !~ /$_/ and
- return "allowed values for $id are: one or more of $allowed";
- }
- $json->{values} = \@a;
- }
- if ($type eq "switchProgram") {
- Log3 $name, 3, "$name $id set switchProgram $value";
- my $postid=$id;
- $postid =~ s|/\d-([A-z][A-z])$||;
- $data=BDKM_makeSwitchPointData($rawdata,$1, $value);
- $data =~ /setpoint/ or return $data;
- $data = BDKM_Encrypt($hash,$data);
- ($data,$err) = BDKM_HttpPUT($hash,$postid,$data);
- } else {
- $data = BDKM_encode_http_data($hash,$json);
- BDKM_msleep($hash->{READBACKDELAY});
- ($data,$err) = BDKM_HttpPUT($hash,$id,$data);
- }
- BDKM_msleep($hash->{READBACKDELAY});
- my $ret = BDKM_GetId($hash,$id);
- $ret ne $value and $id ne "/gateway/DateTimeteTime" and
- return "$name Unable to set +$value+ to $id (readback: +$ret+)";
- return $ret;
- }
- }
- sub BDKM_Get($@)
- {
- my ( $hash, $name, $id, $opt) = @_;
- # specials
- $id eq "INFO" and return BDKM_GetInfo($hash, $opt);
- if(defined $opt and $opt eq "rawforce") {
- $opt="raw";
- } else {
- if(!defined $hash->{IDS}{$id} and !defined $hash->{USERTOREAL}{$id}
- or (defined($opt) and $opt ne "raw" and $opt ne "json")) {
- # only print aliased and special commands (like INFO):
- my @getable=qw(INFO);
- push(@getable, keys %{$hash->{USERTOREAL}});
- return "Unknown argument $id, choose one of ".join(" ",@getable);
- }
- }
- return BDKM_GetId($hash, $id, $opt);
- }
- sub BDKM_GetId($@)
- {
- my ($hash,$id,$opt) = @_;
- my $name=$hash->{NAME};
-
- defined $hash->{USERTOREAL}{$id} and $id = $hash->{USERTOREAL}{$id};
- my $json;
-
- defined $opt or $opt = "";
-
- my $realid=$id;
- if($id =~ m|/\d-[MTWTFS][ouehrau]$|) {
- # one of our pseudo switch program id
- # map to a real gateway id
- ($realid) = BDKM_MapSwitchPrograms([$id]);
- }
- # blocking http get:
- my($err,$data, $httpheader) = BDKM_HttpGET($hash,$realid);
- if($err) {
- Log3 $name, 2, "$name unable to fetch ID $id - $err";
- return "$name unable to fetch ID $id - $err";
- } else {
- ($json,$data) = BDKM_decode_http_data($hash,$data);
- Log3 $name, 2, "$name get $id - HTTP: $httpheader, data: $data";
- if($json) {
- BDKM_update_id_from_json($hash,$json);
- # always check for reading update when id was read
- BDKM_UpdateReadings($hash,[$id]);
- $opt eq "json" and return $json;
- }
- if($opt eq "raw") {
- return $data;
- }
- defined $hash->{IDS}{$id}{VALUE} and return $hash->{IDS}{$id}{VALUE};
- }
- return "";
- }
- # this routine takes the raw http json data of a switch program,
- # the week day and the setpointstring to be set like
- # "0700 comfort2 2200 eco"
- # It then patches the new setpoints for that day to the json data (sorted!).
- sub BDKM_makeSwitchPointData
- {
- my ($data, $weekday, $setpointstr) =@_;
- my @setpoints=();
- my $timeraster=0;
- map {
- s/\}.*//;
- /switchPointTimeRaster.*?(\d+)/ and $timeraster=$1;
- /setpoint[^A-z]/ and !/\"$weekday\"/ and push(@setpoints,'{'.$_.'}');
- } split(/{/,$data);
-
- my @a=split(/\s+/,$setpointstr);
- while(@a) {
- $_=shift(@a);
- s|:||;
- my ($hr,$min)=/^(\d\d)(\d\d)$/ or return "invalid time format - use: HHMM";
- ($hr > 23 or $min > 59) and return "$hr$min use a valid time between 0000 and 2359";
- $timeraster and $min % $timeraster and
- return "switch point $_ not allowed: switchpoint raster is 15 minutes";
- @a or return "$hr$min missing set point type";
- $_=shift(@a);
- my $time = $hr*60+$min;
- push(@setpoints, '{"dayOfWeek":"'.$weekday.'","setpoint":"'.$_.'","time":'.$time.'}'); # add weekday
- }
- return '['.join(',',
- sort {
- # by day num
- $WdToNum{($a =~ /dayOfWeek[^A-z]+([A-z][A-z])/)[0]} <=>
- $WdToNum{($b =~/dayOfWeek[^A-z]+([A-z][A-z])/)[0]} ||
- # and time
- ($a =~ /time[^\d]+(\d+)/)[0] <=>
- ($b =~ /time[^\d]+(\d+)/)[0]
- } @setpoints).']';
- }
- ############################## Helpers ###################################
- sub BDKM_HttpPostOrGet
- {
- my ($hash,$id,$method,$data,$callback) = @_;
- my $param = {
- url => "http://" . $hash->{IP} . $id,
- hash => $hash,
- data => $data,
- method => $method,
- header => "agent: PortalTeleHeater/2.2.3\r\nUser-Agent: TeleHeater/2.2.3\r\nAccept: application/json",
- };
- if(defined($callback)) {
- Log3 $hash, 5, "$hash->{NAME} async $method $param->{url}";
- $param->{timeout} = $hash->{HTTPTIMEOUT};
- $param->{callback} = $callback;
- HttpUtils_NonblockingGet($param);
- return undef;
- } else {
- $param->{timeout} = 3;
- Log3 $hash, 5, "$hash->{NAME} sync $method $param->{url}";
- return (HttpUtils_BlockingGet($param),$param->{httpheader}); #returns ($err, $data)
- }
- }
- sub BDKM_HttpGET
- {
- my ($hash,$id,$callback) = @_;
- return BDKM_HttpPostOrGet($hash,$id,"GET",undef,$callback);
- }
- sub BDKM_HttpPOST
- {
- my ($hash,$id,$data,$callback) = @_;
- return BDKM_HttpPostOrGet($hash,$id,"POST",$data,$callback);
- }
- sub BDKM_HttpPUT
- {
- my ($hash,$id,$data,$callback) = @_;
- return BDKM_HttpPostOrGet($hash,$id,"PUT",$data,$callback);
- }
- sub BDKM_Timer
- {
- my ($hash,$secs,$callback) = @_;
- InternalTimer(gettimeofday()+$secs, $callback, $hash, 0);
- }
- sub BDKM_RemoveTimer
- {
- RemoveInternalTimer($_[0]);
- }
- sub BDKM_Decrypt($$)
- {
- my ($hash, $data) = @_;
- $data = decode_base64($data);
- length($data) & 0xF and return ""; # must be 16byte blocked. if not decryt calls exit()!!!
- $data = $hash->{CRYPT}->decrypt( $data );
- my $len = length($data);
-
- ($len & 0xF) and return $data;
- # 16 byte block, remove padding
- my $i;
- for($i=0; $i < $len && ord(substr($data,-1-$i,1)) == 0; $i++){};
- if($i) {
- return substr($data,0,$len-$i);
- } else {
- # 16byte blocks not zero padded
- # check if RFC PKCS #7 padded and remove padding
- my $padchar = substr($data,($len - 1),1); #last char
- my $num = ord($padchar);
- if($num <= 16) {
- substr($data,$len - $num, $num) eq ($padchar x $num) and
- return substr($data,0,$len - $num);
- }
- }
- return $data;
- }
- sub BDKM_Encrypt($$)
- {
- my ($hash, $data) = @_;
- my $crypt = $hash->{CRYPT};
- my $blocksize = $crypt->blocksize();
- # pad data to block size before encrypting - see RFC 5652
- my $numpad = $blocksize - length($data)%$blocksize;
- return
- encode_base64($crypt->encrypt($data.(chr($numpad) x $numpad)));
- }
- sub BDKM_msleep
- {
- select(undef, undef, undef, $_[0]/1000);
- }
- sub BDKM_decode_http_data
- {
- my ($hash, $data) = @_;
- my $json="";
- Log3 $hash, 6, "$hash->{NAME} raw crypted HTTP data: $data";
- $data =~ /^\s*$/s and return ("","");
- $data = BDKM_Decrypt($hash,$data);
- my $len = length($data);
- Log3 $hash, 4, "$hash->{NAME} deocded $len bytes HTTP data: $data";
- if($data) {
- eval {$json = decode_json(encode_utf8($data)); 1; } or do {
- $json="";
- }
- }
- return ($json,$data);
- }
- sub BDKM_encode_http_data
- {
- my ($hash, $json) = @_;
- my $data = encode_json($json);
- Log3 $hash, 3, "$hash->{NAME} raw HTTP data: $data";
- $data = BDKM_Encrypt($hash,$data);
- Log3 $hash, 6, "$hash->{NAME} encocded HTTP data: $data";
- return $data;
- }
- sub BDKM_update_id_from_json
- {
- my ($hash,$json) = @_;
-
- if ($json) {
- my $id = $json->{id};
- my $type = $json->{type};
- Log3 $hash, 6, "$hash->{NAME} update JSON $id $type";
- defined ($hash->{IDS}{$id}) or $hash->{IDS}{$id}={};
- my $h = $hash->{IDS}{$id};
- if($type eq "stringValue" or $type eq "floatValue"){
- if(!defined($h->{WRITEABLE})) { # initial
- $h->{WRITEABLE} = $json->{writeable};
- $h->{TYPE}=$type;
- defined($json->{unitOfMeasure}) and $h->{UNIT} = $json->{unitOfMeasure};
- defined($json->{minValue}) and $h->{MIN} = $json->{minValue};
- defined($json->{maxValue}) and $h->{MAX} = $json->{maxValue};
- defined($json->{allowedValues}) and $h->{ALLOWED} = join(" ",@{$json->{allowedValues}});
- }
- $h->{VALUE} = $json->{value};
- } elsif ($type eq "switchProgram") {
- my @prog=();
- my $weekday;
- foreach my $sp (@{$json->{switchPoints}}) {
- $weekday = $sp->{dayOfWeek};
- my $t = $sp->{time};
- my $h = int($t/60);
- my $entry = sprintf("%02d%02d %s",$h,$t-($h*60),$sp->{setpoint});
- my $num = $WdToNum{$weekday};
- $prog[$num] = defined $prog[$num] ? $prog[$num]." ".$entry : $entry;
- Log3 $hash, 5, "$hash->{NAME} update switchProgram $weekday $entry $sp->{time}";
- }
- my $i=1;
- foreach $weekday (qw(Mo Tu We Th Fr Sa Su)) {
- my $newid = "$id/$i-".$weekday;
- if(!defined $hash->{IDS}{$newid}) {
- $hash->{IDS}{$newid}={
- ID => $newid,
- TYPE => $type,
- WRITEABLE => 1
- }
- }
- $hash->{IDS}{$newid}{VALUE} = $prog[$i++];
- }
- } elsif ($type eq "errorList") {
- ### Sort list by timestamps
- my $err="";
- if(defined $json->{values}) {
- foreach my $entry (sort ( @{$json->{values}} )) {
- $err .= sprintf("%-20.20s %-3.3s %-4.4s %-2.2s\n",
- $entry->{t}, $entry->{dcd}, $entry->{ccd}, $entry->{cat});
- }
- }
- if(!defined($h->{WRITEABLE})) { # initial
- $h->{WRITEABLE} = $json->{writeable};
- $h->{TYPE} = "arrayData"; #is also arraydata
- }
- $h->{VALUE} = $err;
- } elsif ($type eq "systeminfo" or $type eq "arrayData") {
- my $info="";
- if(defined $json->{values}) {
- foreach my $val (@{$json->{values}}) {
- if(ref($val) eq 'HASH') {
- $info .=join(" ", map { $_.":".$val->{$_} } keys %{$val})." ";
- } else {
- $info .= $val." ";
- }
- }
- $info =~ s/ $//;
- }
- if(!defined($h->{WRITEABLE})) { # initial
- defined($json->{allowedValues}) and $h->{ALLOWED} = join(" ",@{$json->{allowedValues}});
- $h->{WRITEABLE} = $json->{writeable};
- $h->{TYPE}= "arrayData"; # info is also arraydata
- }
- $h->{VALUE} = $info;
- } elsif ($type eq "yRecording") {
- defined $h->{TYPE} or $h->{TYPE}="Recroding";
- # ignore recordings - fhem records :-)
- } elsif ($type eq "refEnum") {
- # ignore directory entry
- } elsif ($type eq "eMonitoringList") {
- # ignore eMonitoringList - I don't have infos about that
- } else {
- Log3 $hash, 2, "$hash->{NAME}: unknown type $type for $id";
- }
- } else {
- Log3 $hash, 5, "$hash->{NAME}: no JSON data available";
- }
- }
- sub BDKM_MapSwitchPrograms
- {
- # translate all /dhwCircuits/dhw1/switchPrograms/A/\d-[A-z][A-z]$ forms to
- # one real reading like /dhwCircuits/dhw1/switchPrograms/A
- my $aref=$_[0];
- my %seen=();
- my $x;
- # substitution needs $x becaus because $_ would modify original array!!
- return grep {!$seen{$_}++} map {$x=$_; $x =~ s|/\d-[MTWTFS][ouehrau]$||;$x} @$aref;
- }
- 1;
- # perl ./contrib/commandref_join.pl FHEM/79_BDKM.pm
- # perl ./contrib/commandref_join.pl
- =pod
- =item device
- =item summary support for Buderus KM-Gateways
- =item summary_DE Unterstützung für Buderus KM-Gateways
- =begin html
- <a name="BDKM"></a>
- <h3>BDKM</h3>
- <ul>
- BDKM is a module supporting Buderus Logamatic KM gateways similar
- to the <a href="#km200">km200</a> module. For installation of the
- gateway see fhem km200 internet wiki<br>
- Compared with the km200 module the code of the BDKM module is more
- compact and has some extra features. It has the ablility to
- define how often a gateway ID is polled, which FHEM reading
- (alias) is generated for a gateway ID and which minimum difference
- to the last reading must exist to generate a new reading (see
- attributes).<br>
- It determines value ranges, allowed values and writeability from
- the gateway supporting FHEMWEB and readingsGroup when setting
- Values (drop down value menues).<br>
- On definition of a BDKM device the gateway is connected and a full
- poll collecting all IDs is done. This takes about 20 to 30
- seconds. After that the module knows all IDs reported
- by the gateway. To examine these IDs just type:<BR>
- <code>get myBDKM INFO</code><BR>
- These IDs can be used with the PollIds attribute to define if and
- how the IDs are read during the poll cycle. <br> All IDs can be
- mapped to own short readings.
- <br><br>
- <a name="BDKMdefine"></a>
- <b>Define</b>
- <ul>
- <code>define <name> BDKM <IP-address|hostname> <GatewayPassword>
- <PrivatePassword> <MD5-Salt></code><br>
- or <br>
- <code>define <name> BDKM <IP-address|hostname> <AES-Key></code><br>
- <br><br>
- <code><name></code> :
- Name of device<br>
- <code><IP-address></code> :
- The IP adress of your Buderus gateway<br>
- <code><GatewayPassword></code> :
- The gateway password as printed on case of the gateway s.th.
- of the form: xxxx-xxxx-xxxx-xxxx<br>
- <code><PrivatePassword></code> : The private password as
- set with the buderus App<br>
- <code><MD5-Salt></code> : MD5 salt for the crypt
- algorithm you want to use (hex string like 867845e9.....). Have a look for km200 salt 86 ... <br>
- AES-Key can be generated here:<br>
- https://ssl-account.com/km200.andreashahn.info<br>
- <br>
- </ul>
- <a name="BDKMset"></a>
- <b>Set </b>
- <ul>
- <code>set <name> <ID> <value> ...</code>
- <br><br>
- where <code>ID</code> is a valid writeable gateway ID (See list command,
- or "<code>get myBDKM INFO</code>")<br>
- The set command first reads the the ID from the gateway and also
- triggers a FHEM readings if necessary. After that it is checked if the
- value is valid. Then the ID and value(s) are transfered to to the
- gateway. After waiting (attr ReadBackDelay milliseconds) the value
- is read back and checked against value to be set. If necessary again
- a FHEM reading may be triggered. The read back value or an error is
- returned by the command. <br>
- Examples:
- <ul>
- <code>set myBDKM /heatingCircuits/hc1/temporaryRoomSetpoint 22.0</code><br>
- or the aliased version of it (if
- /heatingCircuits/hc1/temporaryRoomSetpointee is aliased to
- RoomTemporaryDesiredTemp):<br>
- <code>set myBDKM RoomTemporaryDesiredTemp 22.0</code><br>
- special to set time of gateway to the hosts date:<br>
- <code>set myBDKM /gateway/DateTime now</code><br>
- aliased:<br>
- <code>set myBDKM DateTime now</code><br>
- </ul>
- <br>
- </ul>
- <br>
- <a name="BDKMget"></a>
- <b>Get </b>
- <ul>
- <code>get <name> <ID> <[raw|json]>...</code><br><br>
- where <code>ID</code> is a valid gateway ID or an alias to it.
- (See list command)<br> The get command reads the the ID from the
- gateway, triggeres readings if necessarry, and returns the value
- or an error if s.th. went wrong. While polling is done
- asychronously with a non blocking HTTP GET. The set and get
- functions use a blocking HTTP GET/POST to be able to return a
- value directly to the user. Normaly get and set are only used by
- command line or when setting values via web interface.<br>
- With the <code>raw</code> option the whole original decoded data of the
- ID (as read from the gateway) is returned as a string.<br> With
- the <code>json</code> option a perl hash reference pointing to the
- JSON data is returned (take a look into the module if you want to
- use that)<br>
- <br>
- Examples:
- <ul>
- <code>get myBDKM /heatingCircuits/hc1/temporaryRoomGetpoint</code><br>
- or the aliased version of it (see attr below):<br>
- <code>get myBDKM RoomTemporaryDesiredTemp</code><br>
- <code>get myBDKM DateTime</code><br>
- <code>get myBDKM /gateway/instAccess</code><br>
- Spacial to get Infos about IDs known by the gateway and own
- configurations:<BR>
- <code>get myBDKM INFO</code><br>
- Everything matching /temp/
- <code>get myBDKM INFO temp</code><br>
- Everything matching /Heaven/ or /Hell/
- <code>get myBDKM INFO Heaven Hell</code><br>
- Everything known:
- <code>get myBDKM INFO .*</code><br>
- Arguments to <code>INFO</code> are reqular expressions
- which are matched against all IDs and all aliases.
- </ul>
- <br>
- </ul>
- <br>
- <a name="BDKMattr"></a>
- <b>Attributes</b>
- <ul>
- <li>BaseInterval<br>
- The interval time in seconds between poll cycles.
- It defaults to 120 seconds. Which means that every 120 seconds a
- new poll collects values of IDs which turn it is.
- </li><br>
- <li>InterPollDelay<br>
- The delay time in milliseconds between reading of two IDs from
- the gateway. It defaults to 0 (read as fast as possible).
- Some gateways/heatings seem to stop answering after a while
- when you are reading a lot of IDs. (verbose 2 "communication ERROR").
- To avoid gateway hangups always try to read only as many IDs as
- really required. If it doesn't help try to increase the
- InterPollDelay value. E.g. start with 100.
- </li><br>
- <li>ReadBackDelay<br>
- Read back delay for the set command in milliseconds. This value
- defaults to 500 (0.5s). After setting a value, the gateway need
- some time before the value can be read back. If this delay is
- too short after writing you will get back the old value and not
- the expected new one. The default should work in most cases.
- </li><br>
- <li>HttpTimeout<br>
- Timeout for all HTTP requests in seconds (polling, set,
- get). This defaults to 10s. If there is no answer from the
- gateway for HttpTimeout time an error is returned. If a HTTP
- request expires while polling an error log (level 2) is
- generated and the request is automatically restarted after 60
- seconds.
- </li><br>
- <li>PollIds<br>
- Without this attribute FHEM readings are NOT generated
- automatically! <br>
- This attribute defines how and when IDs are polled within
- a base interval (set by atrribute <code>BaseInterval</code>).<br>
- The attribute contains list of space separated IDs and options
- written as <br>
- <code>GatewayID:Modulo:Delta:Alias</code>
- <br>
- Where Gateway is the real gateway ID like "/gateway/DateTime".<br>
- Modulo is the value which defines how often the GatewayID is
- polled from the gateway and checked for FHEM readings update.
- E.g. a value of 4 means that the ID is polled only every 4th cycle.<br>
- Delta defines the minimum difference a polled value must have to the
- previous reading, before a FHEM reading with the new value is generated.<br>
- Alias defines a short name for the GatewayID under which the gateway ID
- can be accessed. Also readings (Logfile entries) are generated with this
- short alias if set. If not set, the original ID is used.<br>
- In detail:<br>
- <code>ID:1:0:Alias</code> - poll every cycle, when difference >= 0 to previous reading (means always, also for strings) trigger FHEM reading to "Alias"<br>
- <code>ID:1::Alias</code> - poll every cycle, no Delta set => trigger FHEM reading to "Alias" on value change only<br>
- <code>ID:0::Alias</code> - update reading on startup once if reading changed (to the one prevously saved in fhem.save)<br>
- <code>ID:1:0.5:Alias</code> - poll every cycle, when difference => 0.5 trigger a FHEM reading to "Alias"<br>
- <code>ID:15::Alias</code> - poll every 15th cylce, update reading only if changed<br>
- <code>ID:::Alias</code> - update reading on (get/set) only and only if value changed<br>
- <code>ID::0:Alias</code> - update reading on (get/set) only and trigger reading always on get/set<br>
- <code>ID</code> - without colons ":", poll every cycle, update reading allways (same as <code>ID:1:0:</code>)<br>
- Also some usefull defaults can be set by the special keyword RC300DEFAULTS, RC35DEFAULTS, RC30DEFAULTS.<br>
- As I don't know anything about RC35 or RC30 the later keywords are currently empty (please send me some info with "get myBDKM INFO" :-)<br>
- Definitions set by the special keywords (see the module code for it) are overwritten by definitions later set in the attribute definition<br>
- Example:
- <ul>
- <code>attr myBDKM PollIds \<br>
- RC300DEFAULTS \<br>
- /gateway/DateTime:0::Date \<br>
- /system/info:0:0:\<br>
- /dhwCircuits/dhw1/actualTemp:1:0.2:WaterTemp
- </code><br>
- </ul>
- Which means: Use RC300DEFAULTS, trigger FHEM reading "Date" when date has changed on startup only. Trigger FHEM reading "/system/info" (no aliasing) always on startup, poll water temperature every cycle and trigger FHEM reading "WaterTemp" when difference to last reading was at least 0.2 degrees.
- <br>
- </li><br>
- </ul>
- </ul>
- =end html
|