#!/usr/bin/perl
######################################################################
#
# tellstickControllerTdtool program
#
# Copyright (C) 2008-2010 Rickard Andersson (ran42ran@gmail.com)
# Version 2.0.0
#
######################################################################
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
######################################################################
use warnings;
use strict;
use sigtrap 'handler', \&daemon_exit, 'normal-signals';
use sigtrap 'die', 'untrapped', 'normal-signals';
use sigtrap 'stack-trace', 'any', 'error-signals';
use POSIX 'setsid';
use DateTime;
use DateTime::Event::Sunrise;
######################################################################
# All these cfg settings can be altered from the configuration file.
#
my %cfg_set = (
"configfile" => "/etc/tellstickController.conf",
"logfile" => "/var/log/tellstickController.log",
"pidfile" => "/var/run/tellstickController.pid",
"program" => "/usr/bin/tdtool",
"altitude" => "-0.833",
"latitude" => "58.24",
"longitude" => "15.31",
"timezone" => "Europe/Stockholm",
);
######################################################################
my %cfg_group = ();
my %cfg_groupId = ();
my %cfg_idGroup = ();
my %cfg_alias = ();
my %cfg_aliasId = ();
my %cfg_idAlias = ();
my @cfg_rule = ();
my %option = (
"aliases" => 0,
"check" => 0,
"daemon" => 0,
"list" => 0,
"device" => "",
"state" => "",
"swapfirst" => 0,
"swap" => 0,
"test" => 0,
"verbose" => 0,
);
my @device = ();
######################################################################
sub check_args(@) {
my (@args) = @_;
if (not @args) {
usage();
exit;
}
while (@args) {
if ($args[0] =~ /^-(h|-help)$/) {
usage();
exit;
} elsif ($args[0] =~ /^-(a|-aliases)$/) {
shift(@args);
$option{"aliases"} = 1;
next;
} elsif ($args[0] =~ /^-(c|-check)$/) {
shift(@args);
$option{"check"} = 1;
next;
} elsif ($args[0] =~ /^-(d|-daemon)$/) {
shift(@args);
$option{"daemon"} = 1;
next;
} elsif ($args[0] =~ /^-(f|-file)$/) {
shift(@args);
$cfg_set{"configfile"} = shift(@args);
next;
} elsif ($args[0] =~ /^-(g|-get)$/) {
shift(@args);
$option{"get"} = 1;
$option{"device"} = lc($args[0]);
shift(@args);
next;
} elsif ($args[0] =~ /^-(l|-list)$/) {
shift(@args);
$option{"list"} = 1;
next;
} elsif ($args[0] =~ /^-(s|-set)$/) {
shift(@args);
$option{"set"} = 1;
$option{"device"} = lc($args[0]);
shift(@args);
$option{"state"} = lc($args[0]);
shift(@args);
next;
} elsif ($args[0] =~ /^-(w|-swapfirst)$/) {
shift(@args);
$option{"swapfirst"} = 1;
$option{"device"} = lc($args[0]);
shift(@args);
next;
} elsif ($args[0] =~ /^-(x|-swap)$/) {
shift(@args);
$option{"swap"} = 1;
$option{"device"} = lc($args[0]);
shift(@args);
next;
} elsif ($args[0] =~ /^-(t|-test)$/) {
shift(@args);
$option{"test"} = 1;
next;
} elsif ($args[0] =~ /^-(v|-verbose)$/) {
shift(@args);
$option{"verbose"} = 1;
next;
}
usage();
exit;
}
}
sub usage() {
while () {
if ($_ =~ /^#-(.*)/) {
print "$1\n";
}
}
}
sub printLine($) {
my ($text) = @_;
print "$text\n";
}
sub printlogger($) {
my ($text) = @_;
if ($option{"daemon"}) {
my $now = get_datetime_now();
my $logfile = $cfg_set{"logfile"};
my $output = "echo '$now $text' >> $logfile";
system ($output);
}
}
######################################################################
sub daemon_init() {
chdir "/";
umask 0;
my $pid = fork;
exit if $pid;
die "Couldn't fork program" unless defined(\$pid);
setsid() or die "Couldn't start new program session";
open STDIN, '/dev/null' or die "Couldn't read /dev/null!";
open STDOUT, '>/dev/null' or die "Couldn't write to /dev/null!";
open STDERR, '>/dev/null' or die "Couldn't write to /dev/null!";
my $pidfile = $cfg_set{"pidfile"};
$0="tellstickControllerDaemon";
$pid = $$;
system("echo $pid > $pidfile") if (defined($pidfile));
printlogger "> Daemon started";
}
sub daemon_exit() {
printlogger "> Daemon stopped";
unlink $cfg_set{"pidfile"} if (defined($cfg_set{"pidfile"}));
my $pid = $$;
kill('TERM', $pid);
exit;
}
sub get_datetime_now() {
my $now = DateTime->now(time_zone => $cfg_set{"timezone"});
return $now;
}
sub get_datetime_sunrise($) {
my ($now) = @_;
my $sunrise = DateTime::Event::Sunrise->sunrise (
altitude => $cfg_set{"altitude"},
latitude => $cfg_set{"latitude"},
longitude => $cfg_set{"longitude"},
iteration => '4'
);
my $time = $sunrise->next($now);
return $time;
}
sub get_datetime_sunset($) {
my ($now) = @_;
my $sunset = DateTime::Event::Sunrise->sunset (
altitude => $cfg_set{"altitude"},
latitude => $cfg_set{"latitude"},
longitude => $cfg_set{"longitude"},
iteration => '2'
);
my $time = $sunset->next($now);
return $time;
}
sub get_info_from_program() {
my $prog = $cfg_set{"program"};
my $command = "$prog --list";
my $text = "Executing command: '$command'";
printLine $text if ($option{"verbose"});
my @result = qx($command);
foreach my $line (@result) {
chomp($line);
$line =~ s/\s+/ /g;
if ($line =~ /^\d+/i) {
my ($id, $name, $state) = split(/\s+/, $line);
if (defined($name)) {
$cfg_alias{lc($name)} = 'off';
if (defined($state)) {
$cfg_alias{lc($name)} = lc($state);
}
if (defined($id)) {
$cfg_idAlias{lc($id)} = lc($name);
$cfg_aliasId{lc($name)} = lc($id);
}
}
}
}
}
sub read_config($) {
my ($infile) = @_;
my $text = "> Reading configurationfile started";
printLine $text if ($option{"verbose"});
printlogger $text;
open(my $indata, $infile) or die "Couldn't read configfile '$infile'!";
while(my $line = <$indata>) {
chomp($line);
$line =~ s/\s+/ /g;
if ($line =~ /^Set\s+/i) {
my (undef, $key, $val, $suffix) = split(/\s+/, $line);
if (defined($suffix)) {
printLine "Wrong argument '$suffix' in line '$line'!";
} else {
if (defined($key)) {
if (defined($val)) {
$cfg_set{lc($key)} = $val;
} else {
printLine "Wrong value in line '$line'!";
}
} else {
printLine "Wrong key in line '$line'!";
}
}
next;
}
}
close $indata;
get_info_from_program();
open($indata, $infile) or die "Couldn't read configfile '$infile'!";
while(my $line = <$indata>) {
chomp($line);
$line =~ s/\s+/ /g;
if ($line =~ /^Group\s+/i) {
my (undef, $name, $id, $delay, $aliases) = split(/\s+/, $line, 5);
$name = lc($name);
$delay = lc($delay);
if (defined $id) {
$cfg_idGroup{$id} = $name;
$cfg_groupId{$name} = $id;
}
if ($delay =~ /^\d+$/) {
my $error = 0;
$aliases = lc($aliases);
my @aliaslist = split(/\s+/, $aliases);
foreach my $alias (@aliaslist) {
if (! exists($cfg_alias{$alias})) {
printLine "Wrong alias '$alias' in line '$line'!";
$error += 1;
}
}
if ($error == 0) {
$cfg_group{$name} = "$delay $aliases";
}
} else {
printLine "Wrong delay time '$delay' in line '$line'!";
}
next;
}
if ($line =~ /^Rule\s+/i) {
my (undef, $alias, $on, $off, $suffix) = split(/\s+/, $line);
if (defined($suffix)) {
printLine "Wrong argument '$suffix' in line '$line'!";
} else {
$alias = lc($alias);
$on = lc($on);
$off = lc($off);
if ($on eq "") {
printLine "Wrong on time '$on' in line '$line'!";
} elsif ($off eq "") {
printLine "Wrong off time '$off' in line '$line'!";
} else {
if ($on !~ /[\w\/\+\-\:\$\(\)]+/) {
printLine "Wrong on time '$on' in line '$line'!";
} elsif ($off !~ /[\w\/\+\-\:\$\(\)]+/) {
printLine "Wrong off time '$off' in line '$line'!";
}
}
if (exists($cfg_alias{$alias})) {
push @cfg_rule, [$alias, $on, $off];
} elsif (exists($cfg_group{$alias})) {
push @cfg_rule, [$alias, $on, $off];
} else {
printLine "Wrong alias '$alias' in line '$line'!";
}
}
next;
}
}
close $indata;
$text = "=== Set ===";
printLine $text if ($option{"check"});
printlogger $text;
foreach my $key (sort keys %cfg_set) {
$text = "$key = $cfg_set{$key}";
printLine $text if ($option{"check"});
printlogger $text;
}
$text = "=== Alias ===";
printLine $text if ($option{"check"});
printlogger $text;
foreach my $key (sort keys %cfg_alias) {
$text = "$key($cfg_aliasId{$key}) = $cfg_alias{$key}";
printLine $text if ($option{"check"});
printlogger $text;
}
$text = "=== Group ===";
printLine $text if ($option{"check"});
printlogger $text;
foreach my $key (sort keys %cfg_group) {
my $val = $cfg_group{$key};
my ($delay, $aliases) = split(/\s+/, $val, 2);
$text = "$key($cfg_groupId{$key}) =";
$text .= " delay time $delay seconds";
$text .= " between aliases ($aliases)";
printLine $text if ($option{"check"});
printlogger $text;
}
$text = "=== Rule ===";
printLine $text if ($option{"check"});
printlogger $text;
foreach my $rule (@cfg_rule) {
my ($alias, $on, $off) = @$rule;
$text = "$alias =";
$text .= " on at $on" if ($on !~ /no/);
$text .= " and" if (($on !~ /no/) && ($off !~ /no/));
$text .= " off at $off" if ($off !~ /no/);
printLine $text if ($option{"check"});
printlogger $text;
}
$text = "> Reading configurationfile finished";
printLine $text if ($option{"verbose"});
printlogger $text;
}
sub call_program($$) {
my ($device, $state) = @_;
if ($option{"test"}) {
my $text = "Test mode, no real device will be used";
printLine $text if ($option{"verbose"});
printlogger $text;
} else {
my $mode = "--off";
if ($state eq "off") {
$mode = "--off";
} elsif ($state eq "on") {
$mode = "--on";
} elsif (($state >= 0) && ($state <= 255)) {
$mode = "--dimlevel $state --dim";
}
my $prog = $cfg_set{"program"};
my $command = "$prog $mode $device";
my $text = "Executing command: '$command'";
printLine $text if ($option{"verbose"});
system($command);
}
}
sub load_device_rules () {
my $text = "> Loading device rules started";
printLine $text if ($option{"verbose"});
printlogger $text;
my $now = get_datetime_now();
$text = "Time = $now";
printLine $text if ($option{"verbose"});
printlogger $text;
my $today = get_datetime_now();
$today->set( hour => 0, minute => 0, second => 0 );
$text = "Today = $today";
printLine $text if ($option{"verbose"});
printlogger $text;
my $sunrise = get_datetime_sunrise($today);
$text = "Sunrise = $sunrise";
printLine $text if ($option{"verbose"});
printlogger $text;
my $sunset = get_datetime_sunset($today);
$text = "Sunset = $sunset";
printLine $text if ($option{"verbose"});
printlogger $text;
foreach my $rule (@cfg_rule) {
my ($alias, $on, $off) = @$rule;
if (exists($cfg_alias{$alias})) {
my $time = get_rule_datetime($on, $today, $sunrise, $sunset);
my $value = 'on';
if ($on =~ /dimmer\((.*)\)/i) {
load_dimmer_rules($alias, $time, $on, $now, $today, $sunrise, $sunset);
} else {
push @device, [$time, $alias, $value] if (defined($time) && ($now <= $time));
}
$time = get_rule_datetime($off, $today, $sunrise, $sunset);
$value = 'off';
if ($off =~ /dimmer\((.*)\)/i) {
load_dimmer_rules($alias, $time, $off, $now, $today, $sunrise, $sunset);
} else {
push @device, [$time, $alias, $value] if (defined($time) && ($now <= $time));
}
} else {
foreach my $val ($cfg_group{$alias}) {
my ($delay, $aliases) = split(/\s+/, $val, 2);
my $timedelay = 0;
my $ontime = get_rule_datetime($on, $today, $sunrise, $sunset);
my $offtime = get_rule_datetime($off, $today, $sunrise, $sunset);
my (@aliaslist) = split(/\s+/, $aliases);
foreach my $device (@aliaslist) {
if (defined($ontime)) {
my $time = $ontime->clone->add(seconds => $timedelay);
my $value = 'on';
if ($on =~ /dimmer\((.*)\)/i) {
load_dimmer_rules($device, $time, $on, $now, $today, $sunrise, $sunset);
} else {
push @device, [$time, $device, $value] if (defined($time) && ($now <= $time));
}
}
if (defined($offtime)) {
my $time = $offtime->clone->add(seconds => $timedelay);
my $value = 'off';
if ($off =~ /dimmer\((.*)\)/i) {
load_dimmer_rules($device, $time, $off, $now, $today, $sunrise, $sunset);
} else {
push @device, [$time, $device, $value] if (defined($time) && ($now <= $time));
}
}
$timedelay += $delay;
}
}
}
}
@device = sort { $a->[0] cmp $b->[0] } @device;
$text = "=== Device ===";
printLine $text if ($option{"verbose"});
printlogger $text;
foreach my $rule (@device) {
my ($time, $alias, $value) = @$rule;
$text = "$alias = $value at $time";
printLine $text if ($option{"verbose"});
printlogger $text;
}
$text = "> Loading device rules finished";
printLine $text if ($option{"verbose"});
printlogger $text;
}
sub get_rule_datetime($$$$) {
my ($rule, $now, $sunrise, $sunset) = @_;
my ($date, $time) = split(/\//, $rule);
if (not defined($time)) {
$time = $date;
$date = $now->ymd;
} else {
my $today = 0;
my $match = 0;
if ($date =~ /([\d#]{4})-([\d#]{2})-([\d#]{2})/) {
$match = 1;
$today = 1 if ((($1 =~ /####/) || ($now->year == $1))
&& (($2 =~ /##/) || ($now->month == $2))
&& (($3 =~ /##/) || ($now->day == $3)));
}
if ($date =~ /workweek/) {
$match = 1;
$today = 1 if ($now->day_of_week < 6);
}
if ($date =~ /weekend/) {
$match = 1;
$today = 1 if ($now->day_of_week > 5);
}
if ($date =~ /monday/) {
$match = 1;
$today = 1 if ($now->day_of_week == 1);
}
if ($date =~ /tuesday/) {
$match = 1;
$today = 1 if ($now->day_of_week == 2);
}
if ($date =~ /wednesday/) {
$match = 1;
$today = 1 if ($now->day_of_week == 3);
}
if ($date =~ /thursday/) {
$match = 1;
$today = 1 if ($now->day_of_week == 4);
}
if ($date =~ /friday/) {
$match = 1;
$today = 1 if ($now->day_of_week == 5);
}
if ($date =~ /saturday/) {
$match = 1;
$today = 1 if ($now->day_of_week == 6);
}
if ($date =~ /sunday/) {
$match = 1;
$today = 1 if ($now->day_of_week == 7);
}
if ($match) {
if ($today) {
$date = $now->ymd;
} else {
$time = undef;
}
} else {
printLine "Wrong date '$date' for rule '$rule'!";
$time = undef;
}
}
if (defined($time) && ($time !~ /^no$/)) {
my $days = 0;
my $hours = 0;
my $minutes = 0;
my $lastOp = "+";
my $expr = "";
my $op = "";
my $rest = "";
my $mins = 0;
while ($time ne "") {
$op = $lastOp;
if ($time =~ /(.+?)(\+|-)(.*)/) {
$expr = $1;
$lastOp = $2;
$rest = $3;
$time = $rest;
} elsif ($time =~ /(.+)/) {
$expr = $1;
$lastOp = "";
$rest = "";
$time = "";
}
if ($expr =~ /^sunrise$/) {
$expr = 0;
if ($sunrise->hms =~ /(\d\d):(\d\d):(\d\d)/) {
$expr = $1*60+$2;
}
} elsif ($expr =~ /^sunset$/) {
$expr = 0;
if ($sunset->hms =~ /(\d\d):(\d\d):(\d\d)/) {
$expr = $1*60+$2;
}
} elsif ($expr =~ /^random\((\d\d):(\d\d)\)$/) {
$expr = int rand($1*60+$2+1);
} elsif ($expr =~ /^dimmer\((\d\d):(\d\d).*\)$/) {
$expr = 0;
} elsif ($expr =~ /^(\d\d):(\d\d)$/) {
$expr = $1*60+$2;
} else {
printLine "Wrong time '$expr' for rule '$rule'!";
return;
$expr = "";
}
if ($op eq "+") {
$mins += $expr;
} elsif ($op eq "-") {
$mins -= $expr;
}
}
if ($mins <= 0) {
$mins = $mins % (24*60);
$days = 1;
} elsif ($mins >= 24*60) {
$mins = $mins % (24*60);
$days = 1;
}
$hours = int $mins/60;
$minutes = int $mins%60;
# Hopefully we will deal with UTC and daylightsavingtime in a decent fashion?
$time = get_datetime_now();
$time->set(hour => 0, minute => 0, second => 0);
$time->add(hours => $hours+24*$days, minutes => $minutes, seconds => 0);
} else {
$time = undef;
}
return $time;
}
sub load_dimmer_rules($$$$$$$$){
my ($alias, $time, $expr, $now, $today, $sunrise, $sunset) = @_;
my $dimmer =$expr;
if ($expr =~ /dimmer\((.*)\)/i) {
$dimmer = $1;
}
my ($offset, $start, $stop, $step) = split(/\,/,$dimmer);
if (!defined($offset)) {
$offset = '';
}
if ($offset !~ /^\d\d:\d\d$/) {
printLine "Wrong dimmer time offset '$offset' for rule '$expr'!";
next;
}
if (!defined($start)) {
$start = '';
}
if (($start !~ /^(\d+)$/) || (($start < 0) || ($start > 255))) {
printLine "Wrong dimmer start level '$start' for rule '$expr'!";
next;
}
if (!defined($stop)) {
$stop = '';
}
if (($stop !~ /^(\d+)$/) || (($stop < 0) || ($stop > 255))) {
printLine "Wrong dimmer stop level '$stop' for rule '$expr'!";
next;
}
if (!defined($step)) {
$step = '';
}
if (($step !~ /^(\d+)$/) || (($step < 1) || ($step > 255))) {
printLine "Wrong dimmer step level '$step' for rule '$expr'!";
next;
}
if (! defined($time)) {
return;
}
my $dimtime = $time;
my $level = $start;
while(1) {
if ($dimtime =~ /T(.*):00/) {
$dimtime = $1;
}
if ($level != $start) {
$dimtime .= "+" . $offset;
}
$dimtime = get_rule_datetime($dimtime, $today, $sunrise, $sunset);
push @device, [$dimtime, $alias, $level] if (defined($time) && ($now <= $time));
if ($stop>=$start) {
$level += $step;
if ($level>$stop) {
last;
}
}
if ($stop<$start) {
$level -= $step;
if ($level<$stop) {
last;
}
}
}
}
sub check_device_rules($) {
my ($now) = @_;
printLine "Checking device rules '$now'" if ($option{verbose});
my $rule = $device[0];
while (defined(@$rule)) {
my ($time, $device, $value) = @$rule;
if ($time <= $now) {
shift(@device);
change_device_state($device, $value);
} else {
last;
}
$rule = $device[0];
}
}
sub change_device_state($$) {
my ($device, $state) = @_;
call_program($device, $state);
my $text = "Device $device = $state";
printLine $text if ($option{"verbose"});
printlogger $text;
}
######################################################################
sub get_device_state() {
get_info_from_program();
if (defined($option{"device"})) {
my $device = $option{"device"};
if (defined($cfg_idAlias{$device})) {
$device = $cfg_idAlias{$device};
}
if ($cfg_alias{$device}) {
my $state = $cfg_alias{$device};
printLine "$state" if (not $option{"verbose"});
printLine "Device $device = $state" if ($option{"verbose"});
return;
}
if (defined($cfg_idGroup{$device})) {
$device = $cfg_idGroup{$device};
}
if (exists($cfg_group{$device})) {
my $val = $cfg_group{$device};
my ($delay, $aliases) = split(/\s+/, $val, 2);
my $states = "";
my (@aliaslist) = split(/\s+/, $aliases);
foreach my $alias (@aliaslist) {
$states .= " $cfg_alias{$alias}";
}
printLine "Group $device =$states";
return;
}
printLine "No alias or group found with name/id '$device'!";
}
}
sub set_device_state() {
get_info_from_program();
if (defined($option{"device"})) {
if (defined($option{"state"})) {
my $state = $option{"state"};
if ($state !~ /^(on|off|\d+)$/i) {
printLine "No state found with name '$state'!";
return;
}
if (($state =~ /^(\d+)$/i) && (($state < 0) || ($state > 255))) {
printLine "State needs to be an integer between 0 and 255 for dimmers!";
return;
}
my $device = $option{"device"};
if (defined($cfg_idAlias{$device})) {
$device = $cfg_idAlias{$device};
}
if ($cfg_alias{$device}) {
change_device_state($device, $state);
return;
}
if (defined($cfg_idGroup{$device})) {
$device = $cfg_idGroup{$device};
}
if (exists($cfg_group{$device})) {
my $val = $cfg_group{$device};
my ($delay, $aliases) = split(/\s+/, $val, 2);
my (@aliaslist) = split(/\s+/, $aliases);
foreach my $alias (@aliaslist) {
change_device_state($alias, $state);
sleep(1);
}
return;
}
printLine "No alias or group found with name '$device'!";
}
}
}
sub swap_first_device_state() {
get_info_from_program();
if ($option{"device"}) {
if ($option{"swapfirst"}) {
my $device = $option{"device"};
if (defined($cfg_idAlias{$device})) {
$device = $cfg_idAlias{$device};
}
if ($cfg_alias{$device}) {
my $state = $cfg_alias{$device};
if ($state =~ /^off$/i) {
$state = 'on';
} else {
$state = 'off';
}
change_device_state($device, $state);
return;
}
if (defined($cfg_idGroup{$device})) {
$device = $cfg_idGroup{$device};
}
if (exists($cfg_group{$device})) {
my $val = $cfg_group{$device};
my ($delay, $aliases) = split(/\s+/, $val, 2);
my ($alias) = split(/\s+/, $aliases);
my $state = $cfg_alias{$device};
if ($state =~ /^off$/i) {
$state = 'on';
} else {
$state = 'off';
}
my (@aliaslist) = split(/\s+/, $aliases);
foreach my $alias (@aliaslist) {
change_device_state($alias, $state);
sleep(1);
}
return;
}
printLine "No alias or group found with name '$device'!";
}
}
}
sub swap_device_state() {
get_info_from_program();
if ($option{"device"}) {
if ($option{"swap"}) {
my $device = $option{"device"};
if (defined($cfg_idAlias{$device})) {
$device = $cfg_idAlias{$device};
}
if ($cfg_alias{$device}) {
my $state = $cfg_alias{$device};
if ($state =~ /^off$/i) {
$state = 'on';
} else {
$state = 'off';
}
change_device_state($device, $state);
return;
}
if (defined($cfg_idGroup{$device})) {
$device = $cfg_idGroup{$device};
}
if (exists($cfg_group{$device})) {
my $val = $cfg_group{$device};
my ($delay, $aliases) = split(/\s+/, $val, 2);
my (@aliaslist) = split(/\s+/, $aliases);
foreach my $alias (@aliaslist) {
my $state = $cfg_alias{$device};
if ($state =~ /^off$/i) {
$state = 'on';
} else {
$state = 'off';
}
change_device_state($alias, $state);
sleep(1);
}
return;
}
printLine "No alias or group found with name '$device'!";
}
}
}
sub list_all_devices() {
foreach my $key (sort keys %cfg_alias) {
printLine "Device $key($cfg_aliasId{$key}) = $cfg_alias{$key}";
}
foreach my $key (sort keys %cfg_group) {
my $val = $cfg_group{$key};
my ($delay, $aliases) = split(/\s+/, $val, 2);
my $states = "";
my (@aliaslist) = split(/\s+/, $aliases);
foreach my $device (@aliaslist) {
$states .= " $cfg_alias{$device}";
}
printLine "Group $key($cfg_groupId{$key}) =$states";
}
}
sub list_all_aliases {
foreach my $key (sort keys %cfg_alias) {
printLine "Alias $key($cfg_aliasId{$key}) = receiver";
}
foreach my $key (sort keys %cfg_group) {
my $val = $cfg_group{$key};
my ($delay, $aliases) = split(/\s+/, $val, 2);
printLine "Group $key($cfg_groupId{$key}) = delay $delay seconds, aliases ($aliases)";
}
}
sub daemon_loop() {
daemon_init();
while ($option{"daemon"}) {
my $now = get_datetime_now();
my $next_day = get_datetime_now();
$next_day->add( hours => 24 );
$next_day->set( hour => 0, minute => 0, second => 0 );
printLine "Next reload of device rules = $next_day" if ($option{"verbose"});
my $wait_time = 5;
my $loop = 1;
check_device_rules($now);
while ($loop) {
$now = get_datetime_now();
if ($now > $next_day) {
load_device_rules();
$next_day->add( hours => 24 );
}
check_device_rules($now);
sleep($wait_time);
}
}
daemon_exit();
}
sub perform_action() {
if ($option{"aliases"}) {
list_all_aliases();
} elsif ($option{"get"}) {
get_device_state();
} elsif ($option{"set"}) {
set_device_state();
} elsif ($option{"swapfirst"}) {
swap_first_device_state();
} elsif ($option{"swap"}) {
swap_device_state();
} elsif ($option{"list"}) {
list_all_devices();
} elsif ($option{"daemon"}) {
daemon_loop();
}
}
######################################################################
sub main() {
# check arguments
check_args(@ARGV);
# read configuration file
read_config($cfg_set{"configfile"});
# Load rules
load_device_rules();
# perform action
perform_action();
}
main();
######################################################################
__DATA__
#-
#- NAME
#- tellstickControllerTdtool
#-
#- SYNOPSIS
#- tellstickControllerTdtool [options]
#-
#- DESCRIPTION
#- tellstickController is used for controlling wireless recevier devices from
#- a TellStick transmitter. This version uses the tdtool software to perform
#- the actual device control. tellstickController can be used for manual
#- control of devices, or used as a daemon that is controlled by a number of
#- configuration file rules.
#- Devices and groups can be accessed by either name or id.
#- States can be set to ON, OFF or an integer between 0-255 for dimmer.
#-
#- -h, --help Show this help text.
#- -v, --verbose Show extra information.
#- -d, --daemon Starts in daemon mode.
#- -f, --file F Set configfile to file F.
#- -c, --check Check content of configuration file.
#- -t, --test Test mode, no real devices will used.
#- -a, --aliases List of aliases for devices/groups.
#- -l, --list List states for all devices/groups.
#- -s, --set D S Set device D to state S
#- -g, --get D Get state for device/group D.
#- -w, --swapfirst G Swap states for group G based on first device state.
#- -x, --swap D Swap state for device/group D.
#-
#- EXAMPLES
#- tellstickControllerTdtool -l
#- tellstickControllerTdtool --set device_alias on
#- tellstickControllerTdtool --swap device_alias
#- tellstickControllerTdtool -d -f myConfigFile.conf
#-
#- DEPENDENCIES
#- The following Perl modules needs to be installed:
#- DateTime and DateTime::Event::Sunrise
#-
#- FILES
#- tellstickController.conf
#- tellstickController.log
#- tellstickController.pid
#-
#- CONFIGURATION
#- The configuration file consists of a number of settings 'Set', a number of
#- group aliases 'Group', and a number of device rules 'Rules'.
#-
#- The settings controls sunrise/sunset, logfile, pidfile, etc.
#-
#- The groups configures a list of devices and a delay time.
#-
#- The rules can be written as a string containing two parts.
#- The first part is optional and ends with an '/'. It can contain one or more
#- of these keywords 'Weekend', 'Workweek', 'Monday', 'Tuesday', 'Wednesday',
#- 'Thursday', 'Friday', 'Saturday' and 'Sunday'. A specified date like
#- '2008-03-18', '2008-03-##', '####-03-##' or '####-##-15' is also allowed.
#- The second part is mandatory and is either a expression or the keyword 'No'.
#- The expression can contain a micture of these keywords 'Sunrise', 'Sunset',
#- 'Random(HH:MM)', 'HH:MM' and 'Dimmer(HH:MM,startlevel,stoplevel,steplevel)'.
#- A Dimmer can be controlled to change dim level from startlevel to stoplevel
#- by adding/subtracting steplevel value every HH:MM time period.
#-
#- Example rule: Weekend/07:15
#- Example rule: Monday+Sunday/07:15
#- Example rule: 2008-03-##/12:10
#- Example rule: 07:15+Random(02:00)
#- Example rule: Sunset-00:30
#- Example rule: Workweek/07:00+Dimmer(00:01,5,255,25)
#-
#- AUTHOR
#- Original version written by Rickard Andersson
#-
#- LICENSE
#- Copyright (C) 2008-2010 Rickard Andersson. Version 2.0.0
#- This program comes with ABSOLUTELY NO WARRANTY.
#- This is free software, and you are welcome to redistribute it under certain
#- conditions; See license file for details.
#-