#! /usr/bin/perl # * * * * * nice -n20 /root/bin/heat -s use strict; use warnings; my $schedulefile="/root/heat.schedule"; my $statefile="/root/heat.state"; my $resetfile="/root/heat.reset"; my $usbrelay="/root/bin/usbrelay"; my $logfile="/var/log/heat.log"; require POSIX; $|=1; sub readfile($) { my($fname)=@_; local *F; if (!open F,$fname) { warn "$fname: $!"; return undef; } my $F=do { local $/; ; }; defined $F or die "read $fname: $!"; close F or die "read-close $fname: $!"; return $F; } sub writefile($$;$) { my($fname,$content,$mode)=@_; local *F; open F,($mode||">").$fname or die "$fname: $!"; print F $content or die "write $fname: $!"; close F or die "write-close $fname: $!"; } sub logmsg($) { my($s)=@_; chomp $s; writefile $logfile,POSIX::strftime("%Y-%m-%dT%H:%M:%S",localtime)." $s\n",">>"; } sub spawn($) { my($cmd)=@_; $cmd.=" >&2"; system $cmd and die "$cmd: $!"; } my($trash,$min,$hour)=localtime; my $minutes=$hour*60+$min; if (($ARGV[0]||"")=~/^[@]0?(\d+):0?(\d+)$/) { $minutes=$1*60+$2; shift; } my $finishline; my %schedule; my $schedule; if (-e $schedulefile) { $schedule=readfile $schedulefile; while ($schedule=~s/^(0?(\d+):0?(\d+)) ([01s])\n//) { my $tm=$2*60+$3; warn "$schedulefile set twice for: $1" if defined $schedule{$tm}; $schedule{$tm-24*60}="$1 $4\n"; $schedule{$tm }="$1 $4\n"; $schedule{$tm+24*60}="$1 $4\n"; } warn "$schedulefile garbage: $schedule" if $schedule ne ""; warn "Empty $schedulefile" if !%schedule; } sub schedulenext($) { return undef if !%schedule; my($now)=@_; my($bestprev,$prev,$bestnext,$next); for my $found (keys(%schedule)) { if ($found<=$now) { next if defined $bestprev&&$bestprev>$found; $bestprev=$found; $prev=$schedule{$found}; } else { next if defined $bestnext&&$bestnext<$found; $bestnext=$found; $next=$schedule{$found}; } } die "No bestprev" if !defined $bestprev; die "No bestnext" if !defined $bestnext; return [$prev,$next]; } $finishline=schedulenext($minutes); $finishline=[$finishline->[1]] if $finishline; my $silent=shift if ($ARGV[0]||"") eq "-s"; logmsg "command: ".join(" ",@ARGV) if !$silent&&@ARGV; sub info($) { my($s)=@_; print $s if !$silent; } sub finish() { info join("",@$finishline) if defined $finishline; exit 0; } my $reset=readfile $resetfile if -e $resetfile; my($resetminutes,$resetstate); sub resetread() { $reset=~/^0?(\d+)[:.]0?(\d+) ([01s]|reset)\n$/ or warn "Invalid $resetfile: $reset"; $resetminutes=$1*60+$2; $resetstate=($3 eq "reset"?undef:$3); } sub unlink_resetfile() { unlink $resetfile or die "$resetfile: $!"; logmsg "remove reset: $reset"; $reset=$resetminutes=undef; } if (defined $reset) { resetread; unlink_resetfile if $resetminutes==$minutes; } sub finishlinereset() { $finishline=schedulenext $resetminutes; if ($finishline) { $finishline=[$reset,$finishline->[1]]; } else { $finishline=[$reset]; } } finishlinereset if defined $reset; my $state=readfile $statefile; chomp $state; info $state; my $newstate=shift; if (!defined $newstate&&defined $resetstate&&!defined $resetminutes) { $newstate=$resetstate; logmsg "reset: $newstate"; } sub printminutes($) { my($m)=@_; return sprintf "%02d:%02d",int($m/60),$m%60; } if ($silent) { if (!defined $newstate&&!defined $reset&&%schedule) { my $prev=schedulenext($minutes); if ($prev) { my $prev=$prev->[0]; if ($prev=~m{ (.)\n$}) { $newstate=$1; logmsg "scheduled: $prev"; } } } $newstate=$state if !defined $newstate; } if (!defined $newstate) { info "\n"; finish; } die "state!={s|0|1}" if $newstate!~/^[s01]$/; sub setstate() { info "->$newstate"; writefile $statefile,"$newstate\n" if $state ne $newstate; logmsg "$state->$newstate" if $state ne $newstate; if ($newstate ne "s") { my $pid=readfile "pidof -x dnf;true|"; die "\n".printminutes($minutes)." change $state".($state eq $newstate?"":"->$newstate")." refused: dnf running: $pid" if $pid; } my $both={"s"=>[0,0],"0"=>[1,0],"1"=>[1,1]}->{$newstate}; spawn "$usbrelay 1 ".$both->[0]; spawn "$usbrelay 2 ".$both->[1]; info "\n"; } my $newreset=shift; die "Excessive args: ".join(" ",@ARGV) if @ARGV; if (!defined $newreset) { setstate; if (!$silent&&defined $reset) { unlink_resetfile; $finishline=schedulenext($minutes); $finishline=[$finishline->[1]] if $finishline; } finish; } $reset=$newreset; if ($reset=~/^\d+$/) { $resetminutes=($reset+$minutes)%(24*60); $reset=printminutes($resetminutes)." reset\n"; setstate; } else { $reset.=" reset\n"; resetread; $reset=printminutes($resetminutes)." $newstate\n"; info "\n"; } writefile $resetfile,$reset; logmsg "new reset: $reset"; finishlinereset; finish;