2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-15 22:26:04 +00:00

99_Venetian: added new module

git-svn-id: https://svn.fhem.de/fhem/trunk@12226 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
elchefe 2016-10-01 07:15:11 +00:00
parent b65f56aaf7
commit 620fc65f3d
7 changed files with 905 additions and 0 deletions

251
fhem/FHEM/99_Venetian.pm Normal file
View File

@ -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
<a name="Venetian"></a>
<h3>Venetian</h3>
<ul>
<i>Venetian</i> 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.
<br/><br/>
<b>Note:</b> Venetian only works with Fibaro FGM-222 controllers!
<br/><br/>
A minimal set up consists of two devices: one <i>Master</i> and one <i>Blind</i>.
The <i>Master</i> will collect global data from your installation while the <i>Blind</i> will control the actual device.
For each physical device a <i>Blind</i> device must be defined.
<br/><br/>
You can add an optional device <i>Room</i> to control all <i>Blinds</i> in one room with one click.
For this to work your <i>Blinds</i> must be mapped to rooms with the standard attribute "room".
<br/><br/>
<a name="Venetian_Define"></a>
<b>Define</b>
<ul>
Master:<br/>
<code>define &lt;name&gt; Venetian type=master twilight=&lt;name&gt; weather=&lt;name&gt; wind_speed_threshold=&lt;value&gt;</code>
<br/><br/>
Room: <br/>
<code>define &lt;name&gt; Venetian type=room &#91;rooms=&lt;name&gt;,&lt;name&gt;,... &#93;</code>
<br/><br/>
Blind: <br/>
<code>define &lt;name&gt; Venetian type=blind master=&lt;name&gt; device=&lt;name&gt; could_index_threshold=&lt;number&gt; azimuth=&lt;start&gt;-&lt;end&gt; elevation=&lt;start&gt;-&lt;end&gt; months=&lt;start&gt;-&lt;end&gt;</code>
<br/><br/>
<b>Parameters (common)</b>
<ul>
<li/>type<br/>
is one of the values "master", "room" or "blind".
</ul>
<b>Parameters (Master)</b>
<ul>
<li/>twilight<br/>
Name of the <a href="#Twilight">Twilight</a> device.
This is used to get the current position of the sun.
<li/>weather<br/>
Name of the <a href="#Weather">Weather</a> device.
This is used to get the weather reports
<li/>wind_speed_threshold<br/>
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.
</ul>
<b>Parameters (Blind)</b>
<ul>
<li/>master</br>
name of the Venetian "Master" device.
<li/>device</br>
name of the blind controller (Fibaro FGM-222) to be controlled.
<li/>could_index_threshold</br>
Threshold for the cloudiness, as defined in <a href="https://de.wikipedia.org/wiki/Bew%C3%B6lkung">Wikipedia</a>.
The scale is 0 (clear sky) to 8 (overcast), where 9 is unknown.
The blinds are lowered only the the current cloud index is <i>below</i> this number.
<br/>
Example: <code>could_index_threshold=5</code> means broken
<li/>azimuth</br>
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.
<br/>
Example: <code>azimuth=90-120</code> means from 90° to 120°
<li/>elevation</br>
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.
<br/>
Example: <code>azimuth=10-90</code> means from 10° to 90°
<li/>months</br>
The range of months in which the blinds shall be closed, e.g. summer.
<br/>
Example: <code>azimuth=5-10</code> means from May to October
</ul>
<b>Parameters (Room)</b>
<ul>
<li/>rooms (optional)</br>
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.
<br/>
Example: <code>rooms=Kitchen,Living</code> means that all blinds in the rooms named "Kitchen" and "Living" are controlled.
</ul>
</ul>
<a name="Venetian_Set"></a>
<b>Set</b>
<ul>
<b>Blind:</b>
<ul>
<li/><code>set automatic</code></br>
Set blinds to automatic mode. This is the normal mode, if blinds should be set accoring to the sun position.
<li/><code>set stop</code></br>
Stop the blind movement
<li/><code>set windalarm</code></br>
Trigger the wind alarm. This will cause the blinds to be opened, regardless of the current state.
<li/><code>set &lt;scene&gt;</code></br>
Disable the automatic and set a scene manually. The currently available scenes are:
<ul>
<li/><code>open</code><br/>
Fully open the binds.
<li/><code>closed</code> <br/>
Fully close the blinds and slats.
<li/><code>see_through</code> <br/>
Fully close the blinds and set the slats horizontally, so that you can still <i>see through</i>.
<li/><code>shaded</code><br/>
Close the blinds and slightly close the slats. This is the scene when the automatic closes the blinds.
<li/><code>adaptive</code><br/>
<b>Experimental!</b> 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.
</ul>
</ul>
<b>Room:</b>
<ul>
<li/><code>set automatic</code></br>
Set all blinds in this room to automatic mode.
<li/><code>set stop</code></br>
Stop all blinds in this room.
<li/><code>set &lt;scene&gt;</code></br>
Set all blinds in this room to a certain scene.
</ul>
<b>Master:</b>
<ul>
<li/><code>set automatic</code></br>
Set all blinds to automatic mode.
<li/><code>set stop</code></br>
Stop all blinds.
<li/><code>set trigger_update</code></br>
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.
</ul>
</ul>
<!-- there are get operations at the moment
<a name="Venetian_Get"></a>
<b>Get</b>
<ul>
TODO
</ul>
-->
<!-- there are not attributes at the moment
<a name="Venetian_Attributes"></a>
<b>Set</b>
<ul>
TODO
</ul>
-->
</ul>
=end html
=cut

View File

@ -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;

View File

@ -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=<name>' is missing or undefined";
}
$hash->{master_controller} = $h->{master};
if (!defined $h->{device}) {
return "Mandatory argument 'device=<name>' is missing or undefined";
}
$hash->{device} = $h->{device};
if (!defined $h->{could_index_threshold}) {
return "Mandatory argument 'could_index_threshold=<value>' is missing or undefined";
}
$hash->{could_index_threshold} = $h->{could_index_threshold};
if (!defined $h->{azimuth} ) {
return "Mandatory argument 'azimuth=<start>-<end>' 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=<start>-<end>' 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=<start>-<end>' 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

View File

@ -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=<name>' is missing or undefined";
}
$hash->{twilight} = $h->{twilight};
if (!defined $h->{weather}) {
return "Mandatory argument 'weather=<name>' is missing or undefined";
}
$hash->{weather} = $h->{weather};
if (!defined $h->{wind_speed_threshold}) {
return "Mandatory argument 'wind_speed_threshold=<value>' 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;

View File

@ -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;

View File

@ -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

View File

@ -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