Tips en Trucs 2020

Thermostaat met timer

Bash is ideaal om scripts te schrijven. Het heeft niet de mogelijkheden van een programmeertaal zoals python, maar kan mits het gebruik van zijn uitgebreide mogelijkheden en flexibel denken toch gebruikt worden om te programmeren.

Vandaag schrijven we een script waarbij we uitgebreid gebruik maken van array's (verzameling variabelen) om een thermostaat op basis van een weekplanning te maken.

Het script zelf bevat uitgebreide commentaar om de volgende mogelijkheden van bash in de verf te zetten:

Dit script is een vereenvoudiging van een thermostaatregeling in ontwikkeling. Het werd dus vereenvoudigd, van uitgebreidere commentaar voorzien en daardoor niet helemaal uitgewerkt. Maar het toont wel goed hoe je met array's kunt werken en een volledig programma in bash kunt schrijven. Misschien een basis voor een eigen ontwerp.

Het script in werking

Om het script te testen, kan je verschillende variabelen aanpassen.

dany@pindabook:~> sh timerThermostat.sh 
Script draait in testmode en toont debug informatie.
Reset thermostaat
Indien niet alle radiatoren werden aangestuurd, verschijnen rm foutmeldingen.
rm: kan '/tmp/0B1100014153860280' niet verwijderen: Bestand of map bestaat niet
6 07:20 22:45
awake
07:20 08:30 22.00
11:00 13:30 22.20
16:45 17:30 22.20
22:15 22:45 22.20
10:44
Keuken niet verwarmen
Uit te voeren Tasmota opdracht: wget -q http://tasmota_a943fa-1018/cm?cmnd=Power%20Off
Kamertemp: 21.78 °C
Thermostaat temp: 22.00
Zuid aan bij 21.9 °C.
Noord aan bij 21.7 °C.
Noord uit bij 21.9 °C.
Zuid uit bij 22.1 °C.
Livingverwarming Zuid inschakelen
Stuur RF Code: 0B1100010141538601010F80
Bash termostaat

Het script

#!/bin/bash

# Testmode uitschakelen door de volgende regel in commentaar te plaatsen
debug=1
if [ ! -z $debug ]; then
  echo "Script draait in testmode en toont debug informatie."
  # De timer en thermostaat elke keer resetten
  touch timerThermostat/thermostatReset
fi

# Hier initialiseren we arrays
# Timer array met standaard timergegevens
# dag van de week - starttijd - stoptijd
timerdefault[0]="0 07:20 22:45"
timerdefault[1]="2 07:20 22:45"
timerdefault[2]="4 07:20 22:45"
timerdefault[3]="6 07:20 22:45"
timerdefault[4]="1 07:20 22:45"
timerdefault[5]="3 07:20 22:45"
timerdefault[6]="5 07:20 22:45"
# De volgende items staan niet chronologisch
timerdefault[7]="0 16:30 17:00"
timerdefault[8]="0 17:30 22:45"

# Thermostaat array met standaard gegevens voor de keuken
# Starttijd - stoptijd - temperatuur 
thermostatkitchendefault[0]="07:20 08:30 22.00"
thermostatkitchendefault[2]="11:00 13:30 22.20"
thermostatkitchendefault[1]="22:15 22:45 22.20"
thermostatkitchendefault[3]="16:45 17:30 22.20"
# Thermostaat array met standaard gegevens voor de woonkamer
thermostatlivingdefault[0]="08:10 11:15 22.00"
thermostatlivingdefault[2]="13:15 17:00 22.00"
thermostatlivingdefault[1]="17:15 22:45 22.00"
# Het volgende item staat niet chronologisch
thermostatlivingdefault[3]="16:45 17:15 22.00"

# Waar worden de arrays bewaard
if [ ! -d timerThermostat ]; then
  mkdir timerThermostat
fi
timerfile="timerThermostat/timer"
thermostatkitchenfile="timerThermostat/thermostatkitchen"
thermostatlivingfile="timerThermostat/thermostatliving"
# Luchtdruk, Vochtigheid en Temperatuur afkomstig van een sensor
PresHumiTempfile="timerThermostat/PresHumiTemp"
# Indien geen sensor aanwezig, zelf gegevens aanmaken als voorbeeld
if [ ! -f $PresHumiTempfile ]; then
  echo " 998.61 hPa" > $PresHumiTempfile
  echo "  51.58 %" >> $PresHumiTempfile
  echo "  21.78 C" >> $PresHumiTempfile
fi

# Initialiseren Associatieve arrays (namen als index) met de verschillende radiators
declare -A heater
heater[LivingZuidOn]="0B1100010141538601010F80 on"
heater[LivingZuidOff]="0B1100000141538601000080 off"
heater[LivingNoordOn]="0B1100000141538602010F80 on"
heater[LivingNoordOff]="0B1100010141538602000080 off"
heater[KeukenZuidOn]="tasmota_a943fa-1018 on"
heater[KeukenZuidOff]="tasmota_a943fa-1018 off"

# Temperatuurschommeling tussen aan- en uitschakelen radiator
hysteresis="0.1"

if [ -f timerThermostat/thermostatDefault ]; then
  echo "Herstel standaard thermostaat"
  rm $timerfile
  rm $thermostatkitchenfile
  rm $thermostatlivingfile
  # In testmode elke keer Standaard thermostaat gebruiken
  if [ -z $debug ]; then
    rm timerThermostat/thermostatDefault
  fi
fi

if [ -f timerThermostat/thermostatReset ]; then
  echo "Reset thermostaat"
  echo "Indien niet alle radiatoren werden aangestuurd, verschijnen rm foutmeldingen."
  for switch in "${!heater[@]}"
  do
   if [[ "$switch" == *Off ]]; then
     heateritem=${heater[$switch]}
     if [[ "$heateritem" == tasmota* ]]; then
        tempfile="/tmp/${heateritem:0:19}"
     else
        tempfile="/tmp/${heateritem:0:6}${heateritem:8:10}${heateritem:22:2}"
     fi
     rm $tempfile
   fi
  done
  # In testmode elke keer de thermostaat resetten en Manueel ingestelde temperatuur gebruiken
  if [ -z $debug ]; then
    rm /var/www/html/data/thermostatManual
    rm /var/www/html/data/thermostatReset
  fi
fi

# Functie om een Radiografische KlikAanUit Radiator in- of uit te schakelen
sendRF () {
  # Radiator eenduidig uit RF code filteren
  tempfile="/tmp/${1:0:6}${1:8:10}${1:22:2}"
   # indien nodig de status van de radiator initialiseren
  if [ ! -f "$tempfile" ]; then
    if [ "$2" == "off" ]; then
      echo 'on' > "$tempfile"
    else
      echo 'off' > "$tempfile"
    fi
  fi
  # De radiator enkel aansturen indien dit nodig is
  if [ $(cat "$tempfile") == "off" ] && [ "$2" == "on" ]; then
    if [ -f /home/pi/rfxcmd_gc-master/rfxcmd.py ]; then
      python /home/pi/rfxcmd_gc-master/rfxcmd.py -d /dev/ttyUSB0 -s "$1"
    else
      echo "Stuur RF Code: $1"
    fi
    # Bewaar de status van de radiator
    echo 'on' > "$tempfile"
  elif [ $(cat "$tempfile") == "on" ] && [ "$2" == "off" ]; then
    if [ -f /home/pi/rfxcmd_gc-master/rfxcmd.py ]; then
      python /home/pi/rfxcmd_gc-master/rfxcmd.py -d /dev/ttyUSB0 -s "$1"
    else
      echo "Stuur RF Code: $1"
    fi
    echo 'off' > "$tempfile"
  fi
}

# Functie om een WiFi Tasmota Radiator in- of uit te schakelen
tasmota () {
   # indien nodig de status van de radiator initialiseren
  if [ ! -f /tmp/$1 ]; then
    echo "Uit te voeren Tasmota opdracht: wget -q http://$1/cm?cmnd=Power%20Off"
    echo '{"POWER":"OFF"}' > /tmp/$1
  fi
  # De radiator enkel aansturen indien dit nodig is
  if [ $2 == "on" ] && [ $(cat /tmp/$1) == '{"POWER":"OFF"}' ]; then
    echo "Uit te voeren Tasmota opdracht: wget -q http://$1/cm?cmnd=Power%20On"
    # Bewaar de status van de radiator
    echo '{"POWER":"ON"}' > /tmp/$1
    #echo $(wget -qO- http://$1/cm?cmnd=Power) > /tmp/$1
  elif [ $2 == "off" ] && [ $(cat /tmp/$1) == '{"POWER":"ON"}' ]; then
    echo "Uit te voeren Tasmota opdracht: wget -q http://$1/cm?cmnd=Power%20Off"
    echo '{"POWER":"OFF"}' > /tmp/$1
    #echo $(wget -qO- http://$1/cm?cmnd=Power) > /tmp/$1
  fi
}
# Functie om alle radiators uit te schakelen
function thermostatOff {
  # Overloop alle radiator waarden
  for switch in "${!heater[@]}"
  do
   # Enkel de indexnamen die eindigen op Off zijn van belang
   if [[ "$switch" == *Off ]]; then
     # Hoe wordt de radiator aangestuurd, via Tasmota of Radiografische
     if [[ "${heater[$switch]}" == tasmota* ]]; then
       tasmota ${heater[$switch]}
     else
       sendRF ${heater[$switch]}
     fi
   fi
  done
}
# Functie die de radiators aanstuurd
function thermostat {
  temp=$(tail -1 $PresHumiTempfile)
  temp=${temp%% C*}
  # Verwijder eventuele witruimte voor de temperatuur
  temp="${temp#"${temp%%[![:space:]]*}"}"

  # Gebruik indien mogelijk de thermostaat voor de keuken opgeslagen in een bestand, anders de standaard keuken thermostaat opgeslagen in de array thermostatkitchendefault
  if [ ! -f $thermostatkitchenfile ]; then
    # Sla de standaard thermostaat op
    printf "%s\n" "${thermostatkitchendefault[@]}" > $thermostatkitchenfile
  fi
  # Lees de thermostaat voor de keuken in
  mapfile -t raw < $thermostatkitchenfile
  # Sorteer de thermostaatgegevens chronologisch
  IFS=$'\n' thermostatkitchen=($(sort <<<"${raw[*]}"))
  # Verwijder tijdelijke hulpvariabelen
  unset IFS
  unset raw
  if [ ! -z $debug ]; then
    printf "%s\n" "${thermostatkitchen[@]}"
    echo $now
  fi

  # Verwarming wordt standaard uitgeschakeld
  heating="off"
  # Controleer of nu valt tussen een thermostaat start- en stoptijd
  for thermostatitem in "${thermostatkitchen[@]}"; do
    daytime=(${thermostatitem})
    if [[ "${daytime[0]}" < "$now" ]] && [[ "${daytime[1]}" > "$now" ]]; then
      heating="on"
      break
    fi
  done
  # De variable daytime[2] bevat de in de thermostaat ingestelde temperatuur
  # Controleer of er manueel een temperatuur werd ingesteld
  if [ -f timerThermostat/thermostatManual ]; then
    mapfile -t manual < timerThermostat/thermostatManual
    for manualitem in "${manual[@]}"; do
      roomtemp=(${manualitem})
       if [ "${roomtemp[0]}" == "kitchen" ]; then
         daytime[2]=${roomtemp[1]}
         echo "Manual temp kichen: ${daytime[2]} °C"
         heating="on"
        break
      fi
    done
  fi
  # Enkel als de verwarming geregeld moet worden (via thermostaat of manueel)
  if [ "$heating" == "on" ]; then
    # Is de kamertemperatuur hoger of lager dan de ingestelde temperatuur, rekening houdend met de hysteresis
    if (( $(awk "BEGIN {print ($temp < ${daytime[2]} - $hysteresis)}") )); then
      echo "Keukenverwarming inschakelen"
      tasmota ${heater[KeukenZuidOn]}
    elif (( $(awk "BEGIN {print ($temp > ${daytime[2]} + $hysteresis)}") )); then
      echo "Keukenverwarming uitschakelen"
      tasmota ${heater[KeukenZuidOff]}
    fi
  else # Op dit moment wordt de keuken niet verwarmd.
    echo "Keuken niet verwarmen"
    tasmota ${heater[KeukenZuidOff]}
  fi
  # Iets complexere regeling voor de woonkamer met twee radiators
  # Gebruik de standaard of opgeslagen thermostaat
  if [ ! -f $thermostatlivingfile ]; then
    printf "%s\n" "${thermostatlivingdefault[@]}" > $thermostatlivingfile
  fi
  mapfile -t raw < $thermostatlivingfile

  IFS=$'\n' thermostatliving=($(sort <<<"${raw[*]}"))
  unset IFS
  unset raw

  heating="off"
  for thermostatitem in "${thermostatliving[@]}"; do
    daytime=(${thermostatitem})
    if [[ "${daytime[0]}" < "$now" ]] && [[ "${daytime[1]}" > "$now" ]]; then
      heating="on"
      break
    fi
  done
  if [ "$heating" == "on" ]; then
    if [ ! -z $debug ]; then
      echo "Kamertemp: $temp °C"
      echo "Thermostaat temp: ${daytime[2]}"
      echo "Zuid aan bij" $(awk "BEGIN {print (${daytime[2]} - $hysteresis)}") "°C."
      echo "Noord aan bij" $(awk "BEGIN {print (${daytime[2]} - $hysteresis - $hysteresis * 2)}") "°C."
      echo "Noord uit bij" $(awk "BEGIN {print (${daytime[2]} + $hysteresis - $hysteresis * 2)}") "°C."
      echo "Zuid uit bij" $(awk "BEGIN {print (${daytime[2]} + $hysteresis)}") "°C."
    fi
    if (( $(awk "BEGIN {print ($temp < ${daytime[2]} - $hysteresis)}") )); then
      echo "Livingverwarming Zuid inschakelen"
      sendRF ${heater[LivingZuidOn]}
      # Indien de ingestelde temperatuur te laag is gezakt, wordt de tweede radiator ingeschakeld
      if (( $(awk "BEGIN {print ($temp < ${daytime[2]} - $hysteresis - $hysteresis * 2)}") )); then
        echo "Livingverwarming Noord inschakelen"
        sendRF ${heater[LivingNoordOn]}
      fi
    elif (( $(awk "BEGIN {print ($temp > ${daytime[2]} + $hysteresis - $hysteresis * 2)}") )); then
      # Als de ingestelde temperatuur bijna is bereikt, wordt de tweede radiator uitgeschakeld
      echo "Livingverwarming Noord uitschakelen"
      sendRF ${heater[LivingNoordOff]}
      # Bij het licht overschrijden van de ingestelde temperatuur wordt ook de eerste radiator uitgeschakeld
      if (( $(awk "BEGIN {print ($temp > ${daytime[2]} + $hysteresis)}") )); then
        echo "Livingverwarming Zuid uitschakelen"
        sendRF ${heater[LivingZuidOff]}
      fi
    fi
  else # Op dit moment wordt de woonkamer niet verwarmd.
    echo "Living niet verwarmen"
    sendRF ${heater[LivingZuidOff]}
    sendRF ${heater[LivingNoordOff]}
  fi
}

# Gebruik een wekenplanning om de thermostaat aan te sturen
if [ ! -f $timerfile ]; then # default
  printf "%s\n" "${timerdefault[@]}" > $timerfile
fi
mapfile -t raw < $timerfile

IFS=$'\n' timer=($(sort <<<"${raw[*]}"))
unset IFS
unset raw

# Standaard werkt de thermostaat niet (slaaptoestand)
state="sleep"
# Bepaal de weekdag en tijdstip van het moment
weekday=$(date +%w)
now=$(date +%H:%M)
# Doorloop de timergegevens
for timeritem in "${timer[@]}"; do
  daytime=(${timeritem})
  # Timeritem voor vandaag gevonden
  if [ "${daytime[0]}" == "$weekday" ]; then
    echo "$timeritem"
    # Moet de thermostaat geactiveerd worden (wakker worden)
    if [[ "${daytime[1]}" < "$now" ]] && [[ "${daytime[2]}" > "$now" ]]; then
      state="awake"
      break
    fi
  fi
done

echo $state
if [ $state == "awake" ]; then
  # Verwarming regelen
  thermostat
else
  # Verwarming uitschakelen
  thermostatOff
fi