bin/heat: +log
[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 $schedule=readfile $schedulefile;
50 my $finishline;
51 my %schedule;
52 while ($schedule=~s/^(0?(\d+):0?(\d+)) ([01s])\n//) {
53   my $tm=$2*60+$3;
54   warn "$schedulefile set twice for: $1" if defined $schedule{$tm};
55   $schedule{$tm-24*60}="$1 $4\n";
56   $schedule{$tm      }="$1 $4\n";
57   $schedule{$tm+24*60}="$1 $4\n";
58 }
59 warn "$schedulefile garbage: $schedule" if $schedule ne "";
60 sub schedulenext($) {
61   my($now)=@_;
62   my($bestprev,$prev,$bestnext,$next);
63   for my $found (keys(%schedule)) {
64     if ($found<=$now) {
65       next if defined $bestprev&&$bestprev>$found;
66       $bestprev=$found;
67       $prev=$schedule{$found};
68     } else {
69       next if defined $bestnext&&$bestnext<$found;
70       $bestnext=$found;
71       $next=$schedule{$found};
72     }
73   }
74   die "No bestprev" if !defined $bestprev;
75   die "No bestnext" if !defined $bestnext;
76   return [$prev,$next];
77 }
78 $finishline=schedulenext $minutes;
79 my $silent=shift if ($ARGV[0]||"") eq "-s";
80 logmsg "command: ".join(" ",@ARGV) if !$silent&&@ARGV;
81 sub info($) {
82   my($s)=@_;
83   print $s if !$silent;
84 }
85 sub finish() {
86   info join("",@$finishline) if defined $finishline;
87   exit 0;
88 }
89 my $reset=readfile $resetfile if -e $resetfile;
90 my($resetminutes,$resetstate);
91 sub resetread() {
92   $reset=~/^0?(\d+):0?(\d+) ([01s]|reset)\n$/ or warn "Invalid $resetfile: $reset";
93   $resetminutes=$1*60+$2;
94   $resetstate=($3 eq "reset"?undef:$3);
95 }
96 sub unlink_resetfile() {
97   unlink $resetfile or die "$resetfile: $!";
98   logmsg "remove reset: $reset";
99   $reset=$resetminutes=undef;
100 }
101 if (defined $reset) {
102   resetread;
103   unlink_resetfile if $resetminutes==$minutes;
104 }
105 sub finishlinereset() {
106   $finishline=schedulenext $resetminutes;
107   splice @$finishline,1,0,$reset;
108 }
109 finishlinereset if defined $reset;
110 my $state=readfile $statefile;
111 chomp $state;
112 info $state;
113 my $newstate=shift;
114 if (!defined $newstate&&defined $resetstate&&!defined $resetminutes) {
115   $newstate=$resetstate;
116   logmsg "reset: $newstate";
117 }
118 sub printminutes($) {
119   my($m)=@_;
120   return sprintf "%02d:%02d",int($m/60),$m%60;
121 }
122 if ($silent) {
123   if (!defined $newstate&&!defined $reset) {
124     my $prev=schedulenext($minutes)->[0];
125     if ($prev=~m{^@[ printminutes $minutes ] (.)\n$}) {
126       $newstate=$1;
127       logmsg "scheduled: $prev";
128     }
129   }
130   $newstate=$state if !defined $newstate;
131 }
132 if (!defined $newstate) {
133   info "\n";
134   finish;
135 }
136 die "state!={s|0|1}" if $newstate!~/^[s01]$/;
137 sub setstate() {
138   info "->$newstate";
139   my $both={"s"=>[0,0],"0"=>[1,0],"1"=>[1,1]}->{$newstate};
140   $state eq $newstate or unlink $statefile or warn "$statefile: $!";
141   (    spawn "$usbrelay 1 ".$both->[0]
142    and spawn "$usbrelay 2 ".$both->[1])
143   or do { unlink $statefile; die "usbrelay error"; };
144   $state eq $newstate or writefile $statefile,"$newstate\n";
145   info "\n";
146   logmsg "$state->$newstate" if $state ne $newstate;
147 }
148 my $newreset=shift;
149 die "Excessive args: ".join(" ",@ARGV) if @ARGV;
150 if (!defined $newreset) {
151   setstate;
152   if (!$silent&&defined $reset) {
153     unlink_resetfile;
154     $finishline=schedulenext $minutes;
155   }
156   finish;
157 }
158 $reset=$newreset;
159 if ($reset=~/^\d+$/) {
160   $resetminutes=$reset+$minutes;
161   $reset=printminutes($resetminutes)." reset\n";
162   setstate;
163 } else {
164   $reset.=" reset\n";
165   resetread;
166   $reset=printminutes($resetminutes)." $newstate\n";
167   info "\n";
168 }
169 writefile $resetfile,$reset;
170 logmsg "new reset: $reset";
171 finishlinereset;
172 finish;