diff --git a/fhem/FHEM/lib/VenetianBlinds/Shared.pm b/fhem/FHEM/lib/VenetianBlinds/Shared.pm deleted file mode 100644 index e49b31052..000000000 --- a/fhem/FHEM/lib/VenetianBlinds/Shared.pm +++ /dev/null @@ -1,89 +0,0 @@ -############################################## -# -# 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 deleted file mode 100644 index 3c57d01ed..000000000 --- a/fhem/FHEM/lib/VenetianBlinds/VenetianBlindController.pm +++ /dev/null @@ -1,283 +0,0 @@ -############################################## -# -# 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 deleted file mode 100644 index 81d0830ed..000000000 --- a/fhem/FHEM/lib/VenetianBlinds/VenetianMasterController.pm +++ /dev/null @@ -1,185 +0,0 @@ -############################################## -# -# 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 deleted file mode 100644 index 179e7f447..000000000 --- a/fhem/FHEM/lib/VenetianBlinds/VenetianRoomController.pm +++ /dev/null @@ -1,94 +0,0 @@ -############################################## -# -# 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;