From 620fc65f3d0c99150e69e65deabddec0f241e611 Mon Sep 17 00:00:00 2001 From: elchefe <> Date: Sat, 1 Oct 2016 07:15:11 +0000 Subject: [PATCH] 99_Venetian: added new module git-svn-id: https://svn.fhem.de/fhem/trunk@12226 2b470e98-0d58-463d-a4d8-8e2adae1ed80 --- fhem/FHEM/99_Venetian.pm | 251 ++++++++++++++++ fhem/FHEM/lib/VenetianBlinds/Shared.pm | 89 ++++++ .../VenetianBlinds/VenetianBlindController.pm | 283 ++++++++++++++++++ .../VenetianMasterController.pm | 185 ++++++++++++ .../VenetianBlinds/VenetianRoomController.pm | 94 ++++++ fhem/HISTORY | 2 + fhem/MAINTAINER.txt | 1 + 7 files changed, 905 insertions(+) create mode 100644 fhem/FHEM/99_Venetian.pm create mode 100644 fhem/FHEM/lib/VenetianBlinds/Shared.pm create mode 100644 fhem/FHEM/lib/VenetianBlinds/VenetianBlindController.pm create mode 100644 fhem/FHEM/lib/VenetianBlinds/VenetianMasterController.pm create mode 100644 fhem/FHEM/lib/VenetianBlinds/VenetianRoomController.pm diff --git a/fhem/FHEM/99_Venetian.pm b/fhem/FHEM/99_Venetian.pm new file mode 100644 index 000000000..05fef39e0 --- /dev/null +++ b/fhem/FHEM/99_Venetian.pm @@ -0,0 +1,251 @@ +############################################## +# +# This is open source software licensed unter the Apache License 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +############################################## + +# $Id$ + +use v5.10.1; +use strict; +use warnings; +use POSIX; +use experimental "smartmatch"; + + +#add FHEM/lib to @INC if it's not allready included. Should rather be in fhem.pl than here though... +BEGIN { + if (!grep(/FHEM\/lib$/,@INC)) { + foreach my $inc (grep(/FHEM$/,@INC)) { + push @INC,$inc."/lib"; + }; + }; +}; + +use VenetianBlinds::VenetianMasterController; +use VenetianBlinds::VenetianRoomController; +use VenetianBlinds::VenetianBlindController; + +my %valid_types = ( + "master" =>"VenetianMasterController", + "room" => "VenetianRoomController", + "blind" => "VenetianBlindController", + ); + +sub Venetian_Initialize { + my ($hash) = @_; + $hash->{DefFn} = 'Venetian_Define'; + #$hash->{UndefFn} = 'Venetian_Undef'; + $hash->{SetFn} = 'Venetian_Set'; + #$hash->{GetFn} = 'Venetian_Get'; + #$hash->{AttrFn} = 'Venetian_Attr'; + #$hash->{ReadFn} = 'Venetian_Read'; + $hash->{NotifyFn} = 'Venetian_Notify'; + $hash->{parseParams} = 1; + return; +} + +sub Venetian_Define { + my ($hash, $a, $h) = @_; + $hash->{type} = $valid_types{$h->{type}}; + if (!defined $hash->{type}) { + return "Type $h->{type} is not supported!"; + } + return vbc_call("Define",$hash, $a, $h); +} + +sub Venetian_Set { + my ( $hash, $a,$h ) = @_; + my $result = undef; + return vbc_call("Set",$hash, $a, $h); +} + + +sub Venetian_Notify { + my ($own_hash, $dev_hash) = @_; + my $ownName = $own_hash->{NAME}; # own name / hash + return "" if(IsDisabled($ownName)); # Return without any further action if the module is disabled + + my $devName = $dev_hash->{NAME}; # Device that created the events + my $events = main::deviceEvents($dev_hash,1); + return if( !$events ); + + return vbc_call("Notify",$own_hash, $devName, $events); +} + +sub vbc_call{ + my ($func,$hash,$a,$h) = @_; + $func = "VenetianBlinds::$hash->{type}::$func"; + my $result; + { + ## no critic (ProhibitNoStrict) + no strict 'refs'; + $result = &$func($hash, $a, $h); + ## use critic + } + return $result; +} +1; + +=pod +=item summary automates venetian blinds depending on sun and weather +=begin html + + +

Venetian

+ + +=end html +=cut + diff --git a/fhem/FHEM/lib/VenetianBlinds/Shared.pm b/fhem/FHEM/lib/VenetianBlinds/Shared.pm new file mode 100644 index 000000000..e49b31052 --- /dev/null +++ b/fhem/FHEM/lib/VenetianBlinds/Shared.pm @@ -0,0 +1,89 @@ +############################################## +# +# This is open source software licensed unter the Apache License 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +############################################## + +package VenetianBlinds::Shared; +use v5.14; +use strict; +use warnings; +use experimental "smartmatch"; +use base 'Exporter'; + + +# constants ############################################# +use constant scenes => { + "open" => { + "blind" => 99, + "slat" => 99, + }, + "closed" => { + "blind" => 0, + "slat" => 0, + }, + "see_through" => { + "blind" => 0, + "slat" => 50, + }, + "shaded" => { + "blind" => 0, + "slat" => 30, + }, + "adaptive" => { + "blind" => 0, + "slat" => "adaptive", + }, +}; + +our @EXPORT_OK = ('scenes'); + +# functions ############################################# + +sub send_to_all{ + my ($cmd) = @_; + foreach my $device (find_devices()) { + main::fhem("set $device $cmd"); + } +} + +sub find_devices_in_room { + my ($my_room) = @_; + my @result = (); + my @devices = find_devices(); + foreach my $device (@devices){ + my $rooms = main::AttrVal($device,"room",undef); + if (defined $rooms){ + foreach my $room (split(/,/, $rooms)){ + if ($my_room eq $room){ + push(@result,$device); + } + } + } else { + main::Log(3,"Blinds '$device' not mapped to a room"); + } + } + return @result; +} + + +sub find_devices{ + my $devstr = main::fhem("list .* type"); + my @result = (); + foreach my $device (split /\n/, $devstr) { + $device =~ s/^\s+|\s+$//g; # trim white spaces + if( length($device) > 0){ + $device =~ /^(\S+)\s+(\S+)$/; + my $devname = $1; + my $model = $2; + if ($model eq "VenetianBlindController"){ + push(@result,$devname); + } + } + } + return @result; +} + + +1; \ No newline at end of file diff --git a/fhem/FHEM/lib/VenetianBlinds/VenetianBlindController.pm b/fhem/FHEM/lib/VenetianBlinds/VenetianBlindController.pm new file mode 100644 index 000000000..3c57d01ed --- /dev/null +++ b/fhem/FHEM/lib/VenetianBlinds/VenetianBlindController.pm @@ -0,0 +1,283 @@ +############################################## +# +# This is open source software licensed unter the Apache License 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +############################################## +package VenetianBlinds::VenetianBlindController; + +use v5.10.1; +use strict; +use warnings; +use experimental "smartmatch"; +use VenetianBlinds::Shared "scenes"; + + +# constants ######################## + +use constant blind_threshold => 5; #percentage points +use constant slat_threshold => 5; #percentage points +use constant power_threshold => 10; #watts + +# FHEM commands ######################## + +sub Define{ + #TODO: check if $device and $master really exist + my ($hash,$a,$h) = @_; + + if (!defined $h->{master}) { + return "Mandatory argument 'master=' is missing or undefined"; + } + $hash->{master_controller} = $h->{master}; + + if (!defined $h->{device}) { + return "Mandatory argument 'device=' is missing or undefined"; + } + $hash->{device} = $h->{device}; + + if (!defined $h->{could_index_threshold}) { + return "Mandatory argument 'could_index_threshold=' is missing or undefined"; + } + $hash->{could_index_threshold} = $h->{could_index_threshold}; + + if (!defined $h->{azimuth} ) { + return "Mandatory argument 'azimuth=-' is missing or undefined"; + } + my ($azstart,$azend) = split(/-/, $h->{azimuth}); + $hash->{azimuth_start} = $azstart; + $hash->{azimuth_end} = $azend; + + if (!defined $h->{elevation}) { + return "Mandatory argument 'elevation=-' is missing or undefined"; + } + my ($evstart,$evend) = split(/-/, $h->{elevation}); + $hash->{elevation_start} = $evstart; + $hash->{elevation_end} = $evend; + + if (!defined $h->{months} ) { + return "Mandatory argument 'months=-' is missing or undefined"; + } $hash->{azimuth_start} = $azstart; + my ($monstart,$monend) = split(/-/, $h->{months}); + $hash->{month_start} = $monstart; + $hash->{month_end} = $monend; + + return; +} + +sub Set{ + my ( $hash, $a,$h ) = @_; + my $cmd = $a->[1]; + my @scene_list = keys %{&scenes}; + if ( $cmd eq "?" ){ + my $result = "automatic:noArg wind_alarm:noArg stop:noArg"; + foreach my $scene (@scene_list){ + $result .= " $scene:noArg"; + } + return $result; + } elsif ($cmd eq "automatic") { + main::readingsSingleUpdate($hash,"automatic",1,1); + update_automatic($hash,1); + } elsif ($cmd ~~ @scene_list) { + main::readingsSingleUpdate($hash,"automatic",0,1); + set_scene($hash, $cmd, 0); + } elsif ($cmd eq "scenes") { + delete $hash->{scences}; + } elsif ($cmd eq "wind_alarm") { + wind_alarm($hash); + } elsif ($cmd eq "stop") { + stop($hash); + } else { + return "unknown command $cmd"; + } + return; +} + + +sub Notify{ + my ($hash, $devName, $events) = @_; + if ($devName eq $hash->{master_controller}){ + update_automatic($hash,0); + } elsif ($devName eq $hash->{device}) { + update_STATE($hash); + } + return; +} + + +# logic for blind control ##################### +sub update_automatic{ + my ($hash,$force) = @_; + my $master = $hash->{master_controller}; + my $sun_elevation = main::ReadingsVal($master, "sun_elevation", undef); + my $sun_azimuth = main::ReadingsVal($master, "sun_azimuth", undef); + my $wind_speed = main::ReadingsVal($master, "wind_speed", undef); + my $wind_alarm = main::ReadingsVal($master, "wind_alarm", undef); + my $cloud_index = main::ReadingsVal($master, "cloud_index", undef); + my $month = main::ReadingsVal($master, "month", undef); + my $automatic = main::ReadingsVal($hash->{NAME}, "automatic", undef); + my $old_scene = main::ReadingsVal($hash->{NAME}, "scene", undef); + my $mechanical_switches = main::ReadingsVal($hash->{device}, "reportedState", undef); + + # reasons to not work in automatic mode + if ($wind_alarm + or !$automatic + or $month < $hash->{month_start} + or $month > $hash->{month_end} + or $mechanical_switches eq "setOn" + or $mechanical_switches eq "setOff" ){ + return; + main::Log(3,"Automatic inactive on $hash->{NAME}"); + } + + my $new_scene = undef; + if ($hash->{elevation_start} <= $sun_elevation and + $sun_elevation <= $hash->{elevation_end} and + $hash->{azimuth_start} <= $sun_azimuth and + $sun_azimuth <= $hash->{azimuth_end} and + $cloud_index <= $hash->{could_index_threshold}) { + $new_scene ="shaded"; + } else { + $new_scene = "open"; + } + + if ($force or !($new_scene eq $old_scene) or ($new_scene eq "adaptive") ) { + set_scene($hash,$new_scene,0); + } else { + main::Log(5,"Scene has not changed on $hash->{NAME}, not moving blinds"); + } +} + +# smart slat control ####################### +# +# This equation converts the elevation of the sun to the angle of the slats. +# It's an approximation of the trigonometric functions for my slat geometry. +# +# The goal of the "adaptive" mode is to set the angle of the slats just closed +# enough that the sun can't get in. So if the sun elevation is very high +# (e.g. 60°) the slats should be vertical so that the sun can't get in, but you +# can still look out. If the elevation of the sun is very low (e.g. 10°) the +# slats should be fully closed as the sun would otherwise get through the slats. +# +# So the task is to compute the angle of the slats in a way that the sun does +# not get in, but you still can look through. +# +# For that we need to know the geometry of the slats. For my slats: a=7.2cm +# (distance between two slats) and b=8cm (length of the slats). +# the geometry is explained here: doc/adaptive_mode/slat_geomertry.jpg +# +# As it's quite compicated to solve the equation for beta, I created a spread +# sheet and used that to get an approximation for the curves. And this +# approximation is then implemented in this function. The spread sheet is in +# doc/adaptive_mode/approximation_for_slat_geomertry.ods +# +# TODO: to implement this properly, we need to make the slat geometry configurable + + +sub get_slats_for_elevation{ + my ($hash) = @_; + my $master = $hash->{master_controller}; + my $elevation = main::ReadingsVal($master, "sun_elevation", undef); + if ($elevation >= 45) { + return 50; + } elsif ($elevation <= 10) { + return 0; + } + return int($elevation*1.16 + 4); +} + + +# move the blinds ########################## +sub set_scene{ + my ($hash,$scene,$force) = @_; + my $automatic = main::ReadingsVal($hash->{NAME}, "automatic", undef); + my $old_scene = main::ReadingsVal($hash->{NAME}, "scene", undef); + + if (!defined &scenes->{$scene}){ + main::Log(1, "undefined scene &scenes->{$scene}"); + } else { + main::readingsSingleUpdate($hash,"scene",$scene,1); + main::Log(3,"moving blinds $hash->{device} to scene $scene."); + move_blinds($hash, &scenes->{$scene}{blind}, &scenes->{$scene}{slat}); + } + update_STATE($hash); +} + +sub update_STATE { + my ($hash) = @_; + my $automatic = main::ReadingsVal($hash->{NAME}, "automatic", undef); + my $scene = main::ReadingsVal($hash->{NAME}, "scene", undef); + my $mechanical_switches = main::ReadingsVal($hash->{device}, "reportedState", undef); + + if ($mechanical_switches eq "setOn") { + $hash->{STATE} = "mechanical: Up"; + } elsif ( $mechanical_switches eq "setOff") { + $hash->{STATE} = "mechanical: Down"; + } elsif ($automatic) { + $hash->{STATE} = "automatic: $scene"; + } else { + $hash->{STATE} = "manual: $scene"; + } +} + +sub move_blinds{ + my ($hash, $blind, $slat)= @_; + my ($current_blind, $current_slat) = get_position($hash); + if (defined $slat and $slat eq "adaptive") { + $slat = get_slats_for_elevation($hash); + } + if ( defined $blind and + abs($blind-$current_blind) > &blind_threshold ){ + main::fhem("set $hash->{device} positionBlinds $blind"); + count_commands($hash); + } + if ( defined $slat and + abs($slat - $current_slat) > &slat_threshold ){ + main::fhem("set $hash->{device} positionSlat $slat"); + } +} + +sub wind_alarm{ + my ($hash) = @_; + move_blinds($hash,99,undef); +} + +sub stop { + my ($hash) = @_; + main::fhem("set $hash->{device} stop"); + count_commands($hash); + delete $hash->{queue}; +} + +sub count_commands{ + my ($hash) = @_; + my $count = main::ReadingsVal($hash->{NAME}, "command_count", 0) +1; + main::readingsSingleUpdate($hash,"command_count",$count,0); +} + + +# wrappers around readings ############################# +sub get_power{ + my ($hash) = @_; + main::fhem("get $hash->{device} smStatus", undef); + my $power_reading = main::ReadingsVal($hash->{device}, "power", undef); + $power_reading =~ /([\d\.]+)\WW/; + if (!defined $1){ + main::Log(1,"Error reading power level of $hash->{device}:'$power_reading'"); + } + return $1; +} + +sub get_position{ + my ($hash) = @_; + main::fhem("get $hash->{device} position"); + my $device=$hash->{device}; + #TODO: do we really need a ReadingsVal or does the "get position" also deliver that result? + my $position = main::ReadingsVal($device, "position", undef); + $position =~ /Blind (\d+) Slat (\d+)/; + if (!defined $1 or !defined $2){ + main::Log( 1, "Error: could not get position of device $hash->{device}: $position"); + } + return ($1,$2); +} + +1; # end module diff --git a/fhem/FHEM/lib/VenetianBlinds/VenetianMasterController.pm b/fhem/FHEM/lib/VenetianBlinds/VenetianMasterController.pm new file mode 100644 index 000000000..81d0830ed --- /dev/null +++ b/fhem/FHEM/lib/VenetianBlinds/VenetianMasterController.pm @@ -0,0 +1,185 @@ +############################################## +# +# This is open source software licensed unter the Apache License 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +############################################## + +package VenetianBlinds::VenetianMasterController; +use v5.14; +use strict; +use warnings; +use experimental "smartmatch"; +use VenetianBlinds::Shared; + +# Map the condition codes from yahoo to cloudiness index, +# makes it easier to implement thresholds as higher number indicates more clouds +# https://developer.yahoo.com/weather/documentation.html +# https://de.wikipedia.org/wiki/Bew%C3%B6lkung#Einteilung_des_Flugwetterdienstes + +my $yahoo_code_map = { + #TODO: add mapping for more codes + 23 => 4, # blustery + 24 => 4, # windy + 26 => 5, # cloudy + 28 => 6, # mostly cloudy (day) + 30 => 3, # partly cloudy (day) + 32 => 1, # sunny + 34 => 2, # fair (day) + 36 => 0, # hot + 38 => 5, #scattered thunderstorms + 39 => 5, #scattered thunderstorms +}; + +sub Define{ + my ($hash,$a,$h) = @_; + + if (!defined $h->{twilight}) { + return "Mandatory argument 'twilight=' is missing or undefined"; + } + $hash->{twilight} = $h->{twilight}; + + if (!defined $h->{weather}) { + return "Mandatory argument 'weather=' is missing or undefined"; + } + $hash->{weather} = $h->{weather}; + + if (!defined $h->{wind_speed_threshold}) { + return "Mandatory argument 'wind_speed_threshold=' is missing or undefined"; + } + $hash->{wind_speed_threshold} = $h->{wind_speed_threshold}; + + $hash->{STATE} = "OK"; + return; +} + +sub Set{ + my ($hash,$a,$h) = @_; + my $cmd = $a->[1]; + if ($cmd eq "?"){ + return "trigger_update:noArg stop:noArg automatic:noArg"; + } elsif ($cmd eq "trigger_update") { + trigger_update($hash); + } elsif ($cmd eq "stop") { + VenetianBlinds::Shared::send_to_all("stop"); + } elsif ($cmd eq "automatic") { + VenetianBlinds::Shared::send_to_all("automatic"); + } else { + return "unknown command $cmd"; + } + return; +} + + +sub Notify{ + my ($hash, $devName, $events) = @_; + if ($devName eq $hash->{twilight}) { + update_twilight($hash); + } elsif ($devName eq $hash->{weather}){ + update_weather($hash); + check_wind_alarm($hash); + } + + #foreach my $event (@{$events}) { + # $event = "" if(!defined($event)); + # main::Log(3,"Event on device $devName: $event"); + #} + return; +} + +sub trigger_update { + my ($hash) = @_; + update_twilight($hash); + update_calendar($hash); + update_weather($hash); + check_wind_alarm($hash); + return + +} + +sub update_twilight{ + my ($hash) = @_; + # TODO: reduce number of events: only trigger event if data has changed + main::readingsBeginUpdate($hash); + main::readingsBulkUpdate($hash, "sun_elevation", + main::ReadingsVal($hash->{twilight}, "elevation", undef) ); + main::readingsBulkUpdate($hash, "sun_azimuth", + main::ReadingsVal($hash->{twilight}, "azimuth", undef) ); + main::readingsEndUpdate($hash, 1); + return; +} + +sub update_calendar{ + my ($hash) = @_; + my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time); + # Note: months start at 0 = January + $mon +=1; + my $current = main::ReadingsVal($hash->{NAME}, "month", undef); + if ($mon != $current){ + main::readingsSingleUpdate($hash,"month",$mon,1); + } + #TODO: do the update exactly at midnight + main::InternalTimer(main::gettimeofday()+24*60*60, "VenetianMasterController::update_calendar", $hash, 1); + return ; +} + +sub update_weather{ + my ($hash) = @_; + my $condition_code = main::ReadingsVal($hash->{weather}, "code", undef); + if (!defined $condition_code) { + main::Log(1,"could not get Weather condition code from '$hash->{weather}'"); + } + my $cloud_index = undef; + $cloud_index = $yahoo_code_map->{$condition_code}; + if (!defined $cloud_index){ + $cloud_index = 9; + }; + my $wind_speed = main::ReadingsVal($hash->{weather}, "wind_speed", undef); + if (!defined $wind_speed) { + main::Log(1,"could not get Weather wind_speed from '$hash->{weather}'"); + } + + # TODO: reduce number of events: only trigger event if data has changed + main::readingsBeginUpdate($hash); + main::readingsBulkUpdate($hash, "wind_speed", $wind_speed); + main::readingsBulkUpdate($hash, "cloud_index", $cloud_index); + main::readingsEndUpdate($hash, 1); + + return; +} + +sub check_wind_alarm{ + my ($hash) = @_; + my $windspeed = main::ReadingsVal($hash->{NAME}, "wind_speed", undef); + my $windalarm = main::ReadingsVal($hash->{NAME}, "wind_alarm", undef); + given ($windalarm) { + when (0) { + if (($windspeed >= $hash->{wind_speed_threshold})){ + main::Log(3,"Wind alarm: $windspeed km/h"); + main::readingsSingleUpdate($hash,"wind_alarm",1,1); + VenetianBlinds::Shared::send_to_all("wind_alarm"); + $hash->{STATE} = "wind alarm"; + } + } + + when (1) { + if (($windspeed >= $hash->{wind_speed_threshold})){ + main::readingsSingleUpdate($hash,"wind_alarm",1,1); + } else { + if (main::ReadingsAge($hash->{NAME},"wind_speed",undef) > 600) { + main::readingsSingleUpdate($hash,"wind_alarm",0,1); + main::Log(3,"Wind alarm ended."); + $hash->{STATE} = "normal"; + } + } + } + when (undef) { + main::readingsSingleUpdate($hash,"wind_alarm",0,0); + } + } + return; +} + + +1; + diff --git a/fhem/FHEM/lib/VenetianBlinds/VenetianRoomController.pm b/fhem/FHEM/lib/VenetianBlinds/VenetianRoomController.pm new file mode 100644 index 000000000..179e7f447 --- /dev/null +++ b/fhem/FHEM/lib/VenetianBlinds/VenetianRoomController.pm @@ -0,0 +1,94 @@ +############################################## +# +# This is open source software licensed unter the Apache License 2.0 +# http://www.apache.org/licenses/LICENSE-2.0 +# +############################################## + +package VenetianBlinds::VenetianRoomController; + +use v5.10.1; +use strict; +use warnings; +use experimental "smartmatch"; +use VenetianBlinds::Shared; + + +# fhem interface ############################################# +sub Define{ + my ($hash,$a,$h) = @_; + $hash->{STATE} = "OK"; + if (defined $h->{rooms}) { + $hash->{rooms} = $h->{rooms}; + } + return; +} + +sub Notify { + my ($hash, $devName, $events) = @_; + return; +} + +sub Set{ + my ($hash,$a,$h) = @_; + my $cmd = $a->[1]; + my @scene_list = keys %{&VenetianBlinds::Shared::scenes}; + given ($cmd) { + when ("?") { + my $result = "automatic:noArg stop:noArg automatic:noArg"; + foreach my $scene (@scene_list){ + $result .= " $scene:noArg"; + } + return $result; + } + when ("automatic") { + send_to_all_in_my_rooms($hash, "automatic"); + } + when ("stop"){ + send_to_all_in_my_rooms($hash, "stop"); + } + when (@scene_list){ + send_to_all_in_my_rooms($hash, $cmd); + } + default { + return "Unkown command $cmd"; + } + } + return; +} + +# room logic ############################################# + +sub send_to_all_in_my_rooms{ + my ($hash, $cmd) = @_; + foreach my $room ( get_my_rooms($hash) ){ + foreach my $device (VenetianBlinds::Shared::find_devices_in_room($room)) { + main::fhem("set $device $cmd"); + } + } +} + +sub send_to_all_in_room{ + my ($cmd,$room) = @_; + foreach my $device (find_devices_in_room($room)) { + main::fhem("set $device $cmd"); + } +} + +sub get_my_rooms{ + my ($hash) = @_; + + my $rooms = undef; + if (defined $hash->{rooms}){ + $rooms = $hash->{rooms}; + } else { + $rooms = main::AttrVal($hash->{NAME},"room",undef); + } + if (!defined $rooms) { + main::Log(1,"Error reading rooms for VenetianRoomController '$hash->{NAME}'"); + return; + } + return split(/,/,$rooms); +} + +1; diff --git a/fhem/HISTORY b/fhem/HISTORY index 90cbd9d0f..7a3257c2f 100644 --- a/fhem/HISTORY +++ b/fhem/HISTORY @@ -692,3 +692,5 @@ - Mon Sep 12 2016 (zap) - added modules 88_HMCCU, 88_HMCCUCHN, 88_HMCCUDEV HMCCUConf for interface FHEM - Homematic CCU2 +- Sat Oct 1 2016 (Christian.Kühnel) + - added new module 99_Venetian diff --git a/fhem/MAINTAINER.txt b/fhem/MAINTAINER.txt index fba4bdfda..b34d5ee10 100644 --- a/fhem/MAINTAINER.txt +++ b/fhem/MAINTAINER.txt @@ -384,6 +384,7 @@ FHME/98_weekprofile.pm risiko http://forum.fhem.de Frontends FHEM/98_STOCKQUOTES.pm vbs http://forum.fhem.de Unterstuetzende Dienste FHEM/99_SUNRISE_EL.pm rudolfkoenig http://forum.fhem.de Automatisierung FHEM/99_Utils.pm rudolfkoenig http://forum.fhem.de Automatisierung +FHEM/99_Venetian.pm Christian.Kühnel http://forum.fhem.de Automatisierung FHEM/Blocking.pm rudolfkoenig http://forum.fhem.de Automatisierung FHEM/DevIo.pm rudolfkoenig http://forum.fhem.de Sonstiges FHEM/Color.pm justme1968 http://forum.fhem.de Sonstiges