9227df5471b25a99109b08d6522e95f2bf79ef77
[nethome.git] / bin / heat
1 #! /usr/bin/perl
2 # * * * * * nice -n20 /root/bin/heat -s
3 use strict;
4 use warnings;
5 my $schedulefile="/root/heat.schedule";
6 my $statefile="/root/heat.state";
7 my $resetfile="/root/heat.reset";
8 my $usbrelay="/root/bin/usbrelay";
9 my $logfile="/var/log/heat.log";
10 require POSIX;
11 $|=1;
12 sub readfile($) {
13   my($fname)=@_;
14   local *F;
15   if (!open F,$fname) {
16     warn "$fname: $!";
17     return undef;
18   }
19   my $F=do { local $/; <F>; };
20   defined $F or die "read $fname: $!";
21   close F or die "read-close $fname: $!";
22   return $F;
23 }
24 sub writefile($$;$) {
25   my($fname,$content,$mode)=@_;
26   local *F;
27   open F,($mode||">").$fname or die "$fname: $!";
28   print F $content or die "write $fname: $!";
29   close F or die "write-close $fname: $!";
30 }
31 sub logmsg($) {
32   my($s)=@_;
33   chomp $s;
34   writefile $logfile,POSIX::strftime("%Y-%m-%dT%H:%M:%S",localtime)." $s\n",">>";
35 }
36 sub spawn($) {
37   my($cmd)=@_;
38   $cmd.=" >&2";
39   system $cmd or return 1;
40   warn "$cmd: $!";
41   return 0;
42 }
43 my($trash,$min,$hour)=localtime;
44 my $minutes=$hour*60+$min;
45 if (($ARGV[0]||"")=~/^[@]0?(\d+):0?(\d+)$/) {
46   $minutes=$1*60+$2;
47   shift;
48 }
49 my $finishline;
50 my %schedule;
51 my $schedule;
52 if (-e $schedulefile) {
53   $schedule=readfile $schedulefile;
54   while ($schedule=~s/^(0?(\d+):0?(\d+)) ([01s])\n//) {
55     my $tm=$2*60+$3;
56     warn "$schedulefile set twice for: $1" if defined $schedule{$tm};
57     $schedule{$tm-24*60}="$1 $4\n";
58     $schedule{$tm      }="$1 $4\n";
59     $schedule{$tm+24*60}="$1 $4\n";
60   }
61   warn "$schedulefile garbage: $schedule" if $schedule ne "";
62   warn "Empty $schedulefile" if !%schedule;
63 }
64 sub schedulenext($) {
65   return undef if !%schedule;
66   my($now)=@_;
67   my($bestprev,$prev,$bestnext,$next);
68   for my $found (keys(%schedule)) {
69     if ($found<=$now) {
70       next if defined $bestprev&&$bestprev>$found;
71       $bestprev=$found;
72       $prev=$schedule{$found};
73     } else {
74       next if defined $bestnext&&$bestnext<$found;
75       $bestnext=$found;
76       $next=$schedule{$found};
77     }
78   }
79   die "No bestprev" if !defined $bestprev;
80   die "No bestnext" if !defined $bestnext;
81   return [$prev,$next];
82 }
83 $finishline=schedulenext $minutes;
84 my $silent=shift if ($ARGV[0]||"") eq "-s";
85 logmsg "command: ".join(" ",@ARGV) if !$silent&&@ARGV;
86 sub info($) {
87   my($s)=@_;
88   print $s if !$silent;
89 }
90 sub finish() {
91   info join("",@$finishline) if defined $finishline;
92   exit 0;
93 }
94 my $reset=readfile $resetfile if -e $resetfile;
95 my($resetminutes,$resetstate);
96 sub resetread() {
97   $reset=~/^0?(\d+):0?(\d+) ([01s]|reset)\n$/ or warn "Invalid $resetfile: $reset";
98   $resetminutes=$1*60+$2;
99   $resetstate=($3 eq "reset"?undef:$3);
100 }
101 sub unlink_resetfile() {
102   unlink $resetfile or die "$resetfile: $!";
103   logmsg "remove reset: $reset";
104   $reset=$resetminutes=undef;
105 }
106 if (defined $reset) {
107   resetread;
108   unlink_resetfile if $resetminutes==$minutes;
109 }
110 sub finishlinereset() {
111   $finishline=schedulenext $resetminutes;
112   if ($finishline) {
113     splice @$finishline,1,0,$reset;
114   } else {
115     $finishline=[$reset];
116   }
117 }
118 finishlinereset if defined $reset;
119 my $state=readfile $statefile;
120 chomp $state;
121 info $state;
122 my $newstate=shift;
123 if (!defined $newstate&&defined $resetstate&&!defined $resetminutes) {
124   $newstate=$resetstate;
125   logmsg "reset: $newstate";
126 }
127 sub printminutes($) {
128   my($m)=@_;
129   return sprintf "%02d:%02d",int($m/60),$m%60;
130 }
131 if ($silent) {
132   if (!defined $newstate&&!defined $reset&&%schedule) {
133     my $prev=schedulenext($minutes)->[0];
134     if ($prev=~m{^@[ printminutes $minutes ] (.)\n$}) {
135       $newstate=$1;
136       logmsg "scheduled: $prev";
137     }
138   }
139   $newstate=$state if !defined $newstate;
140 }
141 if (!defined $newstate) {
142   info "\n";
143   finish;
144 }
145 die "state!={s|0|1}" if $newstate!~/^[s01]$/;
146 sub setstate() {
147   info "->$newstate";
148   my $both={"s"=>[0,0],"0"=>[1,0],"1"=>[1,1]}->{$newstate};
149   $state eq $newstate or unlink $statefile or warn "$statefile: $!";
150   (    spawn "$usbrelay 1 ".$both->[0]
151    and spawn "$usbrelay 2 ".$both->[1])
152   or do { unlink $statefile; die "usbrelay error"; };
153   $state eq $newstate or writefile $statefile,"$newstate\n";
154   info "\n";
155   logmsg "$state->$newstate" if $state ne $newstate;
156 }
157 my $newreset=shift;
158 die "Excessive args: ".join(" ",@ARGV) if @ARGV;
159 if (!defined $newreset) {
160   setstate;
161   if (!$silent&&defined $reset) {
162     unlink_resetfile;
163     $finishline=schedulenext $minutes;
164   }
165   finish;
166 }
167 $reset=$newreset;
168 if ($reset=~/^\d+$/) {
169   $resetminutes=$reset+$minutes;
170   $reset=printminutes($resetminutes)." reset\n";
171   setstate;
172 } else {
173   $reset.=" reset\n";
174   resetread;
175   $reset=printminutes($resetminutes)." $newstate\n";
176   info "\n";
177 }
178 writefile $resetfile,$reset;
179 logmsg "new reset: $reset";
180 finishlinereset;
181 finish;