2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-04-25 15:59:21 +00:00

99_Venetian: removed /FHEM/lib/VenetianBlinds as it's against the conventions

git-svn-id: https://svn.fhem.de/fhem/trunk@12249 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
elchefe 2016-10-03 06:37:58 +00:00
parent 01d49ad8c1
commit 1d8eafdca1
4 changed files with 0 additions and 651 deletions

View File

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

View File

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

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

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