bab4730557e73e0b9642ac1ee9d7bf3715c2860a
[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 and die "$cmd: $!";
40 }
41 my($trash,$min,$hour)=localtime;
42 my $minutes=$hour*60+$min;
43 if (($ARGV[0]||"")=~/^[@]0?(\d+):0?(\d+)$/) {
44   $minutes=$1*60+$2;
45   shift;
46 }
47 my $finishline;
48 my %schedule;
49 my $schedule;
50 if (-e $schedulefile) {
51   $schedule=readfile $schedulefile;
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   warn "Empty $schedulefile" if !%schedule;
61 }
62 sub schedulenext($) {
63   return undef if !%schedule;
64   my($now)=@_;
65   my($bestprev,$prev,$bestnext,$next);
66   for my $found (keys(%schedule)) {
67     if ($found<=$now) {
68       next if defined $bestprev&&$bestprev>$found;
69       $bestprev=$found;
70       $prev=$schedule{$found};
71     } else {
72       next if defined $bestnext&&$bestnext<$found;
73       $bestnext=$found;
74       $next=$schedule{$found};
75     }
76   }
77   die "No bestprev" if !defined $bestprev;
78   die "No bestnext" if !defined $bestnext;
79   return [$prev,$next];
80 }
81 $finishline=[schedulenext($minutes)->[1]];
82 my $silent=shift if ($ARGV[0]||"") eq "-s";
83 logmsg "command: ".join(" ",@ARGV) if !$silent&&@ARGV;
84 sub info($) {
85   my($s)=@_;
86   print $s if !$silent;
87 }
88 sub finish() {
89   info join("",@$finishline) if defined $finishline;
90   exit 0;
91 }
92 my $reset=readfile $resetfile if -e $resetfile;
93 my($resetminutes,$resetstate);
94 sub resetread() {
95   $reset=~/^0?(\d+)[:.]0?(\d+) ([01s]|reset)\n$/ or warn "Invalid $resetfile: $reset";
96   $resetminutes=$1*60+$2;
97   $resetstate=($3 eq "reset"?undef:$3);
98 }
99 sub unlink_resetfile() {
100   unlink $resetfile or die "$resetfile: $!";
101   logmsg "remove reset: $reset";
102   $reset=$resetminutes=undef;
103 }
104 if (defined $reset) {
105   resetread;
106   unlink_resetfile if $resetminutes==$minutes;
107 }
108 sub finishlinereset() {
109   $finishline=schedulenext $resetminutes;
110   if ($finishline) {
111     $finishline=[$reset,$finishline->[1]];
112   } else {
113     $finishline=[$reset];
114   }
115 }
116 finishlinereset if defined $reset;
117 my $state=readfile $statefile;
118 chomp $state;
119 info $state;
120 my $newstate=shift;
121 if (!defined $newstate&&defined $resetstate&&!defined $resetminutes) {
122   $newstate=$resetstate;
123   logmsg "reset: $newstate";
124 }
125 sub printminutes($) {
126   my($m)=@_;
127   return sprintf "%02d:%02d",int($m/60),$m%60;
128 }
129 if ($silent) {
130   if (!defined $newstate&&!defined $reset&&%schedule) {
131     my $prev=schedulenext($minutes)->[0];
132     if ($prev=~m{^@{[ printminutes $minutes ]} (.)\n$}) {
133       $newstate=$1;
134       logmsg "scheduled: $prev";
135     }
136   }
137   $newstate=$state if !defined $newstate;
138 }
139 if (!defined $newstate) {
140   info "\n";
141   finish;
142 }
143 die "state!={s|0|1}" if $newstate!~/^[s01]$/;
144 sub setstate() {
145   info "->$newstate";
146   writefile $statefile,"$newstate\n" if $state ne $newstate;
147   logmsg "$state->$newstate" if $state ne $newstate;
148   if ($newstate ne "s") {
149     my $pid=readfile "pidof -x dnf;true|";
150     die "\nchange refused: dnf running: $pid" if $pid;
151   }
152   my $both={"s"=>[0,0],"0"=>[1,0],"1"=>[1,1]}->{$newstate};
153   spawn "$usbrelay 1 ".$both->[0];
154   spawn "$usbrelay 2 ".$both->[1];
155   info "\n";
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)->[1]];
164   }
165   finish;
166 }
167 $reset=$newreset;
168 if ($reset=~/^\d+$/) {
169   $resetminutes=($reset+$minutes)%(24*60);
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;