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
+
+ Venetian implements a fully automated solution to control venetian blinds.
+ It will consinder lots of input data to set the blinds as a human being would do.
+ It use the sun position (from Twilight) the weather (from Weather) and orientation of the blinds.
+ The blinds are opened automatically if the wind speed exceeds a user defined threshold.
+
+ Note: Venetian only works with Fibaro FGM-222 controllers!
+
+ A minimal set up consists of two devices: one Master and one Blind.
+ The Master will collect global data from your installation while the Blind will control the actual device.
+ For each physical device a Blind device must be defined.
+
+ You can add an optional device Room to control all Blinds in one room with one click.
+ For this to work your Blinds must be mapped to rooms with the standard attribute "room".
+
+
+ Define
+
+ Master:
+ define <name> Venetian type=master twilight=<name> weather=<name> wind_speed_threshold=<value>
+
+
+ Room:
+ define <name> Venetian type=room [rooms=<name>,<name>,... ]
+
+
+ Blind:
+ define <name> Venetian type=blind master=<name> device=<name> could_index_threshold=<number> azimuth=<start>-<end> elevation=<start>-<end> months=<start>-<end>
+
+
+ Parameters (common)
+
+ type
+ is one of the values "master", "room" or "blind".
+
+ Parameters (Master)
+
+ twilight
+ Name of the Twilight device.
+ This is used to get the current position of the sun.
+ weather
+ Name of the Weather device.
+ This is used to get the weather reports
+ wind_speed_threshold
+ Maximum wind speed in km/h the venetian blinds are designed for.
+ If the measured wind speed exceed this threshold, the blinds are opened automatically to prevent damages.
+
+ Parameters (Blind)
+
+ master
+ name of the Venetian "Master" device.
+ device
+ name of the blind controller (Fibaro FGM-222) to be controlled.
+ could_index_threshold
+ Threshold for the cloudiness, as defined in Wikipedia.
+ The scale is 0 (clear sky) to 8 (overcast), where 9 is unknown.
+ The blinds are lowered only the the current cloud index is below this number.
+
+ Example: could_index_threshold=5
means broken
+ azimuth
+ The range of the azimuth of the sun, in which the blinds shall be closed.
+ This is measured in degrees.
+ This is defined by the pyhiscal orientation of your window.
+ You can use a compass to measure this for every window.
+
+ Example: azimuth=90-120
means from 90° to 120°
+ elevation
+ The range of the elevation of the sun, in which the blinds shall be closed.
+ This is measured in degrees.
+ You can guess this from the pyhsical localtion of your window and obstacles around your building.
+
+ Example: azimuth=10-90
means from 10° to 90°
+ months
+ The range of months in which the blinds shall be closed, e.g. summer.
+
+ Example: azimuth=5-10
means from May to October
+
+ Parameters (Room)
+
+ rooms (optional)
+ Comma separated list of rooms in which this device shall control the blinds.
+ If this is not defined, the rooms from the Attribute of this device are used.
+
+ Example: rooms=Kitchen,Living
means that all blinds in the rooms named "Kitchen" and "Living" are controlled.
+
+
+
+ Set
+
+ Blind:
+
+ set automatic
+ Set blinds to automatic mode. This is the normal mode, if blinds should be set accoring to the sun position.
+ set stop
+ Stop the blind movement
+ set windalarm
+ Trigger the wind alarm. This will cause the blinds to be opened, regardless of the current state.
+ set <scene>
+ Disable the automatic and set a scene manually. The currently available scenes are:
+
+ open
+ Fully open the binds.
+ closed
+ Fully close the blinds and slats.
+ see_through
+ Fully close the blinds and set the slats horizontally, so that you can still see through.
+ shaded
+ Close the blinds and slightly close the slats. This is the scene when the automatic closes the blinds.
+ adaptive
+ Experimental! Tries to set the slats so that they are just closed enough so that the sun does not get in.
+ This takes the elevation of the sun and geometry of the slats into consideration.
+
+
+ Room:
+
+ set automatic
+ Set all blinds in this room to automatic mode.
+ set stop
+ Stop all blinds in this room.
+ set <scene>
+ Set all blinds in this room to a certain scene.
+
+ Master:
+
+ set automatic
+ Set all blinds to automatic mode.
+ set stop
+ Stop all blinds.
+ set trigger_update
+ Trigger all blinds in automatic mode to update their scenes to the current sun position.
+ This should not be neccesary in normal operation but is useful when trying our different parameters.
+
+
+
+
+
+
+
+
+
+=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