1579 lines
41 KiB
Bash
Executable File
1579 lines
41 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# TODO: comparison to previous reading
|
|
# TODO: gnuplot of given time range
|
|
# TODO: ditch pymyair
|
|
# TODO: manual controls
|
|
|
|
|
|
DEFAULT_KILLFILE="${HOME}/.aircon_noaction"
|
|
DEFAULT_CONFIGFILE=${HOME}/.airconrc
|
|
DEFAULT_CSVFILE=${HOME}/acstats.csv
|
|
DEFAULT_AIRCON_IP=10.99.99.1
|
|
DEFAULTLIMIT=3
|
|
DEFAULT_TOLERANCE=0
|
|
VALID_MODES=" cool heat "
|
|
VALID_ZONE_STATES=" open close "
|
|
|
|
BOLD="\033[1m"
|
|
PLAIN="\033[0m"
|
|
UNDERLINE="\033[4m"
|
|
GREY="\033[1;30m"
|
|
RED="\033[31m"
|
|
GREEN="\033[32m"
|
|
YELLOW="\033[33m"
|
|
BLUE="\033[34m"
|
|
CYAN="\033[36m"
|
|
WHITE="\033[37m"
|
|
LINK="$BLUE$UNDERLINE"
|
|
|
|
|
|
function enable_cronmode() {
|
|
cronmode=1
|
|
BOLD=""
|
|
PLAIN=""
|
|
UNDERLINE=""
|
|
GREY=""
|
|
RED=""
|
|
GREEN=""
|
|
YELLOW=""
|
|
BLUE=""
|
|
CYAN=""
|
|
WHITE=""
|
|
LINK="$BLUE$UNDERLINE"
|
|
}
|
|
|
|
function usage() {
|
|
echo "usage: $0 [options]"
|
|
echo
|
|
echo " Modifies aircon based on configured parameters in $DEFAULT_CONFIGFILE."
|
|
echo
|
|
echo " -h Show this text."
|
|
echo " -c Cron mode. Only show output if actions were taken."
|
|
echo " -i x.x.x.x Specify IP address for aircon (default is $DEFAULT_AIRCON_IP)"
|
|
echo " -I db Log all zone temperatures to given influxdb database, then exit (see -o)."
|
|
echo " -A db Log actions to given influxdb database."
|
|
echo " -f file Specify an alternate config file."
|
|
echo " -k file If file exists, never change aircon settings (default: $DEFAULT_KILLFILE)."
|
|
echo " -l num Set number of too hot/cold zones at which to taking action."
|
|
echo " -L Log all zone temperatures to CSV file, then exit (see -o)."
|
|
echo " -o file Specify CSV output file. Default: $DEFAULT_CSVFILE"
|
|
echo " -m Generate a config file based on current aircon setup."
|
|
echo " -p Profiler mode."
|
|
echo " -w List which zone owners' devices are available then exit."
|
|
echo " -t num Specify degrees below min temperature before taking action."
|
|
echo " -T num Specify degrees above max temperature before taking action."
|
|
echo " -y Actually run commands/db inserts. By default, commands are just displayed."
|
|
echo
|
|
}
|
|
|
|
function action() {
|
|
echo -e "$BOLD$GREEN>> Running action: $PLAIN$GREEN$*$PLAIN"
|
|
}
|
|
|
|
function error() {
|
|
echo -e "$BOLD${RED}ERROR: $PLAIN$RED$*$PLAIN" >/dev/stderr
|
|
}
|
|
|
|
function info() {
|
|
echo -e "$BOLD${CYAN}>> $PLAIN$CYAN$*$PLAIN"
|
|
}
|
|
|
|
# get_midrange zone_idx
|
|
function get_midrange() { # outputs midpoint of temperature range for given zone
|
|
local min max
|
|
min=${zwantmin[$idx]}
|
|
max=${zwantmax[$idx]}
|
|
echo "($min + $max) / 2" | bc
|
|
}
|
|
|
|
function getoppositestate() {
|
|
if [[ $1 == "open" ]]; then
|
|
echo "close"
|
|
elif [[ $1 == "close" ]]; then
|
|
echo "open"
|
|
fi
|
|
}
|
|
|
|
function getzoneaction() { # populates zproblem[] and zaction[]
|
|
local idx problem num thisaction priority pingok fv
|
|
local donearby
|
|
idx=$1
|
|
|
|
profile "getzoneaction for idx ${zname[$idx]}"
|
|
thisaction="n/a"
|
|
if [[ ${zignore[$idx]} -eq 1 ]]; then
|
|
problem="n/a"
|
|
else
|
|
if [[ -z ${zowner[$idx]} ]]; then
|
|
pingok=1
|
|
elif canping "${zowner[$idx]}"; then
|
|
pingok=1
|
|
else
|
|
pingok=0
|
|
fi
|
|
fv=$(getforcevent ${zname[$idx]})
|
|
|
|
donearby=0
|
|
|
|
if [[ ! -z $fv && ${zstate[$idx]} == $(getoppositestate ${fv}) ]]; then
|
|
problem="force_${fv}"
|
|
priority=99
|
|
elif [[ $airconmode != "off" && ${zstate[$idx]} != "close" && $pingok -eq 0 ]]; then
|
|
problem="owner_not_home"
|
|
priority=99
|
|
elif [[ $pingok -eq 1 && $(echo "${ztemp[$idx]} > (${zwantmax[$idx]} + $tolerance_h)" | bc) -eq 1 ]]; then
|
|
problem="too_hot"
|
|
priority=$(echo "${ztemp[$idx]} - ${zwantmax[$idx]}" | bc)
|
|
if [[ $(echo "${ztemp[$idx]} > (${zwantmax[$idx]} + ($tolerance_h * 2))" | bc) -eq 1 ]]; then
|
|
donearby=1
|
|
fi
|
|
elif [[ $pingok -eq 1 && $(echo "${ztemp[$idx]} < (${zwantmin[$idx]} - $tolerance_l)" | bc) -eq 1 ]]; then
|
|
problem="too_cold"
|
|
priority=$(echo "${zwantmin[$idx]} - ${ztemp[$idx]}" | bc)
|
|
if [[ $(echo "${ztemp[$idx]} < (${zwantmin[$idx]} - ($tolerance_l * 2))" | bc) -eq 1 ]]; then
|
|
donearby=1
|
|
fi
|
|
else
|
|
problem="n/a"
|
|
priority=0
|
|
fi
|
|
#info "${zname[$idx]} priority is $priority"
|
|
zproblem[$idx]="$problem"
|
|
zpri[$idx]="$priority"
|
|
|
|
if [[ $problem == "owner_not_home" ]]; then
|
|
# turn the zone off
|
|
if [[ ${zstate[$idx]} != "close" ]]; then
|
|
thisaction="$thisaction close_vent"
|
|
fi
|
|
elif [[ $problem =~ force_ ]]; then
|
|
if [[ ${zstate[$idx]} != $fv ]]; then
|
|
thisaction="$thisaction ${fv}_vent"
|
|
fi
|
|
elif [[ $problem == "too_cold" ]]; then
|
|
if [[ $airconmode == "heat" || $airconmode == "off" ]]; then
|
|
# turn the system on, if required
|
|
if [[ $airconmode == "off" ]]; then
|
|
if [[ $modelock == "n/a" || $modelock == "heat" ]]; then
|
|
thisaction="$thisaction power_on set_mode:heat"
|
|
fi
|
|
fi
|
|
|
|
# if we couldnt turn the system on, there's nothing else to do
|
|
if [[ $airconmode == "heat" ]]; then
|
|
# adjust set temperature, if required
|
|
if [[ $(echo "${zsettemp[$idx]} > ${zwantmax[$idx]}" | bc) -eq 1 || $(echo "${zsettemp[$idx]} < ${zwantmin[$idx]}" | bc) -eq 1 ]]; then
|
|
# set temperature to midpoint of acceptable range
|
|
num=$(get_midrange $idx)
|
|
if [[ $zsettemp != $num ]]; then
|
|
thisaction="$thisaction set_temp:$num"
|
|
fi
|
|
fi
|
|
# open the vent, if required
|
|
if [[ ${zstate[$idx]} != "open" ]]; then
|
|
thisaction="$thisaction open_vent"
|
|
fi
|
|
# set myzone, even if not required (will fix later)
|
|
thisaction="$thisaction set_myzone"
|
|
fi
|
|
elif [[ $airconmode == "cool" ]]; then
|
|
# set temperature to midpoint of acceptable range
|
|
num=$(get_midrange $idx)
|
|
if [[ $zsettemp != $num ]]; then
|
|
thisaction="$thisaction set_temp:$num"
|
|
fi
|
|
# close the vent, if required
|
|
if [[ ${zstate[$idx]} != "close" ]]; then
|
|
thisaction="$thisaction close_vent"
|
|
elif [[ $donearby -eq 1 ]]; then
|
|
thisaction="close_nearby_zone"
|
|
fi
|
|
fi
|
|
elif [[ $problem == "too_hot" ]]; then
|
|
if [[ $airconmode == "cool" || $airconmode == "off" ]]; then
|
|
# turn the system on, if required
|
|
if [[ $airconmode == "off" ]]; then
|
|
if [[ $modelock == "n/a" || $modelock == "cool" ]]; then
|
|
thisaction="$thisaction power_on set_mode:cool"
|
|
fi
|
|
fi
|
|
|
|
# if we couldnt turn the system on, there's nothing else to do
|
|
if [[ $airconmode == "cool" ]]; then
|
|
# adjust set temperature, if required
|
|
if [[ $(echo "${zsettemp[$idx]} > ${zwantmax[$idx]}" | bc) -eq 1 || $(echo "${zsettemp[$idx]} < ${zwantmin[$idx]}" | bc) -eq 1 ]]; then
|
|
# set temperature to midpoint of acceptable range
|
|
num=$(get_midrange $idx)
|
|
if [[ $zsettemp != $num ]]; then
|
|
thisaction="$thisaction set_temp:$num"
|
|
fi
|
|
fi
|
|
# open the vent, if required
|
|
if [[ ${zstate[$idx]} != "open" ]]; then
|
|
thisaction="$thisaction open_vent"
|
|
fi
|
|
# set myzone, even if not required (will fix later)
|
|
thisaction="$thisaction set_myzone"
|
|
fi
|
|
elif [[ $airconmode == "heat" ]]; then
|
|
# set temperature to midpoint of acceptable range
|
|
num=$(get_midrange $idx)
|
|
if [[ $zsettemp != $num ]]; then
|
|
thisaction="$thisaction set_temp:$num"
|
|
fi
|
|
# close the vent, if required
|
|
if [[ ${zstate[$idx]} != "close" ]]; then
|
|
thisaction="$thisaction close_vent"
|
|
elif [[ $donearby -eq 1 ]]; then
|
|
thisaction="close_nearby_zone"
|
|
fi
|
|
fi
|
|
fi
|
|
fi
|
|
zaction[$idx]="$thisaction"
|
|
profile "getzoneaction for idx ${zname[$idx]}"
|
|
}
|
|
|
|
function getidxfromname() { # name
|
|
local x
|
|
for x in ${!zname[@]}; do
|
|
if [[ ${zname[$x]} == $1 ]]; then
|
|
echo "${x}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
function getnamefromid() { # id
|
|
local x
|
|
for x in ${!zname[@]}; do
|
|
if [[ ${zid[$x]} == $1 ]]; then
|
|
echo "${zname[$x]}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
# getforcevent zone_name
|
|
function getforcevent() {
|
|
local x retval
|
|
retval=""
|
|
for x in ${!fv_zonename[@]}; do
|
|
if [[ ${fv_zonename[$x]} == "$1" ]]; then
|
|
retval="${fv_state[$x]}"
|
|
break
|
|
fi
|
|
done
|
|
echo "$retval"
|
|
}
|
|
|
|
# getalladj zone_name
|
|
function getalladj() {
|
|
local x retval
|
|
retval=""
|
|
for x in ${!adj_zonename[@]}; do
|
|
if [[ ${adj_zonename[$x]} == "$1" ]]; then
|
|
retval="${adj_nearby[$x]}"
|
|
break
|
|
fi
|
|
done
|
|
echo "$retval"
|
|
}
|
|
|
|
function find_nearby_zones() { # zone_idx filter1 filter2 ... filter is: open|closed|noaction
|
|
local idx thiszone nearbyzones allnearbyzones thisone zi ok db conditions
|
|
idx=$1
|
|
shift
|
|
conditions="$*"
|
|
db=0
|
|
if [[ $db -eq 1 ]]; then
|
|
echo "find_nearby_zones(): idx = $idx" >/dev/stderr
|
|
echo "find_nearby_zones(): conditions = [$conditions]" >/dev/stderr
|
|
fi
|
|
thiszone=${zname[$idx]}
|
|
allnearbyzones=$(getalladj ${thiszone})
|
|
nearbyzones=""
|
|
if [[ $db -eq 1 ]]; then
|
|
echo "find_nearby_zones(): allnearbyzones = [$allnearbyzones]" >/dev/stderr
|
|
fi
|
|
for thisone in $allnearbyzones; do
|
|
ok=1
|
|
zi=$(getidxfromname $thisone)
|
|
if [[ $conditions == *open* && ${zstate[$zi]} != "open" ]]; then
|
|
ok=0
|
|
fi
|
|
if [[ $conditions == *closed* && ${zstate[$zi]} != "close" ]]; then
|
|
ok=0
|
|
fi
|
|
if [[ $conditions == *noaction* && ${zaction[$zi]} != "n/a" ]]; then
|
|
ok=0
|
|
fi
|
|
if [[ $ok -eq 1 ]]; then
|
|
nearbyzones="$nearbyzones $thisone"
|
|
fi
|
|
done
|
|
echo "$nearbyzones"
|
|
}
|
|
|
|
|
|
function generate_actions() { # populates global: nairconcommands & airconcmd[]
|
|
local x y z adj tempstr nhot ncold
|
|
local powerchange nmyzones maxpri bestidx
|
|
local globaction mzmustmove poss nomyzone
|
|
local db
|
|
db=0
|
|
|
|
nairconcommands=0
|
|
powerchange=""
|
|
|
|
for x in ${!zname[@]}; do
|
|
getzoneaction $x
|
|
done
|
|
|
|
# pass 1
|
|
profile "pass 1 start"
|
|
for x in ${!zname[@]}; do
|
|
thisaction="${zaction[$x]}"
|
|
IFS=' ' read -ra tok <<< "${zaction[$x]}"
|
|
for y in ${!tok[@]}; do
|
|
if [[ ${tok[$y]} == "close_nearby_zone" ]]; then
|
|
# is there a nearby zone that's open?
|
|
adj=$(find_nearby_zones $x open noaction)
|
|
if [[ -z $adj ]]; then
|
|
zaction[$x]="power_off"
|
|
if [[ -z $powerchange ]]; then
|
|
powerchange="power_off"
|
|
elif [[ ${zaction[$x]} != $powerchange ]]; then
|
|
zactionfail[$x]=1
|
|
fi
|
|
else
|
|
tempstr=""
|
|
for z in $adj; do
|
|
tempstr="$tempstr close:$z"
|
|
done
|
|
zaction[$x]="${zaction[$x]/close_nearby_zone/$tempstr}"
|
|
fi
|
|
fi
|
|
done
|
|
done
|
|
|
|
# pass 2 - If multiple zones are too hot/cold, turn the whole system off/on (if off, replace all actions)
|
|
nhot=0
|
|
ncold=0
|
|
for x in ${!zname[@]}; do
|
|
if [[ -z $powerchange ]]; then
|
|
if [[ ${zproblem[$x]} == "too_hot" ]]; then
|
|
nhot=$((nhot + 1))
|
|
elif [[ ${zproblem[$x]} == "too_cold" ]]; then
|
|
ncold=$((ncold + 1))
|
|
fi
|
|
fi
|
|
done
|
|
|
|
globsetmode=""
|
|
globprob=""
|
|
if [[ $airconmode == "off" ]]; then
|
|
# aircon is off
|
|
if [[ $ncold -ge $limit ]]; then
|
|
globprob="too_cold"
|
|
if [[ $modelock == "n/a" || $modelock == "heat" ]]; then
|
|
globsetmode=" set_mode:heat"
|
|
fi
|
|
elif [[ $nhot -ge $limit ]]; then
|
|
globprob="too_hot"
|
|
if [[ $modelock == "n/a" || $modelock == "cool" ]]; then
|
|
globsetmode=" set_mode:cool"
|
|
fi
|
|
fi
|
|
[[ ! -z $globsetmode ]] && globaction="power_on"
|
|
else
|
|
if [[ $airconmode == "heat" && $nhot -ge $limit ]]; then
|
|
globprob="too_hot"
|
|
elif [[ $airconmode == "cool" && $ncold -ge $limit ]]; then
|
|
globprob="too_cold"
|
|
fi
|
|
[[ ! -z $globprob ]] && globaction="power_off"
|
|
fi
|
|
if [[ ! -z $globprob ]]; then
|
|
for x in ${!zname[@]}; do
|
|
if [[ ${zproblem[$x]} == $globprob && ${zaction[$x]} != *${globaction}* ]]; then
|
|
zaction[$x]="$globaction$globsetmode ${zaction[$x]}"
|
|
zactionfail[$x]=0
|
|
elif [[ ${globaction} == "power_off" && ${zaction[$x]} != *$globaction* ]]; then
|
|
zactionfail[$x]=1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# pass xx - if we're closing the constant zone, and it's NOT the myzone,
|
|
# just turn the whole system off (since it will probably be opened.
|
|
# again anyway).
|
|
if [[ -z $powerchange && $airconmode != "off" ]]; then
|
|
for x in ${!zname[@]}; do
|
|
if [[ ${zaction[$x]} == *close_vent* && $constant == ${zname[$x]} ]]; then
|
|
if [[ $airconmyzoneid != ${zid[$x]} ]]; then
|
|
zaction[$x]="power_off"
|
|
[[ -z $powerchange ]] && powerchange="power_off"
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# pass 3 - If we're powering the whole system on/off, don't do anything else
|
|
if [[ ! -z $powerchange ]]; then
|
|
for x in ${!zname[@]}; do
|
|
if [[ ${powerchange} == "power_off" && ${zaction[$x]} != $powerchange ]]; then
|
|
zactionfail[$x]=1
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# pass xx - if we're closing the myzone, pick a new one
|
|
[[ $db -eq 1 ]] && info "pass 3 - powerchange is '$powerchange'"
|
|
if [[ -z $powerchange ]]; then
|
|
if [[ $airconmode != "off" ]]; then
|
|
mzmustmove=0
|
|
# get list of all zones which can't be myzone
|
|
nomyzone=""
|
|
for x in ${!zname[@]}; do
|
|
if [[ ${znomyzone[$x]} -eq 1 ]]; then
|
|
nomyzone="$nomyzone ${zname[$x]} "
|
|
fi
|
|
done
|
|
for x in ${!zname[@]}; do
|
|
if [[ $airconmyzoneid == ${zid[$x]} || ${zaction[$x]} == *set_myzone* ]]; then
|
|
if [[ ${zstate[$x]} == "close" || ${zaction[$x]} == *close_vent* ]]; then
|
|
mzmustmove=1
|
|
elif [[ ${zaction[@]} == *close:${zname[$x]}* ]]; then
|
|
mzmustmove=1
|
|
elif [[ ${znomyzone[$x]} -eq 1 ]]; then
|
|
mzmustmove=1
|
|
fi
|
|
if [[ $mzmustmove -eq 1 ]]; then
|
|
[[ $db -eq 1 ]] && info "myzone must move away from ${zname[$x]}"
|
|
#info "myzone must move away from ${zname[$x]}"
|
|
zaction[$x]="${zaction[$x]/set_myzone/}"
|
|
nomyzone="$nomyzone ${zname[$x]} "
|
|
[[ $db -eq 1 ]] && info "zaction[${zname[$z]} is '${zaction[$x]}'"
|
|
[[ $db -eq 1 ]] && info "nomyzone is '$nomyzone}'"
|
|
fi
|
|
fi
|
|
done
|
|
if [[ $mzmustmove -eq 1 ]]; then
|
|
[[ $db -eq 1 ]] && info "got mzmustmove == 1"
|
|
[[ $db -eq 1 ]] && info "FINAL nomyzone is '$nomyzone}'"
|
|
# find an alternative
|
|
for x in ${!zname[@]}; do
|
|
# open or opening? set myzone. we'll resolve
|
|
# duplicates in the next step.
|
|
[[ $db -eq 1 ]] && info " checking if we can move mz to ${zname[$x]}"
|
|
poss=0
|
|
if [[ ${zaction[$x]} != *set_myzone* && $nomyzone != *\ ${zname[$x]}\ * ]]; then
|
|
[[ $db -eq 1 ]] && info " not already moving mz here + not in nomyzone list"
|
|
if [[ ${zstate[$x]} == "open" || ${zaction[$x]} == *open_vent* ]]; then
|
|
[[ $db -eq 1 ]] && info " is open or opening -> OK"
|
|
poss=1
|
|
elif [[ ${zaction[@]} == *open:${zname[$x]}* ]]; then
|
|
[[ $db -eq 1 ]] && info " is opening via adjacency-> OK"
|
|
poss=1
|
|
fi
|
|
if [[ $poss -eq 1 ]]; then
|
|
[[ $db -eq 1 ]] && info " myzone could move to ${zname[$x]}"
|
|
#info " myzone could move to ${zname[$x]}"
|
|
zaction[$x]="${zaction[$x]} set_myzone"
|
|
[[ $db -eq 1 ]] && info " new zaction[${zname[$x]}] is ${zaction[x]}"
|
|
fi
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
fi
|
|
|
|
|
|
# pass 4 - multiple myzones?
|
|
if [[ -z $powerchange ]]; then
|
|
nmyzones=0
|
|
maxpri=-999
|
|
bestidx=-1
|
|
for x in ${!zname[@]}; do
|
|
# Multiple myzones?
|
|
if [[ ${zaction[$x]} == *set_myzone* ]]; then
|
|
#info " ${zname[$x]} wants myzone (pri=${zpri[$x]} maxpri=$maxpri)"
|
|
nmyzones=$((nmyzones + 1))
|
|
if [[ $(echo "scale=1; ${zpri[$x]} > $maxpri" | bc) == "1" ]]; then
|
|
bestidx=$x
|
|
maxpri=$(echo "scale=1; ${zpri[$x]}" | bc)
|
|
#info " new best priority: ${zname[$x]} = $maxpri"
|
|
fi
|
|
fi
|
|
done
|
|
if [[ $nmyzones -gt 1 ]]; then
|
|
for x in ${!zname[@]}; do
|
|
if [[ $x -ne $bestidx && ${zaction[$x]} == *set_myzone* ]]; then
|
|
zaction[$x]="${zaction[$x]/set_myzone/}"
|
|
fi
|
|
done
|
|
fi
|
|
fi
|
|
|
|
# final pass
|
|
for x in ${!zname[@]}; do
|
|
if [[ ${zactionfail[$x]} -eq 0 ]]; then
|
|
# Remove myzone if already set
|
|
if [[ ${zaction[$x]} == *set_myzone* && $airconmyzoneid == ${zid[x]} ]]; then
|
|
zaction[$x]="${zaction[$x]/set_myzone/}"
|
|
fi
|
|
|
|
# Remove leading spaces
|
|
zaction[$x]=$(echo ${zaction[$x]} | awk '{$1=$1; sub("n/a ",""); sub(" n/a","") };1')
|
|
# Generate actual aircon commands from zone commands
|
|
gen_aircon_command ${x} ${zaction[$x]}
|
|
fi
|
|
done
|
|
}
|
|
|
|
|
|
# gen_aircon_command zone_idx "command1 command2 etc"
|
|
function gen_aircon_command() {
|
|
local idx allactions this toadd num doneset othername otherid otheridx db donemode str
|
|
idx=$1
|
|
shift
|
|
allactions="$*"
|
|
doneset=0
|
|
donemode=0
|
|
|
|
db=0
|
|
[[ $db -eq 1 ]] && info "gen_aircon_command() for idx=${idx}: $allactions"
|
|
|
|
IFS=' ' read -ra tok <<< "${allactions}"
|
|
for this in ${tok[@]}; do
|
|
if [[ $this == *power_on* ]]; then
|
|
add_aircon_command $idx -1 "Power on system" "on"
|
|
elif [[ $this == *power_off* ]]; then
|
|
add_aircon_command $idx -1 "Power off system" "off"
|
|
elif [[ $this == *set_myzone* && $airconmyzoneid != ${zid[$idx]} ]]; then
|
|
add_aircon_command $idx -1 "Set MyZone to ${zname[$idx]}" "myzone --zone ${zid[$idx]}"
|
|
elif [[ $this == *open:* ]]; then
|
|
othername=$(echo "$this" | sed -e 's/^.*open://;s/ .*//')
|
|
otheridx=$(getidxfromname $othername)
|
|
otherid=${zid[$otheridx]}
|
|
add_aircon_command $idx $otheridx "Open vent in ${zname[$otheridx]}" "set --zone ${otherid} --state on --temp ${zsettemp[$otheridx]}"
|
|
elif [[ $this == *close:* ]]; then
|
|
othername=$(echo "$this" | sed -e 's/^.*close://;s/ .*//')
|
|
otheridx=$(getidxfromname $othername)
|
|
otherid=${zid[$otheridx]}
|
|
add_aircon_command $idx $otheridx "Close vent in ${zname[$otheridx]}" "set --zone ${otherid} --state off --temp ${zsettemp[$otheridx]}"
|
|
elif [[ $this == *set_mode:* ]]; then
|
|
str=$(echo "$this" | sed -e 's/^.*set_mode://;s/ .*//')
|
|
add_aircon_command $idx -1 "Set system mode to '$str'" "$str"
|
|
elif [[ $this == *set_temp* || $this == *open_vent* || $this == *close_vent* ]]; then
|
|
if [[ $doneset -eq 0 ]]; then
|
|
toadd=""
|
|
comm=""
|
|
if [[ $allactions == *open_vent* ]]; then
|
|
toadd="$toadd --state on"
|
|
comm="open vent"
|
|
elif [[ $allactions == *close_vent* ]]; then
|
|
toadd="$toadd --state off"
|
|
comm="close vent"
|
|
fi
|
|
if [[ $allactions == *set_temp* ]]; then
|
|
num=$(echo "$this" | sed -e 's/^.*set_temp://;s/ .*//')
|
|
toadd="$toadd --temp $num"
|
|
[[ ! -z $comm ]] && comm="${comm} and "
|
|
comm="${comm}set temperature to $num degrees"
|
|
else
|
|
toadd="$toadd --temp ${zsettemp[$idx]}"
|
|
fi
|
|
if [[ ! -z $toadd ]]; then
|
|
add_aircon_command $idx -1 "In zone ${zname[$idx]}, $comm" "set --zone ${zid[$idx]}$toadd"
|
|
doneset=1
|
|
fi
|
|
fi
|
|
fi
|
|
done
|
|
}
|
|
|
|
# add_aircon_command zone_idx "comment goes here" "actual pymyair command to run"
|
|
function add_aircon_command() {
|
|
local x idx otheridx comment db
|
|
|
|
[[ $# -le 1 ]] && return 1
|
|
|
|
idx=$1
|
|
shift
|
|
otheridx=$1
|
|
shift
|
|
comment=$1
|
|
shift
|
|
|
|
if [[ $otheridx -ne -1 ]]; then
|
|
if [[ ! -z $comment ]]; then
|
|
comment="${comment} (to fix ${zname[$idx]})"
|
|
fi
|
|
fi
|
|
|
|
db=0
|
|
[[ $db -eq 1 ]] && info " add_aircon_command() for idx=${idx} otheridx=${otheridx} comment=[$comment]: $*" >/dev/stderr
|
|
|
|
[[ -z $* ]] && return 1
|
|
|
|
for x in ${airconcmd[@]}; do
|
|
if [[ $x == "$*" ]]; then
|
|
return 1
|
|
fi
|
|
done
|
|
airconcmd[$nairconcommands]="$*"
|
|
airconcomment[$nairconcommands]="$comment"
|
|
airconcmdzone[$nairconcommands]="${zname[$idx]}"
|
|
airconcmdotherzone[$nairconcommands]="${zname[$otheridx]}"
|
|
airconproblem[$nairconcommands]="${zname[$idx]} is ${zproblem[$idx]/_/ }"
|
|
#if [[ $otheridx -ne -1 ]]; then
|
|
# airconcomment[$nairconcommands]="${airconcomment[$nairconcommands]}, update nearby zone ${zname[$otheridx]}"
|
|
#fi
|
|
nairconcommands=$((nairconcommands + 1))
|
|
return 0
|
|
|
|
}
|
|
|
|
function addnoop() {
|
|
local idx
|
|
idx=$(getidxfromname "$1")
|
|
zignore[$idx]=1
|
|
}
|
|
|
|
|
|
function addnomyzone() {
|
|
local idx
|
|
idx=$(getidxfromname "$1")
|
|
znomyzone[$idx]=1
|
|
}
|
|
|
|
function addforcevent() {
|
|
local x idx
|
|
idx=$nforcevents
|
|
for x in ${!fv_zonename[@]}; do
|
|
if [[ ${fv_zonename[$x]} == "$1" ]]; then
|
|
idx=$x
|
|
break;
|
|
fi
|
|
done
|
|
fv_zonename[$idx]="$1"
|
|
fv_state[$idx]="$2"
|
|
[[ $idx == $nforcevents ]] && nforcevents=$((nforcevents + 1))
|
|
}
|
|
|
|
function addtemprange() {
|
|
local x idx
|
|
idx=$ntempranges
|
|
for x in ${!tr_zonename[@]}; do
|
|
if [[ ${tr_zonename[$x]} == "$1" ]]; then
|
|
idx=$x
|
|
break;
|
|
fi
|
|
done
|
|
tr_zonename[$idx]="$1"
|
|
tr_range[$idx]="$2"
|
|
[[ $idx == $ntempranges ]] && ntempranges=$((ntempranges + 1))
|
|
}
|
|
|
|
function addadj() {
|
|
local name
|
|
name="$1"
|
|
shift
|
|
adj_zonename[$nadj]="$name"
|
|
adj_nearby[$nadj]="$*"
|
|
nadj=$((nadj + 1))
|
|
}
|
|
|
|
function addperson() {
|
|
local h p x idx db
|
|
db=0
|
|
|
|
p="$1"
|
|
shift
|
|
h="$*"
|
|
|
|
[[ $db -eq 1 ]] && info "addperson() person $p host(s) [$h]"
|
|
|
|
idx=$npeople
|
|
for x in ${!pname[@]}; do
|
|
if [[ ${pname[$x]} == "$p" ]]; then
|
|
[[ $db -eq 1 ]] && info " owner already exists, appending."
|
|
idx=$x
|
|
break;
|
|
fi
|
|
done
|
|
pname[$idx]="$p"
|
|
if [[ ${pdev[$idx]} != *\ $h\ * ]]; then
|
|
pdev[$idx]="${pdev[$idx]} $h "
|
|
fi
|
|
[[ $db -eq 1 ]] && info " dev val is: ${pdev[$idx]}"
|
|
[[ $idx == $npeople ]] && npeople=$((npeople + 1))
|
|
}
|
|
|
|
function addowner() {
|
|
local z h local x idx db
|
|
db=0
|
|
|
|
z="$1"
|
|
shift
|
|
h="$*"
|
|
|
|
[[ $db -eq 1 ]] && info "addowner() zone $z host(s) [$h]"
|
|
|
|
idx=$nowners
|
|
for x in ${!ownerzone[@]}; do
|
|
if [[ ${ownerzone[$x]} == "$z" ]]; then
|
|
[[ $db -eq 1 ]] && info " owner already exists, appending."
|
|
idx=$x
|
|
break;
|
|
fi
|
|
done
|
|
ownerzone[$idx]="$z"
|
|
if [[ ${ownerhost[$idx]} != *\ $h\ * ]]; then
|
|
ownerhost[$idx]="${ownerhost[$idx]} $h "
|
|
fi
|
|
[[ $db -eq 1 ]] && info " hosts val is: ${ownerhost[$idx]}"
|
|
[[ $idx == $nowners ]] && nowners=$((nowners + 1))
|
|
}
|
|
|
|
# getownerperson zone_name
|
|
# returns name of person who owns zone
|
|
function getownerperson() {
|
|
local x retval
|
|
retval=""
|
|
for x in ${!ownerzone[@]}; do
|
|
if [[ ${ownerzone[$x]} == "$1" ]]; then
|
|
retval="${ownerperson[$x]}"
|
|
break
|
|
fi
|
|
done
|
|
echo "$retval"
|
|
}
|
|
|
|
# getowner zone_name
|
|
# returns devices associated woth zone
|
|
function getowner() {
|
|
local x retval
|
|
retval=""
|
|
for x in ${!ownerzone[@]}; do
|
|
if [[ ${ownerzone[$x]} == "$1" ]]; then
|
|
retval="${ownerhost[$x]}"
|
|
break
|
|
fi
|
|
done
|
|
echo "$retval"
|
|
}
|
|
|
|
# gettemprange zone_name
|
|
function gettemprange() {
|
|
local x retval
|
|
retval="n/a"
|
|
for x in ${!tr_zonename[@]}; do
|
|
if [[ ${tr_zonename[$x]} == "$1" ]]; then
|
|
retval="${tr_range[$x]}"
|
|
break
|
|
fi
|
|
done
|
|
echo "$retval"
|
|
}
|
|
|
|
function getstrtype() {
|
|
local strtype
|
|
shopt -s nocasematch
|
|
strtype=unknown
|
|
if [[ "$*" =~ ^[0-9]{4}$ ]]; then
|
|
strtype="24h"
|
|
elif [[ "$*" =~ ^(mon|tue|wed|thu|fri|sat|sun)$ ]]; then
|
|
strtype="weekday"
|
|
fi
|
|
shopt -u nocasematch
|
|
echo "$strtype"
|
|
}
|
|
|
|
function numtoweekday() {
|
|
local num
|
|
shopt -s nocasematch
|
|
num=-1
|
|
case "$1" in
|
|
"mon") num=1;;
|
|
"tue") num=2;;
|
|
"wed") num=3;;
|
|
"thu") num=4;;
|
|
"fri") num=5;;
|
|
"sat") num=6;;
|
|
"sun") num=7;;
|
|
esac
|
|
shopt -u nocasematch
|
|
echo "$num"
|
|
}
|
|
|
|
function conderror() {
|
|
error "Invalid condition: $1"
|
|
echo -e "${RED} Start: $2"
|
|
echo -e "${RED} End: $3"
|
|
}
|
|
|
|
function load_config() {
|
|
local line rv timestr ttok stype ok starth endh nowh
|
|
local cond allconds x
|
|
local db
|
|
db=0
|
|
rv=0
|
|
while read line; do
|
|
line=${line%%#*} # remove comments
|
|
if [[ ! -z ${line// } ]]; then
|
|
# time based options
|
|
if [[ ${line:0:1} == "@" ]]; then
|
|
timestr=${line%% *}
|
|
timestr=${timestr:1}
|
|
IFS=';' read -ra allconds <<< "$timestr"
|
|
for cond in ${allconds[@]} ; do
|
|
[[ $db -eq 1 ]] && info "Processing condition: '$cond'"
|
|
IFS='-' read -ra ttok <<< "$cond"
|
|
if [[ ${#ttok[@]} == 1 ]]; then
|
|
stype[0]=$(getstrtype ${ttok[0]})
|
|
if [[ ${stype[0]} != "weekday" ]]; then
|
|
error "Invalid condition: '${cond}'"
|
|
return 1
|
|
fi
|
|
starth=$(numtoweekday ${ttok[0]})
|
|
if [[ $starth -eq -1 ]]; then
|
|
error "'${ttok[0]}' is not a valid type here (type is ${stype[0]})"
|
|
|
|
return 1
|
|
fi
|
|
endh="$starth"
|
|
nowh=$(date +%u)
|
|
else
|
|
stype[0]=$(getstrtype ${ttok[0]})
|
|
stype[1]=$(getstrtype ${ttok[1]})
|
|
starth=""
|
|
endh=""
|
|
nowh=""
|
|
if [[ ${stype[0]} != ${stype[1]} ]]; then
|
|
error "start and end condition types don't match:"
|
|
echo -e "${RED} Start: ${ttok[0]} (${stype[0]})${PLAIN}"
|
|
echo -e "${RED} End: ${ttok[1]} (${stype[1]})${PLAIN}"
|
|
return 1
|
|
elif [[ ${stype[0]} == "24h" ]]; then
|
|
starth=${ttok[0]}
|
|
endh=${ttok[1]}
|
|
nowh=$(date +%H%M)
|
|
elif [[ ${stype[0]} == "weekday" ]]; then
|
|
starth=$(numtoweekday ${ttok[0]})
|
|
endh=$(numtoweekday ${ttok[1]})
|
|
if [[ $starth -eq -1 || $endh -eq -1 ]]; then
|
|
conderror "${cond}" "'${ttok[0]}' (${stype[0]})" "'${ttok[1]}' (${stype[1]})"
|
|
|
|
return 1
|
|
fi
|
|
nowh=$(date +%u)
|
|
else
|
|
conderror "${cond}" "'${ttok[0]}' (${stype[0]})" "'${ttok[1]}' (${stype[1]})"
|
|
return 1
|
|
fi
|
|
fi
|
|
if [[ ! -z $starth ]]; then
|
|
[[ $db -eq 1 ]] && info " check if $nowh is between $starth and $endh"
|
|
ok=0
|
|
if [[ $(echo "$starth < $endh" | bc) == "1" ]]; then
|
|
if [[ $(echo "$nowh >= $starth" | bc) == "1" && $(echo "$nowh <= $endh" | bc) == "1" ]]; then
|
|
ok=1
|
|
fi
|
|
elif [[ $(echo "$starth > $endh" | bc) == "1" ]]; then
|
|
if [[ $(echo "$nowh <= $endh" | bc) == "1" || $(echo "$nowh >= $starth" | bc) == "1" ]]; then
|
|
ok=1
|
|
fi
|
|
else
|
|
if [[ $nowh == $endh || $nowh == $starth ]]; then
|
|
ok=1
|
|
fi
|
|
fi
|
|
[[ $db -eq 1 && $ok -eq 0 ]] && info " $nowh isn't in range $cond"
|
|
[[ $db -eq 1 && $ok -eq 1 ]] && info " MATCH: $nowh within range $cond"
|
|
fi
|
|
if [[ $ok -eq 0 ]]; then
|
|
break
|
|
fi
|
|
done
|
|
if [[ $ok -eq 1 ]]; then
|
|
# strip condition off the front
|
|
line=${line#* }
|
|
fi
|
|
else
|
|
ok=1
|
|
fi
|
|
if [[ $ok -eq 1 ]]; then
|
|
IFS=' ' read -ra tok <<< "$line"
|
|
if [[ ${tok[0]} == "temp" ]]; then
|
|
addtemprange ${tok[1]} ${tok[2]}
|
|
elif [[ ${tok[0]} == "constant" ]]; then
|
|
if [[ -z ${tok[1]} ]]; then
|
|
error "Missing zone name in constant command."
|
|
exit 1
|
|
elif [[ -z $constant ]]; then
|
|
constant=${tok[1]}
|
|
else
|
|
error "Constant zone defined more than once."
|
|
exit 1
|
|
fi
|
|
elif [[ ${tok[0]} == "adj" ]]; then
|
|
addadj ${tok[1]} ${tok[@]:2}
|
|
elif [[ ${tok[0]} == "nomyzone" || ${tok[0]} == "nomy" ]]; then
|
|
addnomyzone ${tok[1]}
|
|
elif [[ ${tok[0]} == "noop" || ${tok[0]} == "ignore" ]]; then
|
|
addnoop ${tok[1]}
|
|
elif [[ ${tok[0]} == "force" ]]; then
|
|
if [[ $VALID_ZONE_STATES == *\ ${tok[2]}\ * ]]; then
|
|
addforcevent ${tok[1]} ${tok[2]}
|
|
else
|
|
error "Invalid zone state '${tok[2]}'. Valid options are: $VALID_ZONE_STATES"
|
|
exit 1
|
|
fi
|
|
elif [[ ${tok[0]} == "owner" ]]; then
|
|
addowner ${tok[1]} ${tok[@]:2}
|
|
elif [[ ${tok[0]} == "person" ]]; then
|
|
addperson ${tok[1]} ${tok[@]:2}
|
|
elif [[ ${tok[0]} == "modelock" ]]; then
|
|
if [[ $VALID_MODES == *\ ${tok[1]}\ * ]]; then
|
|
modelock="${tok[1]}"
|
|
else
|
|
error "Invalid modelock '${tok[1]}'. Valid options are: $VALID_MODES"
|
|
exit 1
|
|
fi
|
|
elif [[ ${tok[0]} == "test" ]]; then
|
|
info "Got test option: '${tok[@]}'"
|
|
rv=1
|
|
fi
|
|
fi
|
|
|
|
fi
|
|
done < "$CONFIGFILE"
|
|
return $rv
|
|
}
|
|
|
|
# if we can ping any of the args, return ok
|
|
function canping() {
|
|
local ip db host thisrv mac
|
|
db=0
|
|
for host in $*; do
|
|
ip=$(getent hosts $host | grep -v :)
|
|
ip=${ip%% *}
|
|
if [[ -z $ip ]]; then
|
|
thisrv=1
|
|
else
|
|
#arp -d $ip >/dev/null 2>&1
|
|
#ping -c1 -w1 -n -q $ip >/dev/null 2>&1 &
|
|
#sleep 0.3
|
|
mac=$(arp -n $ip)
|
|
mac=$(echo "$mac" | egrep -v "Host|xpired" | awk '{print $2}')
|
|
[[ $mac == *:* ]] && thisrv=0 || thisrv=1
|
|
fi
|
|
[[ $db -eq 1 ]] && info "canping() $host ($ip) is $thisrv - mac=$mac"
|
|
if [[ $thisrv -eq 0 ]]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
function addzone() {
|
|
local name state settemp temp wanttemp wanttempmin wanttempmax id
|
|
name="$1"
|
|
state="$2"
|
|
settemp="$3"
|
|
temp="$4"
|
|
id="$5"
|
|
zname[$nzones]="$name"
|
|
zstate[$nzones]="$state"
|
|
zsettemp[$nzones]="$settemp"
|
|
ztemp[$nzones]="$temp"
|
|
zid[$nzones]="$id"
|
|
zwanttemp[$nzones]="n/a"
|
|
zproblem[$nzones]="n/a"
|
|
zaction[$nzones]="n/a"
|
|
zactionfail[$nzones]=0
|
|
zowner[$nzones]=$(getowner "$name")
|
|
zownerperson[$nzones]=$(getownerperson "$name")
|
|
#info "zone $name owner is [${zowner[$nzones]}]"
|
|
zignore[$nzones]=0
|
|
znomyzone[$nzones]=0
|
|
zpri[$nzones]=0
|
|
wanttemp=$(gettemprange "$name")
|
|
if [[ $wanttemp == "n/a" ]]; then
|
|
zwanttemp[$nzones]="n/a"
|
|
zwantmin[$nzones]="-99"
|
|
zwantmax[$nzones]="99"
|
|
else
|
|
zwanttemp[$nzones]="$wanttemp"
|
|
zwantmin[$nzones]="${wanttemp%-*}"
|
|
zwantmax[$nzones]="${wanttemp#*-}"
|
|
fi
|
|
|
|
nzones=$((nzones + 1))
|
|
}
|
|
|
|
function getcol() {
|
|
local col
|
|
case $1 in
|
|
"off") col="$GREY";;
|
|
"cool") col="$CYAN";;
|
|
"heat") col="$RED";;
|
|
*) col="$PLAIN";;
|
|
esac
|
|
echo $col
|
|
}
|
|
|
|
function gen_config() {
|
|
local x
|
|
if [[ -e $CONFIGFILE ]]; then
|
|
error "$CONFIGFILE already exists"
|
|
return 1
|
|
fi
|
|
get_aircon_info
|
|
cp /dev/null $CONFIGFILE
|
|
echo "# Specify commandline args here" >> $CONFIGFILE
|
|
echo "# options -p -t 0.3 ... " >> $CONFIGFILE
|
|
echo >> $CONFIGFILE
|
|
echo "# Don't switch to modes other than this" >> $CONFIGFILE
|
|
echo "#modelock heat|cool" >> $CONFIGFILE
|
|
echo >> $CONFIGFILE
|
|
echo "# Set temperature range to enforce" >> $CONFIGFILE
|
|
for x in ${!zname[@]}; do
|
|
echo "#temp ${zname[$x]} min_temp-max_temp" >> $CONFIGFILE
|
|
done
|
|
echo >> $CONFIGFILE
|
|
echo "# Turn off zone if we can't ARP for given hostname's IP" >> $CONFIGFILE
|
|
for x in ${!zname[@]}; do
|
|
echo "#owner ${zname[$x]} hostname.domain" >> $CONFIGFILE
|
|
done
|
|
echo >> $CONFIGFILE
|
|
echo "# Define which zones are adjacent to each other (used to close nearby" >> $CONFIGFILE
|
|
echo "# zones to help control temperature)" >> $CONFIGFILE
|
|
for x in ${!zname[@]}; do
|
|
echo "#adj ${zname[$x]} adjacent_zone1 [adjacent_zone2] ... [adjacent_zoneX]" >> $CONFIGFILE
|
|
done
|
|
echo >> $CONFIGFILE
|
|
echo "# Force a zone into a certain state" >> $CONFIGFILE
|
|
echo "#force ${zname[0]} open" >> $CONFIGFILE
|
|
echo "#force ${zname[1]} close" >> $CONFIGFILE
|
|
echo >> $CONFIGFILE
|
|
echo "# All commands except options can be restricted to certain times with:" >> $CONFIGFILE
|
|
echo "# @hhmm-hhmm Restrict between certain 24 hour times" >> $CONFIGFILE
|
|
echo "# @Mon-Wed Restrict between certain weekdays" >> $CONFIGFILE
|
|
echo "# @Mon Restrict to one weekdays only" >> $CONFIGFILE
|
|
|
|
echo "# @Mon;hhmm-hhmm Restrict both day and time" >> $CONFIGFILE
|
|
echo "# For example:" >> $CONFIGFILE
|
|
echo "# Keep zone closed on Tuesdays and Wednesdays" >> $CONFIGFILE
|
|
echo "#@Tue-Wed force ${zname[0]} close" >> $CONFIGFILE
|
|
echo "# Override temperature range between 11pm and 5am" >> $CONFIGFILE
|
|
echo "#@2300-0500 temp ${zname[0]} 20-21" >> $CONFIGFILE
|
|
echo "# Keep zone open between 10am and 11am on Mondays" >> $CONFIGFILE
|
|
echo "#@Mon;1000-1100 force ${zname[0]} open" >> $CONFIGFILE
|
|
}
|
|
|
|
function get_aircon_info() {
|
|
profile "query aircon"
|
|
[[ $cronmode -eq 0 && $logmode -eq 0 ]] && echo -en "${GREEN}${BOLD}>> ${PLAIN}${GREEN}Querying aircon... ${PLAIN}"
|
|
zones=$(myair $AIRCON_IP zones | jq -r '.[] | [ .name, .state, .setTemp, .measuredTemp, .number ] | @csv' | tr -d '" ')
|
|
airconmode=$(myair $AIRCON_IP mode)
|
|
nzones=0
|
|
for line in $zones; do
|
|
IFS=',' read -ra tok <<< "$line"
|
|
addzone "${tok[0]}" "${tok[1]}" "${tok[2]}" "${tok[3]}" "${tok[4]}"
|
|
done
|
|
airconmyzoneid=$(myair $AIRCON_IP myzone)
|
|
airconmyzone=$(getnamefromid $airconmyzoneid)
|
|
[[ $cronmode -eq 0 && $logmode -eq 0 ]] && echo -e "${GREEN}${BOLD}ok${PLAIN}"
|
|
profile "query aircon"
|
|
}
|
|
|
|
function show_aircon_status() {
|
|
local zonestr zonecol settempcol actioncol thiswantcol thisstate thisstateform
|
|
local thisformat x powercol lockcol
|
|
local FORMAT1 FORMAT2 FORMATHOT FORMATCOLD FORMATOK CLOSEDFORM OPENFORM
|
|
|
|
FORMAT1="${BOLD}%-16s%-7s%-8s%-7s%-7s%s${PLAIN}"
|
|
FORMAT2="${UNDERLINE}${FORMAT1}${PLAIN}"
|
|
FORMATHOT="${RED}%-7s${PLAIN}"
|
|
FORMATCOLD="${CYAN}%-7s${PLAIN}"
|
|
FORMATOK="${WHITE}%-7s${PLAIN}"
|
|
CLOSEDFORM="$GREY%-8s$PLAIN"
|
|
OPENFORM="$PLAIN%-8s$PLAIN"
|
|
|
|
powercol=$(getcol $airconmode)
|
|
lockcol=$(getcol $modelock)
|
|
printf "${BOLD}Aircon mode:${PLAIN} $powercol%s$PLAIN" $airconmode
|
|
if [[ $modelock != "n/a" ]]; then
|
|
echo -e " (locked to $lockcol$modelock$PLAIN)"
|
|
else
|
|
echo
|
|
fi
|
|
printf "${BOLD}Current myzone:${PLAIN} %s (zone %s)\n" $airconmyzone $airconmyzoneid
|
|
printf "${BOLD}Tolerance:${PLAIN} %s degrees low, %s degrees high" $tolerance_l $tolerance_h
|
|
|
|
echo
|
|
|
|
printf "$FORMAT1\n" "" "Valid" "" "Set" "Actual" "Proposed "
|
|
printf "$FORMAT2\n" "Zone" "Temp" "State" "Temp" "Temp" "Action "
|
|
for x in ${!zname[@]}; do
|
|
if [[ ${zproblem[$x]} == "too_hot" ]]; then
|
|
thisformat="${FORMATHOT}"
|
|
elif [[ ${zproblem[$x]} == "too_cold" ]]; then
|
|
thisformat="${FORMATCOLD}"
|
|
else
|
|
thisformat="${FORMATOK}"
|
|
fi
|
|
if [[ ${airconmode} == "off" ]]; then
|
|
thisstateform="${CLOSEDFORM}"
|
|
thisstate="<off>"
|
|
elif [[ ${zstate[$x]} == "close" ]]; then
|
|
thisstateform="${CLOSEDFORM}"
|
|
thisstate=${zstate[$x]}
|
|
else
|
|
thisstateform="${OPENFORM}"
|
|
thisstate=${zstate[$x]}
|
|
fi
|
|
thiswant="${zwanttemp[$x]}"
|
|
if [[ $thiswant == "n/a" ]]; then
|
|
thiswantcol="$GREY"
|
|
else
|
|
thiswantcol="$PLAIN"
|
|
fi
|
|
if [[ ${zactionfail[$x]} -eq 1 || ${zaction[$x]} == "n/a" ]]; then
|
|
actioncol="$GREY"
|
|
else
|
|
actioncol="$PLAIN"
|
|
fi
|
|
|
|
if [[ ${airconmode} == "off" ]]; then
|
|
settempcol="$GREY"
|
|
else
|
|
settempcol="$PLAIN"
|
|
fi
|
|
|
|
if [[ $airconmyzoneid == ${zid[$x]} ]]; then
|
|
zonecol="$WHITE$BOLD"
|
|
zonestr="${zname[$x]} (M)"
|
|
else
|
|
zonecol="$PLAIN"
|
|
zonestr="${zname[$x]}"
|
|
fi
|
|
if [[ $constant == ${zname[$x]} ]]; then
|
|
zonestr="${zname[$x]} (C)"
|
|
fi
|
|
|
|
printf "$zonecol%-16s$PLAIN$thiswantcol%-7s$PLAIN$thisstateform$settempcol%-7s$PLAIN$thisformat$actioncol%s$PLAIN\n" "${zonestr}" "${thiswant}" "${thisstate}" "${zsettemp[$x]}" "${ztemp[$x]}" "${zaction[$x]}"
|
|
done
|
|
|
|
}
|
|
|
|
function show_analysis() {
|
|
local count x ostr
|
|
echo -e "${UNDERLINE}Analysis:${PLAIN}"
|
|
count=0
|
|
for x in ${!zproblem[@]}; do
|
|
if [[ ${zproblem[$x]} != "n/a" ]]; then
|
|
if [[ ${zproblem[$x]} == "owner_not_home" ]]; then
|
|
if [[ ${zowner[$x]} == *\ * ]]; then
|
|
ostr="owners are"
|
|
else
|
|
ostr="owner is"
|
|
fi
|
|
echo "- ${zname[$x]} open but $ostr not detected (${zowner[$x]})."
|
|
elif [[ ${zproblem[$x]} =~ force_ ]]; then
|
|
what=${zproblem[$x]##*_}
|
|
[[ $what == "close" ]] && what="${what}d"
|
|
echo "- ${zname[$x]} is forced $what"
|
|
else
|
|
if [[ $constant == ${zname[$x]} ]]; then
|
|
append=" (constant zone)"
|
|
else
|
|
append=""
|
|
fi
|
|
echo "- ${zname[$x]} is ${zproblem[$x]/_/ }${append}"
|
|
fi
|
|
count=$((count + 1))
|
|
fi
|
|
done
|
|
if [[ $globprob == too_* ]]; then
|
|
echo "- $limit or more zones are ${globprob/_/ }"
|
|
fi
|
|
[[ $count -eq 0 ]] && echo -e "${GREEN}All is good!${PLAIN}"
|
|
}
|
|
|
|
function show_proposed_commands() {
|
|
local str x maxlen count cmdformat fullcmd
|
|
str="Proposed commands"
|
|
echo -e "${UNDERLINE}${str}:${PLAIN}"
|
|
|
|
if [[ $nairconcommands -gt 0 ]]; then
|
|
if [[ -f ${KILLFILE} ]]; then
|
|
echo -e "${RED}Not running commands because killfile exists ($KILLFILE)${PLAIN}"
|
|
elif [[ $DOIT -ne 1 ]]; then
|
|
echo -e "${RED}Not running commands because -y not specified${PLAIN}"
|
|
fi
|
|
fi
|
|
|
|
maxlen=-99
|
|
for x in ${!airconcmd[@]}; do
|
|
fullcmd="myair $AIRCON_IP ${airconcmd[$x]}"
|
|
thislen=${#fullcmd}
|
|
[[ $thislen -gt $maxlen ]] && maxlen=$thislen
|
|
done
|
|
|
|
cmdformat="%-$((maxlen + 3))s"
|
|
count=0
|
|
for x in ${!airconcmd[@]}; do
|
|
fullcmd="myair $AIRCON_IP ${airconcmd[$x]}"
|
|
printf -- "$cmdformat" "$fullcmd"
|
|
[[ ! -z ${airconcomment[$x]} ]] && printf " $GREEN$BOLD# $PLAIN$GREEN%s$PLAIN\n" "${airconcomment[$x]}" || echo
|
|
count=$((count + 1))
|
|
done
|
|
[[ $count -eq 0 ]] && echo -e "${GREY}n/a${PLAIN}"
|
|
}
|
|
|
|
function run_commands() {
|
|
local x
|
|
for x in ${!airconcmd[@]}; do
|
|
[[ ! -z ${airconcomment[$x]} ]] && action "${airconcomment[$x]}"
|
|
echo RUNNING myair $AIRCON_IP ${airconcmd[$x]}
|
|
myair $AIRCON_IP ${airconcmd[$x]} >/dev/null 2>&1
|
|
influx_insert "INSERT aircon action=\"${airconcomment[$x]}\",comment=\"${airconproblem[$x]}\""
|
|
done
|
|
}
|
|
|
|
function profile() {
|
|
local diff
|
|
[[ $profiler != 1 ]] && return
|
|
p_last=$p_now
|
|
p_now=$(date +%s)
|
|
diff=$(( p_now - $p_last))
|
|
if [[ -z $p_str ]]; then
|
|
p_str="$*"
|
|
else
|
|
info "+${diff} secs [$*]" >/dev/stderr
|
|
p_str=""
|
|
fi
|
|
|
|
}
|
|
|
|
# poplulates glpbals:
|
|
# failedcmds
|
|
# influxrv
|
|
function influx_clear() {
|
|
failedcmds=""
|
|
influxrv=0
|
|
}
|
|
|
|
# poplulates glpbals:
|
|
# failedcmds
|
|
# influxrv
|
|
function influx_insert() { # $1 == cmd
|
|
local cmd thisrv
|
|
if [[ -z ${influx} ]]; then
|
|
return 1
|
|
fi
|
|
cmd="$1"
|
|
if [[ $DOIT -eq 1 ]]; then
|
|
${influx} -database "$influxdb" -execute "$cmd"
|
|
else
|
|
echo "${influx} -database $influxdb -execute $cmd"
|
|
thisrv=0
|
|
fi
|
|
thisrv=$?
|
|
influxrv=$((influxrv + $thisrv))
|
|
if [[ $thisrv -ne 0 ]]; then
|
|
failedcmds="$failedcmds$cmd\n"
|
|
fi
|
|
}
|
|
|
|
# poplulates glpbals:
|
|
# influx
|
|
# influxdb
|
|
function influx_init() { # $1 == dbname
|
|
local dbfound
|
|
influx=$(which influx 2>/dev/null)
|
|
influxdb="$1"
|
|
if [[ $? -ne 0 ]]; then
|
|
error "influx executable not found in path."
|
|
return 1
|
|
fi
|
|
dbfound=$(${influx} -database "$influxdb" -execute "SHOW DATABASES" | grep -w ^${influxdb})
|
|
if [[ -z $dbfound ]]; then
|
|
error "Couldn't find influx database '$influxdb'"
|
|
return 1
|
|
fi
|
|
influx_clear
|
|
return 0
|
|
}
|
|
|
|
|
|
|
|
p_now=$(date +%s)
|
|
p_last=$p_now
|
|
p_str=""
|
|
|
|
CONFIGFILE=${DEFAULT_CONFIGFILE}
|
|
KILLFILE=${DEFAULT_KILLFILE}
|
|
AIRCON_IP=${DEFAULT_AIRCON_IP}
|
|
|
|
airconmode="unknown"
|
|
airconmyzoneid="n/a"
|
|
airconmyzone="n/a"
|
|
globprob=""
|
|
nairconcommands=0
|
|
ntempranges=0
|
|
nadj=0
|
|
nowners=0
|
|
npeople=0
|
|
tolerance_l=${DEFAULT_TOLERANCE}
|
|
tolerance_h=${DEFAULT_TOLERANCE}
|
|
constant=""
|
|
|
|
DOIT=0
|
|
makeconfig=0
|
|
modelock="n/a"
|
|
cronmode=0
|
|
logmode=0 #0=none, 1=csv_file, 2=local influxdb
|
|
influxdb=""
|
|
showwho=0
|
|
limit=$DEFAULTLIMIT
|
|
profiler=0
|
|
csvfile="$DEFAULT_CSVFILE"
|
|
|
|
|
|
# check for config file option first
|
|
if [[ $* == *-f\ * ]]; then
|
|
cf=$(echo "$*" | sed -e 's/.*-f //;s/ .*//')
|
|
if [[ ! -z $cf ]]; then
|
|
CONFIGFILE="$cf"
|
|
fi
|
|
fi
|
|
|
|
ALLARGS=""
|
|
if [[ -e $CONFIGFILE ]]; then
|
|
# load options from file...
|
|
ALLARGS=$(egrep "^options " $CONFIGFILE | sed -e 's/^options //' | tr '\n' ' ')
|
|
fi
|
|
|
|
ALLARGS="$ALLARGS $*"
|
|
|
|
optstring="A:cf:hi:I:k:l:Lo:pymwt:T:x:"
|
|
while getopts "$optstring" i $ALLARGS; do
|
|
case "$i" in
|
|
h)
|
|
usage;
|
|
exit 1;
|
|
;;
|
|
f)
|
|
CONFIGFILE=${OPTARG}
|
|
;;
|
|
i)
|
|
AIRCON_IP=${OPTARG}
|
|
;;
|
|
I)
|
|
logmode=2
|
|
influx_init "$OPTARG" || exit 1
|
|
;;
|
|
A)
|
|
influx_init "$OPTARG" || exit 1
|
|
;;
|
|
k)
|
|
KILLFILE=${OPTARG}
|
|
;;
|
|
l)
|
|
limit=${OPTARG}
|
|
;;
|
|
o)
|
|
csvfile="$OPTARG"
|
|
;;
|
|
L)
|
|
logmode=1
|
|
;;
|
|
t)
|
|
tolerance_l=${OPTARG}
|
|
;;
|
|
T)
|
|
tolerance_h=${OPTARG}
|
|
;;
|
|
c)
|
|
enable_cronmode
|
|
;;
|
|
p)
|
|
profiler=1
|
|
info "Profiler mode enabled"
|
|
;;
|
|
m)
|
|
makeconfig=1
|
|
;;
|
|
y)
|
|
DOIT=1
|
|
;;
|
|
w)
|
|
showwho=1
|
|
;;
|
|
x)
|
|
MYAIR="${OPTARG}"
|
|
;;
|
|
*)
|
|
usage;
|
|
exit 1;
|
|
;;
|
|
esac
|
|
done
|
|
shift $((OPTIND - 1))
|
|
|
|
|
|
if [[ -z $MYAIR ]]; then
|
|
MYAIR=$(which myair)
|
|
if [[ $? -ne 0 ]]; then
|
|
error "Can't find pymyair executable 'myair' in path. Install it from here: https://github.com/smallsam/pymyair"
|
|
exit 1
|
|
fi
|
|
else
|
|
if [[ ! -x "$MYAIR" ]]; then
|
|
error "Specified pymyair executable '$MYAIR' not found."
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
if [[ $makeconfig -eq 1 ]]; then
|
|
gen_config
|
|
rv=$?
|
|
if [[ $rv -eq 0 ]]; then
|
|
info "A config file for your current aircon setup has been generated here:"
|
|
info " $CONFIGFILE"
|
|
info "Please review this and update as required."
|
|
fi
|
|
exit $rv
|
|
fi
|
|
|
|
if [[ -e $CONFIGFILE ]]; then
|
|
if ! load_config; then
|
|
error "Config load failed"
|
|
exit 1
|
|
fi
|
|
else
|
|
error "Config file $CONFIGFILE doesn't exist."
|
|
exit 1
|
|
fi
|
|
|
|
# ping all hosts in background to populate arp table
|
|
for x in ${devices}; do
|
|
ping -c1 -w1 -n -q $ip >/dev/null 2>&1 &
|
|
done
|
|
wait
|
|
|
|
if [[ $showwho -eq 1 ]]; then
|
|
# get max phone name length
|
|
maxlen=1
|
|
for x in ${pname[@]}; do
|
|
[[ ${#x} -gt $maxlen ]] && maxlen=$((${#x} + 3))
|
|
done
|
|
# get a list of all phones
|
|
TFORMAT="${BOLD}${UNDERLINE}%-${maxlen}s%-16s${PLAIN}\n"
|
|
HFORMAT="%-${maxlen}s${GREEN}%-16s${PLAIN}\n"
|
|
AFORMAT="%-${maxlen}s${RED}%-16s${PLAIN}\n"
|
|
echo
|
|
printf "$TFORMAT" "Person" "Availability"
|
|
for x in ${!pname[@]}; do
|
|
if canping "${pdev[$x]}"; then
|
|
thisform="$HFORMAT"
|
|
str="At home"
|
|
else
|
|
thisform="$AFORMAT"
|
|
str="Out of the house"
|
|
fi
|
|
printf "$thisform" "${pname[$x]}" "$str"
|
|
done
|
|
echo
|
|
exit 0
|
|
fi
|
|
|
|
|
|
|
|
get_aircon_info
|
|
|
|
if [[ $logmode -eq 1 ]]; then
|
|
now=$(date +'%d/%m/%Y %H:%M:%S')
|
|
if [[ ! -e $csvfile ]]; then
|
|
cp /dev/null $csvfile
|
|
echo -n "Date" >>$csvfile
|
|
for x in ${!zname[@]}; do
|
|
echo -n ",${zname[$x]}" >> $csvfile
|
|
done
|
|
echo >> $csvfile
|
|
fi
|
|
echo -n "${now}" >>$csvfile
|
|
for x in ${!zname[@]}; do
|
|
echo -n ",${ztemp[$x]}" >> $csvfile
|
|
done
|
|
echo >> $csvfile
|
|
exit 0
|
|
elif [[ $logmode -eq 2 ]]; then
|
|
now=$(date +'%d/%m/%Y %H:%M:%S')
|
|
|
|
rv=0
|
|
failedcmds=""
|
|
influx_clear
|
|
for x in ${!zname[@]}; do
|
|
influx_insert "INSERT temperature,room=${zname[$x]} value=${ztemp[$x]}"
|
|
done
|
|
|
|
for x in ${!pname[@]}; do
|
|
canping "${pdev[$x]}" && ishome=1 || ishome=0
|
|
influx_insert "INSERT attendance,person=${pname[$x]} value=$ishome"
|
|
done
|
|
[[ $airconmode == "off" ]] && pw=0 || pw=1
|
|
influx_insert "INSERT aircon running=$pw"
|
|
# 0= off
|
|
# 1=cool
|
|
# 2=heat
|
|
# 3=dry
|
|
# 4=fan/other
|
|
case "$airconmode" in
|
|
"off") modenum=0;;
|
|
"cool") modenum=1;;
|
|
"heat") modenum=2;;
|
|
"dry") modenum=3;;
|
|
*) modenum=4;;
|
|
esac
|
|
influx_insert "INSERT aircon mode=$modenum"
|
|
|
|
if [[ $influxrv -gt 0 ]]; then
|
|
echo -e "${RED}$failedcmds${PLAIN}" | sed -e 's/^/ /'
|
|
fi
|
|
exit ${rv}
|
|
fi
|
|
|
|
generate_actions
|
|
|
|
if [[ $cronmode -eq 1 ]]; then
|
|
# only show output if we are doing something
|
|
[[ $nairconcommands -ge 1 ]] && showoutput=1 || showoutput=0
|
|
else
|
|
showoutput=1
|
|
fi
|
|
|
|
|
|
if [[ $showoutput -eq 1 ]]; then
|
|
echo
|
|
show_aircon_status
|
|
echo
|
|
show_analysis
|
|
echo
|
|
show_proposed_commands
|
|
echo
|
|
fi
|
|
|
|
# Actually run the commands
|
|
if [[ $DOIT -eq 1 ]]; then
|
|
if [[ ! -f ${KILLFILE} ]]; then
|
|
run_commands
|
|
[[ $cronmode -ne 1 ]] && echo
|
|
fi
|
|
fi
|