[Tuto] Script prévision planning selon température

Vous avez créé un script LUA dont vous êtes fier, un .sh génial, un programme Python hors du commun, un Tuto, c'est ici que vous pouvez les partager.
rapinou
Messages : 119
Inscription : 17 nov. 2016, 01:44

[Tuto] Script prévision planning selon température

Message par rapinou »

Bonjour,

Je viens partager aujourd'hui mon script pour adapter automatiquement le planning chauffage à la température ambiante, et ainsi optimiser au mieux les temps de chauffe.

En effet, selon la température extérieure, au moment de passer du mode ECO au mode CONFORT (pour une programmation standard sans capteurs de mouvements), la température intérieure n'est pas forcément tous les jours la même, et donc le temps de chauffe nécessaire non plus.

Un exemple basé sur mon installation :

Le mode CONFORT est réglé par thermostat à 20°C. Le mode ECO est réglé par script à CONFORT - 3°C, soit 17°C.
Je règle une plage CONFORT le matin, le midi et le soir (quand on est à la maison).

- Avec une température extérieure de 10°C, au moment de remettre en route le mode CONFORT, ma température intérieure n'a pas eu le temps de redescendre à 17°C et se trouve vers 18/19°C. Le temps de chauffe nécessaire pour remonter à 20°C est de 10/20 minutes.

- Avec une température extérieure de 0°C, en mode ECO la température intérieure retombe à 17°C. Le temps de chauffe nécessaire pour remonter à 20°C est de 30 minutes.

Avec un planning standard, on anticipe manuellement le temps de chauffe, mais ce n'est pas optimisé pour ne chauffer que le strict nécessaire. C'est là que le script entre en action.

I - PREREQUIS

Côté Domoticz :

Créer une variable de type entier, ici Mode_planning_RDC :
Image

Les interrupteurs :
Image

- Un interrupteur de type sélecteur. Sur lequel nous allons enregistrer le planning :
Image
Image

- Un interrupteur de type sélecteur. Qui récupérera les différentes infos (absence, marche forcée, planning, variable) pour en faire le tri par script LUA et donner le bon ordre :
Image

/!\ Régler les mêmes niveaux sur les deux sélecteurs pour les modes ECO et CONFORT.

- Un interrupteur type On/Off pour allumer ou non le radiateur (ici par fil pilote).
- Une sonde température et un thermostat.

Côté Raspberry :

- Installer jq :

Code : Tout sélectionner

wget https://github.com/stedolan/jq/releases/download/jq-1.5/jq-1.5.tar.gz
tar xfvz jq-1.5.tar.gz
cd jq-1.5
./configure
make
make install
- Installer bc :

Code : Tout sélectionner

sudo apt-get install bc
II - LE SCRIPT BASH

A placer dans /home/pi/domoticz/scripts/sh/

Code : Tout sélectionner

#!/bin/bash

################################
###### Variables à éditer ######
################################

ip="192.168.0.38" # ip locale
port="8080" # port Domoticz
idx_planning="15" # idx de l'interrupteur Planning
lvl_confort="10" # Level du mode confort des interrupteurs Planning et Mode
lvl_eco="20" # Level du mode éco des interrupteurs Planning et Mode
idx_sonde="7" # idx de la sonde de température
idx_thermostat="6" # idx du thermostat
tps_chauffe_1deg="10" # Temps de chauffe nécessaire pour gagner 1°C en partant de radiateurs froids, en minutes.
tps_chauffe_2deg="20" # Temps de chauffe nécessaire pour gagner 2°C en partant de radiateurs froids, en minutes.
tps_chauffe_3deg="30" # Temps de chauffe nécessaire pour gagner 3°C en partant de radiateurs froids, en minutes.
var_mode="Mode_planning_RDC" # Nom de la variable utilisateur, préférer les underscores _ aux espaces dans le nom
idx_var_mode="5" # idx de la variable utilisateur

################################
## Fin des variables à éditer ##
################################

#echo "====================" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
# On détermine si on est un jour de semaine ou le weekend (weekend:512 / semaine:256)
# Possible d'adapter à un planning plus complexe en récupérant les valeurs "Days" en consultant dans un navigateur : http://ip:port/json.htm?type=schedules
if [ $(date +%u) -gt 5 ]
	then
	jour_sem=512
	echo_jour_sem="Weekend"
	#echo "weekend" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
else
	jour_sem=256
	echo_jour_sem="Semaine"
	#echo "semaine" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
fi
# On récupère le prochain timer sur le planning
timer=$(curl -s "http://$ip:$port/json.htm?type=schedules" | jq '[.result[] | select((.DeviceRowID | contains ('$idx_planning')) and (.Days | contains('$jour_sem')) and (.Level | contains('$lvl_confort')))]' | jq '[.[].ScheduleDate | strptime("%Y-%m-%d %H:%M:%S") | mktime]' | jq 'min')
#echo "Timestamp prochain timer: $timer" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
# On récupère la date du serveur
date_serveur=$(curl -s "http://$ip:$port/json.htm?type=command&param=getSunRiseSet" | jq -r '.ServerTime | strptime("%Y-%m-%d %H:%M:%S") | mktime')
#echo "Timestamp date serveur: $date_serveur" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
# Calcul du délai avant timer
delai=$((($timer-$date_serveur)/60))
#echo "Délai avant timer: $delai min" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
# On récupère la température intérieure
temp_int=$(curl -s "http://$ip:$port/json.htm?type=devices&rid=$idx_sonde" | jq -r '.result[].Temp | tonumber')
#echo "Température intérieure: $temp_int °C" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
# On récupère la température de consigne confort
consigne_conf=$(curl -s "http://$ip:$port/json.htm?type=devices&rid=$idx_thermostat" | jq -r '.result[].SetPoint | tonumber')
#echo "Thermostat confort: $consigne_conf °C" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
# Calcul de la différence de température
delta_temp=$(bc -l <<<"scale=2;$consigne_conf-$temp_int")
#echo "Delta température: $delta_temp °C" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
# On récupère la variable actuelle en base de données
var_init=$(curl -s "http://$ip:$port/json.htm?type=command&param=getuservariable&idx=$idx_var_mode" | jq -r '.result[].Value | tonumber')
#echo "Variable initiale: $var_init" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
# On détermine le mode de chauffe et on modifie la variable utilisateur
if [ 1 -eq `echo "${delta_temp} > 0" | bc` ] && [ $delai -le $tps_chauffe_1deg ]
	then
	curl "http://$ip:$port/json.htm?type=command&param=updateuservariable&vname=$var_mode&vtype=0&vvalue=$lvl_confort"
	if [ $var_init -ne $lvl_confort ]
		then
		echo -e "=============================================================\n$(date '+%H:%M:%S - %d-%m-%Y') - $echo_jour_sem\nDélai avant timer: $delai min\nTempérature intérieure: $temp_int °C\nDelta température: $delta_temp °C\nCondition 1: Delta > 0 et délai timer <= temps de chauffe 1°C.\nModification de la variable en level confort" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
	fi
elif [ 1 -eq `echo "${delta_temp} > 1" | bc` ] && [ $delai -le $tps_chauffe_2deg ]
	then
	curl "http://$ip:$port/json.htm?type=command&param=updateuservariable&vname=$var_mode&vtype=0&vvalue=$lvl_confort"
	if [ $var_init -ne $lvl_confort ]
		then
		echo -e "=============================================================\n$(date '+%H:%M:%S - %d-%m-%Y') - $echo_jour_sem\nDélai avant timer: $delai min\nTempérature intérieure: $temp_int °C\nDelta température: $delta_temp °C\nCondition 2: Delta > 1 et délai timer <= temps de chauffe 2°C.\nModification de la variable en level confort" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
	fi
elif [ 1 -eq `echo "${delta_temp} > 2" | bc` ] && [ $delai -le $tps_chauffe_3deg ]
	then
	curl "http://$ip:$port/json.htm?type=command&param=updateuservariable&vname=$var_mode&vtype=0&vvalue=$lvl_confort"
	if [ $var_init -ne $lvl_confort ]
		then
		echo -e "=============================================================\n$(date '+%H:%M:%S - %d-%m-%Y') - $echo_jour_sem\nDélai avant timer: $delai min\nTempérature intérieure: $temp_int °C\nDelta température: $delta_temp °C\nCondition 3: Delta > 2 et délai timer <= temps de chauffe 3°C.\nModification de la variable en level confort" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
	fi
else
	lvl_planning=$(curl -s "http://$ip:$port/json.htm?type=devices&rid=$idx_planning" | jq -r '.result[].Level | tonumber')
	curl "http://$ip:$port/json.htm?type=command&param=updateuservariable&vname=$var_mode&vtype=0&vvalue=$lvl_planning"
	if [ $var_init -ne $lvl_planning ]
		then
		echo -e "=============================================================\n$(date '+%H:%M:%S - %d-%m-%Y') - $echo_jour_sem\nAucune condition remplie: On respecte le planning" >> /home/pi/domoticz/scripts/sh/logs_planning.txt
	fi
fi
On édite les variables pour adapter à sa config.

Les "echo" ne sont là que pour le débug, à commenter ou dé-commenter selon son besoin. La sortie se fait dans un fichier nommé logs_planning.txt dans le dossier /scripts/sh/.

On donne les droits nécessaires pour l'exécution du fichier :

Code : Tout sélectionner

chmod +x planning.sh
On exécute le script chaque minute via crontab :

Code : Tout sélectionner

crontab -e

Code : Tout sélectionner

# Lancement du script Mode Planning
* * * * * /home/pi/domoticz/scripts/sh/planning.sh
/!\ Dans mon cas, "jq" posait problème lors de l'exécution du script en cron (alors qu'il fonctionnait bien en direct), j'ai dû ajouter les lignes suivantes au début du fichier crontab :

Code : Tout sélectionner

SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
III - LE SCRIPT CHAUFFAGE PRINCIPAL

Là il va falloir adapter à votre script chauffage principal.

Dans mon cas, c'est un script LUA lié à tout un tas d'interrupteurs pour avoir une config totalement paramétrable côté client.
En gros :
- Un inter général pour couper complètement ou non le chauffage.
- Un inter "marche forcée" pour forcer le mode CONFORT.
- Un inter "absence" pour forcer le mode ECO.
- Un planning.
- Un sélecteur de mode, "cœur" du système.

Mon script LUA pour l'exemple :

Code : Tout sélectionner

--------------------------------
------ Variables à éditer ------
--------------------------------

local zone = 'RDC' -- Nom de la zone de chauffe
local inter = 'Interrupteur général chauffage' -- Nom de l'interrupteur virtuel général des radiateurs de la maison
local absence = 'Absence' -- Nom de l'interrupteur virtuel d'absence
local boost = 'Marche forcée RDC' -- Nom de l'interrupteur virtuel de marche forcée
local planning = 'Planning chauffage RDC' -- Nom du sélecteur virtuel de planning chauffage
local radiateur = 'Fil pilote RDC' -- Nom de l'interrupteur virtuel d'action fil pilote
local puissance = 3500 -- Puissance du chauffage en watt
local conso = 'Consommation chauffage RDC' -- Nom du compteur virtuel de consommation électrique
local conso_id = 20 -- idx du compteur virtuel de consommation électrique
local sonde = 'RDC' -- Nom de la sonde de température
local consigne_conf = 'Thermostat RDC' -- Nom de thermostat virtuel de gestion de la température de confort
local diff_eco = 3 -- Différence de température entre les modes Confort et Eco
local selec_mode = 'Mode chauffage RDC' -- Nom du sélecteur virtuel d'indication de mode de chauffage Off/Confort/Eco
local lvl_off = 00 -- Niveaux du sélecteur virtuel de mode de chauffage
local lvl_conf = 10
local lvl_eco = 20
local tarifhp = 'Tarif HP' -- Nom de la variable utilisateur
local tarifhc = 'Tarif HC' -- Nom de la variable utilisateur
local hp_hc = 'EDF HP / HC' -- Nom du sélecteur Heures pleines/Heures creuses
local lvl_hp = 10 -- Niveaux du sélecteur HP/HC
local lvl_hc = 20
local inc_meter = 'Coût chauffage RDC' -- Nom du compteur virtuel de coût EDF
local inc_meter_id = 23 -- idx du compteur virtuel de coût EDF
local tps_confort = 'Temps Confort RDC' -- Nom de la variable utilisateur
local var_cout = 'Coût chauffage RDC' -- Nom de la variable utilisateur
local var_planning = 'Mode_planning_RDC' -- Nom de la variable utilisateur

--------------------------------
-- Fin des variables à éditer --
--------------------------------

function timedifference(d)
	s = otherdevices_lastupdate[d]
	year = string.sub(s, 1, 4)
	month = string.sub(s, 6, 7)
	day = string.sub(s, 9, 10)
	hour = string.sub(s, 12, 13)
	minutes = string.sub(s, 15, 16)
	seconds = string.sub(s, 18, 19)
	t1 = os.time()
	t2 = os.time{year=year, month=month, day=day, hour=hour, min=minutes, sec=seconds}
	difference = os.difftime (t1, t2)
	return difference
end

-- Date du dernier relevé de température
local lastSeen = string.sub(os.date("!%X", timedifference(sonde)), 1, 5)

commandArray = {}

-- Relevé de température
	local temperature = tonumber(string.sub(otherdevices_svalues[sonde],1,4))

-- Récupération de la valeur du thermostat et du planning
	consigne_conf = tonumber(otherdevices_svalues[consigne_conf])
	consigne_eco = tonumber(consigne_conf - diff_eco)
	planning = tonumber(otherdevices_svalues[planning])

-- Incrémentation de la variable Temps Confort pour les stats
	tps_confort_init = tonumber(uservariables[tps_confort])
	if planning == lvl_conf then
		tps_confort_maj = tps_confort_init + 1
	else
		tps_confort_maj = tps_confort_init
	end
	commandArray['Variable:'..tps_confort] = tostring(tps_confort_maj)

-- Modification du sélecteur de mode
	var_planning = uservariables[var_planning] -- Récupération de la variable mode planning générée par le script planning.sh
	if (otherdevices[inter] == 'Off') then -- Si l'interrupteur général est coupé :
		commandArray[selec_mode]='Set Level: '..lvl_off..'' -- On passe le sélecteur sur Off,
		mode = tonumber(lvl_off) -- Et on enregistre le niveau correspondant pour la suite du script.
	elseif (otherdevices[absence] == 'On') or (lastSeen > '00:10') then -- Si mode absence activé ou sonde muette depuis +10 min :
		commandArray[selec_mode]='Set Level: '..lvl_eco..'' -- On passe le sélecteur sur ECO,
		mode = tonumber(lvl_eco) -- Et on enregistre le niveau correspondant pour la suite du script.
	elseif (otherdevices[boost] == 'On') then -- Si marche forcée activée :
		commandArray[selec_mode]='Set Level: '..lvl_conf..'' -- On passe le sélecteur sur CONFORT,
		mode = tonumber(lvl_conf) -- Et on enregistre le niveau correspondant pour la suite du script.
	else
		commandArray[selec_mode]='Set Level: '..var_planning..'' -- Si aucune de ces conditions n'est remplie, on passe sur la variable mode planning,
		mode = tonumber(var_planning) -- Et on enregistre le niveau correspondant pour la suite du script.
		print('Level mode planning: '..var_planning) -- logs
	end

-- Récupération de la consigne à appliquer
	if mode == lvl_conf then -- Si le mode enregistré correspond au mode CONFORT :
		consigne = consigne_conf -- Consigne confort.
	else -- Sinon :
		consigne = consigne_eco -- Consigne ECO.
	end

-- Comparaison température/consigne et action fil pilote
	if (mode == lvl_off or temperature >= consigne) then
		commandArray[radiateur]='Off'
		-- Envoi de la conso dans le capteur virtuel d'énergie
		npower, energy = string.match(otherdevices_svalues[conso], "(%d+%.*%d*);(%d+%.*%d*)")
		if (otherdevices[radiateur] == 'On') then
			power = tonumber(npower - puissance)
			if (power < 0) then
				power = 0
			end
		else
			power = tonumber(npower)
		end
		energy = tonumber(energy)
	else
		commandArray[radiateur]='On'
		-- Envoi de la conso dans le capteur virtuel d'énergie
		npower, energy = string.match(otherdevices_svalues[conso], "(%d+%.*%d*);(%d+%.*%d*)")
		if (otherdevices[radiateur] == 'On') then
			power = tonumber(npower)
		else
			power = tonumber(npower + puissance)
		end
		energy = tonumber(energy + (puissance / 60))
	end

-- Mise à jour du compteur conso
	commandArray[1]={['UpdateDevice'] = conso_id .. '|0|' .. power .. ';' .. energy}

-- Calcul du coût EDF
	if (otherdevices[radiateur] == 'On') then
		instant_energy = tonumber(puissance / 60) -- Consommation instantanée
	else
		instant_energy = 0
	end
	
	hp_hc = tonumber(otherdevices_svalues[hp_hc]) -- Récupération du planning HP/HC
	tarifhp = tonumber(uservariables[tarifhp]) -- Récupération des tarifs HP/HC
	tarifhc = tonumber(uservariables[tarifhc])

	if hp_hc == lvl_hp then
		instant_cout = instant_energy * tarifhp / 1000
	elseif hp_hc == lvl_hc then
		instant_cout = instant_energy * tarifhc / 1000
	end

	compteur_init = tonumber(otherdevices_svalues[inc_meter]) -- Récupération de la valeur initiale du compteur

	compteur_maj = compteur_init + instant_cout -- On ajoute la conso instantanée au compteur

-- Mise à jour compteur tarif
	commandArray[2]={['UpdateDevice'] = inc_meter_id .. '|0|' .. compteur_maj}

-- Mise à jour variable tarif pour stats quotidiennes
	var_cout_init = tonumber(uservariables[var_cout]) -- Récupération de la valeur initiale de la variable
	var_cout_maj =  var_cout_init + instant_cout -- Ajout du coût instantanée
	commandArray['Variable:'..var_cout] = tostring(var_cout_maj) -- Mise à jour de la variable

-- Logs
	print('Température '..zone..': '..temperature..' °C / Consigne : '..consigne..' °C')
	print('Coût chauffage '..zone..': '..compteur_maj..' €')

return commandArray
C'est un peu une usine à gaz, mais la partie qui nous intéresse est ici :

Code : Tout sélectionner

-- Modification du sélecteur de mode
	var_planning = uservariables[var_planning] -- Récupération de la variable mode planning générée par le script planning.sh
	if (otherdevices[inter] == 'Off') then -- Si l'interrupteur général est coupé :
		commandArray[selec_mode]='Set Level: '..lvl_off..'' -- On passe le sélecteur sur Off,
		mode = tonumber(lvl_off) -- Et on enregistre le niveau correspondant pour la suite du script.
	elseif (otherdevices[absence] == 'On') or (lastSeen > '00:10') then -- Si mode absence activé ou sonde muette depuis +10 min :
		commandArray[selec_mode]='Set Level: '..lvl_eco..'' -- On passe le sélecteur sur ECO,
		mode = tonumber(lvl_eco) -- Et on enregistre le niveau correspondant pour la suite du script.
	elseif (otherdevices[boost] == 'On') then -- Si marche forcée activée :
		commandArray[selec_mode]='Set Level: '..lvl_conf..'' -- On passe le sélecteur sur CONFORT,
		mode = tonumber(lvl_conf) -- Et on enregistre le niveau correspondant pour la suite du script.
	else
		commandArray[selec_mode]='Set Level: '..var_planning..'' -- Si aucune de ces conditions n'est remplie, on passe sur la variable mode planning,
		mode = tonumber(var_planning) -- Et on enregistre le niveau correspondant pour la suite du script.
		print('Level mode planning: '..var_planning) -- logs
	end

-- Récupération de la consigne à appliquer
	if mode == lvl_conf then -- Si le mode enregistré correspond au mode CONFORT :
		consigne = consigne_conf -- Consigne confort.
	else -- Sinon :
		consigne = consigne_eco -- Consigne ECO.
	end
Quelques tests pour vérifier que tout fonctionne correctement.
Pour ma part, j'ai attendu avant de modifier mon script LUA que tout fonctionne correctement. J'ai fait tourner le script BASH quelque temps en modifiant les valeurs de thermostat et de planning pour vérifier que le fichier logs renvoie systématiquement les bonnes valeurs.

Et maintenant le réglage du planning se fait selon les heures de présence, plus besoin de programmer des temps de chauffe préalables, le script le fera automatiquement selon la température ambiante.

J'espère avoir été assez clair dans mes explications, et n'avoir rien oublié.

J'en profite pour remercier l'équipe du forum et ses membres pour toute l'aide qu'ils nous apportent quotidiennement.

Et merci à vil1driver pour son fameux script chauffage qui sert de base à tout le monde ^^
RPI 3 - Raspbian 8 (Jessie) - Domoticz V3.5877
rapinou
Messages : 119
Inscription : 17 nov. 2016, 01:44

Re: Script prévision planning selon température

Message par rapinou »

Mise à jour du script et du tuto.

Vu que j'ai planté mon Pi et que je viens de refaire l'install, j'ai remarqué que la méthode d'installation de "jq" ne convenait pas, j'ai repris celle indiquée sur le blog. J'ai ajouté aussi l'install de "bc". Et optimisation du script.

Note à moi-même : Faire des sauvegardes régulières pour éviter de se retaper toute l'installation si on plante tout :D
RPI 3 - Raspbian 8 (Jessie) - Domoticz V3.5877
Répondre