#! /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 or return 1; warn "$cmd: $!"; return 0; } 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; 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) { splice @$finishline,1,0,$reset; } 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)->[0]; if ($prev=~m{^@{[ printminutes $minutes ]} (.)\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"; my $both={"s"=>[0,0],"0"=>[1,0],"1"=>[1,1]}->{$newstate}; $state eq $newstate or unlink $statefile or warn "$statefile: $!"; ( spawn "$usbrelay 1 ".$both->[0] and spawn "$usbrelay 2 ".$both->[1]) or do { unlink $statefile; die "usbrelay error"; }; $state eq $newstate or writefile $statefile,"$newstate\n"; info "\n"; logmsg "$state->$newstate" if $state ne $newstate; } my $newreset=shift; die "Excessive args: ".join(" ",@ARGV) if @ARGV; if (!defined $newreset) { setstate; if (!$silent&&defined $reset) { unlink_resetfile; $finishline=schedulenext $minutes; } finish; } $reset=$newreset; if ($reset=~/^\d+$/) { $resetminutes=$reset+$minutes; $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;