+.config/yt-dlp.conf
[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);
82 $finishline=[$finishline->[1]] if $finishline;
83 my $silent=shift if ($ARGV[0]||"") eq "-s";
84 logmsg "command: ".join(" ",@ARGV) if !$silent&&@ARGV;
85 sub info($) {
86   my($s)=@_;
87   print $s if !$silent;
88 }
89 sub finish() {
90   info join("",@$finishline) if defined $finishline;
91   exit 0;
92 }
93 my $reset=readfile $resetfile if -e $resetfile;
94 my($resetminutes,$resetstate);
95 sub resetread() {
96   $reset=~/^0?(\d+)[:.]0?(\d+) ([01s]|reset)\n$/ or warn "Invalid $resetfile: $reset";
97   $resetminutes=$1*60+$2;
98   $resetstate=($3 eq "reset"?undef:$3);
99 }
100 sub unlink_resetfile() {
101   unlink $resetfile or die "$resetfile: $!";
102   logmsg "remove reset: $reset";
103   $reset=$resetminutes=undef;
104 }
105 if (defined $reset) {
106   resetread;
107   unlink_resetfile if $resetminutes==$minutes;
108 }
109 sub finishlinereset() {
110   $finishline=schedulenext $resetminutes;
111   if ($finishline) {
112     $finishline=[$reset,$finishline->[1]];
113   } else {
114     $finishline=[$reset];
115   }
116 }
117 finishlinereset if defined $reset;
118 my $state=readfile $statefile;
119 chomp $state;
120 info $state;
121 my $newstate=shift;
122 if (!defined $newstate&&defined $resetstate&&!defined $resetminutes) {
123   $newstate=$resetstate;
124   logmsg "reset: $newstate";
125 }
126 sub printminutes($) {
127   my($m)=@_;
128   return sprintf "%02d:%02d",int($m/60),$m%60;
129 }
130 if ($silent) {
131   if (!defined $newstate&&!defined $reset&&%schedule) {
132     my $prev=schedulenext($minutes);
133     if ($prev) {
134       my $prev=$prev->[0];
135       if ($prev=~m{ (.)\n$}) {
136         $newstate=$1;
137         logmsg "scheduled: $prev";
138       }
139     }
140   }
141   $newstate=$state if !defined $newstate;
142 }
143 if (!defined $newstate) {
144   info "\n";
145   finish;
146 }
147 die "state!={s|0|1}" if $newstate!~/^[s01]$/;
148 sub setstate() {
149   info "->$newstate";
150   writefile $statefile,"$newstate\n" if $state ne $newstate;
151   logmsg "$state->$newstate" if $state ne $newstate;
152   if ($newstate ne "s") {
153     my $pid=readfile "pidof -x dnf;true|";
154     die "\n".printminutes($minutes)." change $state".($state eq $newstate?"":"->$newstate")." refused: dnf running: $pid" if $pid;
155   }
156   my $both={"s"=>[0,0],"0"=>[1,0],"1"=>[1,1]}->{$newstate};
157   spawn "$usbrelay 1 ".$both->[0];
158   spawn "$usbrelay 2 ".$both->[1];
159   info "\n";
160 }
161 my $newreset=shift;
162 die "Excessive args: ".join(" ",@ARGV) if @ARGV;
163 if (!defined $newreset) {
164   setstate;
165   if (!$silent&&defined $reset) {
166     unlink_resetfile;
167     $finishline=schedulenext($minutes);
168     $finishline=[$finishline->[1]] if $finishline;
169   }
170   finish;
171 }
172 $reset=$newreset;
173 if ($reset=~/^\d+$/) {
174   $resetminutes=($reset+$minutes)%(24*60);
175   $reset=printminutes($resetminutes)." reset\n";
176   setstate;
177 } else {
178   $reset.=" reset\n";
179   resetread;
180   $reset=printminutes($resetminutes)." $newstate\n";
181   info "\n";
182 }
183 writefile $resetfile,$reset;
184 logmsg "new reset: $reset";
185 finishlinereset;
186 finish;