Autre matériel de brassageDIY : Contrôleur de température à base de raspberrypi

Le fourre tout
Règles du forum
Chers membres, merci de prendre connaissance et respecter les quelques règles de bon sens suivantes avant de poster votre message :
- Vous assurer que vous postez dans la bonne rubrique
- Vérifier qu'il n’existe pas déjà une réponse à votre question ou un sujet identique
- Prendre conscience que vos propos n’engagent que vous et que vous devrez en assumer la paternité
- Vérifier les sources des informations que vous diffusez, en vous assurant le cas échéant de respecter les droits d’auteur qui peuvent être liés aux informations, images ou documents cités
- Prendre soin de respecter vos interlocuteurs et bannir les insultes et autres propos diffamatoires ou dégradants
- Vous assurer de rester autant que faire se peut dans le sujet exposé
- Prendre le temps de vérifier l’orthographe et la grammaire de votre message
Merci par avance de votre contribution à préserver le bon esprit de ce forum.
Madef
Ch'ti nouveau
Messages : 29
Inscrit depuis : 5 ans 1 mois
Mon équipement : Cuve d’ébullitions / de chauffe MegaPot 15 gallons
2 cuves d'empâtage Fermenter's Favorites de 10 gallons dont une avec fond filtrant
Plaque induction 3500W
Fermenteur PET avec robinet Big Mouth Bubbler 6,5 gallons (x3)
Brasseur : Amateur
Localisation : 22 rue des Azalees, 92230 Gennevilliers
A remercié : 4 fois
A été remercié : 3 fois

DIY : Contrôleur de température à base de raspberrypi

Message par Madef »

Bonjour à tous,


Afin de contrôler la température durant la fermentation, j'ai mis en place un contrôleur maison.

Pour cela j'ai utilisé un Raspberry pi zéro W, un élément chauffant pour terrarium, un vieux réfrigérateur, une sonde de température, un module relais double, une résistance de 4,7kOhm, une boite de dérivation pour contenir le tout, du fil électrique, deux prises femelles et une mâle, et un nécessaire à souder.
Je n'ai pas procédé à des modifications du réfrigérateur pour le moment car je l'utilise occasionnellement pour autre chose. Je prévoie d'acheter un réfrigérateur dédié au brassage.

Pour la commande de température, c'est un script python qui tourne toutes les minutes et qui est commandé par un site distant. Cela présente un avantage : l'interface est commandable depuis n'importe où. En contre partie le système est dépendant d'internet. Cette partie peut être facilement changée. À l'avenir je rajouterai certainement une commande sur l'appareil.

Si j'ai choisi ce système, plutôt qu'un système tout fait, c'est plus pour des raison d'amusement que de coût. Le DIY doit être légèrement moins cher mais de peu. Le rendu est forcément plus amateur aussi.


Voici donc le schéma électronique :
Image

Voici le schéma électrique (fait à la main désolé) :
Image

Voici la photo du contrôleur ouvert :
Image

Et la photo du tout installé :
Image

La capteur qui pour le moment est "à l’arrache" dans le frigo :
Image


Pour le code voici le script python (script.py) :

Code : Tout sélectionner

#!/usr/bin/env python3

import RPi.GPIO as GPIO
import requests
import time
import json
import conf
import datetime

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(conf.OUTPUT_COOL, GPIO.OUT)
GPIO.setup(conf.OUTPUT_HEAT, GPIO.OUT)

now = str(datetime.datetime.now())

# Fonction : Retourner la temperature actuelle et ordonner ou non son enregistrement
def getTemp(sensorId):
    # On ouvre le fichier de la sonde. Remplacer les X par le numero de la sonde
    fichSonde = open("/sys/bus/w1/devices/"+sensorId+"/w1_slave")
    contenu = fichSonde.read()
    fichSonde.close()

    # On 'nettoie' le contenu pour ne recuperer que la temperature
    secondeLigne = contenu.split("\n")[1]
    temperatureData = secondeLigne.split(" ")[9]
    temperature = float(temperatureData[2:])
    temperature = temperature / 1000
    return temperature

# GPIO.output(conf.OUTPUT_COOL, 1)
# GPIO.output(conf.OUTPUT_HEAT, 1)


temp = getTemp(conf.SENSOR)

#requests.get(conf.SENSOR_URL + "?sensor=" + conf.SENSOR_NAME + "&temp="+str(temp))

result = requests.get(conf.TEMP_CONTROLLER_URL + "?name=" + conf.SENSOR_NAME + "&token=" + conf.SENSOR_TOKEN).json()

#print ('Min: '+str(result['min']))
#print ('Max: '+str(result['max']))
#print ('Actual: '+str(temp))

if temp > (result['max']):
    GPIO.output(conf.OUTPUT_HEAT, 1)
    GPIO.output(conf.OUTPUT_COOL, 0)
    # print ('cool')
    print (str(result['min'])+','+str(result['max'])+','+str(temp)+','+now+',cool')
elif temp < (result['min']):
    GPIO.output(conf.OUTPUT_HEAT, 0)
    GPIO.output(conf.OUTPUT_COOL, 1)
    # print ('heat')
    print (str(result['min'])+','+str(result['max'])+','+str(temp)+','+now+',heat')
else:
    GPIO.output(conf.OUTPUT_HEAT, 0)
    GPIO.output(conf.OUTPUT_COOL, 0)
    print (str(result['min'])+','+str(result['max'])+','+str(temp)+','+now+',none')

Et la fichier de configuration (conf.py) :

Code : Tout sélectionner

#!/usr/bin/env python3

SENSOR = "28-021091777367"
OUTPUT_HEAT = 27
OUTPUT_COOL = 17
TEMP_CONTROLLER_URL = "http://domain.com/get.php"
SENSOR_URL = "http://domain.com/put.php"
SENSOR_NAME = 'frigo1'
SENSOR_TOKEN = 'nimportequoi'
Et il faut sur une serveur rajouter get.php et put.php et index.html:

Code : Tout sélectionner

<?php

$errors = [];
$errorCode = 0;
$db = new SQLite3('mysqlitedb.db');
$db->exec('CREATE TABLE rule (name TEXT, token TEXT, min REAL, max REAL)');

if (!isset($_GET['name'])) {
    $errors[] = 'Param name is missing.';
} else {
    $name = SQLite3::escapeString($_GET['name']);
    if ($name === '') {
        $errors[] = 'Param name cannot be empty.';
    }
}

if (!isset($_GET['token'])) {
    $errors[] = 'Param token is missing.';
} else {
    $token = SQLite3::escapeString($_GET['token']);
    if ($token === '') {
        $errors[] = 'Param token cannot be empty.';
    }
    $hash = md5($token);
}

if (!count($errors)) {
    // Check token if name exists
    $sql = "SELECT min, max FROM rule WHERE name = '$name' AND token = '$hash'";
    $result = $db->query($sql);
    $result = $result->fetchArray();

    if (!$result) {
        $errors[] = 'Rule not found.';
    }
}

if (count($errors)) {
    $result = [
        'status' => 'error',
        'errors' => $errors,
    ];
} else {
    $result = [
        'status' => 'success',
        'min' => (float) $result['min'],
        'max' => (float) $result['max'],
    ];
}

header('Content-Type: application/json');
echo json_encode($result);

Code : Tout sélectionner

<?php
$errors = [];
$db = new SQLite3('mysqlitedb.db');
$db->exec('CREATE TABLE rule (name TEXT, token TEXT, min REAL, max REAL)');
if (!isset($_GET['name'])) {
    $errors[] = 'Param name is missing.';
} else {
    $name = SQLite3::escapeString($_GET['name']);
    if ($name === '') {
        $errors[] = 'Param name cannot be empty.';
    }
}
if (!isset($_GET['token'])) {
    $errors[] = 'Param token is missing.';
} else {
    $token = SQLite3::escapeString($_GET['token']);
    if ($token === '') {
        $errors[] = 'Param token cannot be empty.';
    }
    $hash = md5($token);
}
if (!isset($_GET['min'])) {
    $errors[] = 'Param min is missing.';
} else {
    $min = $_GET['min'];
    if ($min === '') {
        $errors[] = 'Param min cannot be empty.';
    } elseif ($min != (string) (float)$min) {
        $errors[] = 'Param min must be a float.';
    }
}
if (!isset($_GET['max'])) {
    $errors[] = 'Param max is missing.';
} else {
    $max = $_GET['max'];
    if ($max === '') {
        $errors[] = 'Param max cannot be empty.';
    } elseif ($max != (string) (float)$max) {
        $errors[] = 'Param max must be a float.';
    }
}
if (!count($errors)) {
    // Check token if name exists
    $sql = "SELECT token FROM rule WHERE name = '$name'";
    $result = $db->query($sql);
    $result = $result->fetchArray();
    if ($result) {
        if ($result['token'] != $hash) {
            $errors[] = 'Invalid token.';
        }
    }
}
if (!count($errors)) {
    $db->exec("DELETE FROM rule where name = '$name'");
    $db->exec("INSERT INTO rule (name, token, min, max) VALUES ('$name', '$hash', $min, $max)");
}
if (count($errors)) {
    $result = [
        'status' => 'error',
        'errors' => $errors,
    ];
} else {
    $result = [
        'status' => 'success',
    ];
}
header('Content-Type: application/json');
echo json_encode($result);

Code : Tout sélectionner

<html>
    <head>
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
        <meta name="viewport" content="width=device-width, initial-scale=1">

        <link rel="icon" href="https://blog.labierequiflotte.fr/wp-content/uploads/2019/01/cropped-favicon-geant-32x32.png" sizes="32x32" />
        <link rel="icon" href="https://blog.labierequiflotte.fr/wp-content/uploads/2019/01/cropped-favicon-geant-192x192.png" sizes="192x192" />
        <link rel="apple-touch-icon-precomposed" href="https://blog.labierequiflotte.fr/wp-content/uploads/2019/01/cropped-favicon-geant-180x180.png" />
        <meta name="msapplication-TileImage" content="https://blog.labierequiflotte.fr/wp-content/uploads/2019/01/cropped-favicon-geant-270x270.png" />
    </head>
    <body style="margin: auto; padding-top: 5rem;">
        <nav class="navbar navbar-dark bg-primary fixed-top">
            <a class="navbar-brand" href="#">La Bière Qui Flotte - Temp Controller</a>
        </nav>
        <div class="container-fluid">
            <div class="row justify-content-md-center">
                <div class="col-md-8 col">
                    <form method="GET" style="" id="form-login">
                        <div class="js-alert"></div>
                        <div class="form-row">
                            <div class="col">
                                <input class="form-control js-name" type="text" name="name" value="" placeholder="Name"/>
                            </div>
                            <div class="col">
                                <input class="form-control js-token" type="text" name="token" value="" placeholder="Token"/>
                            </div>
                            <div class="col">
                                <input class="form-control btn btn-primary" type="submit" value="Change"/>
                            </div>
                        </div>
                    </form>
                </div>
            </div>
            <div class="row justify-content-md-center">
                <div class="col-md-8 col">
                    <form method="GET" style="" id="form-controller">
                        <div class="form-row">
                            <div class="col">
                                <input type="hidden" name="name" class="js-name"/>
                                <input type="hidden" name="token" class="js-token"/>
                                <input class="form-control js-min" type="float" name="min" value="" id="min" placeholder="Min (°C)"/>
                            </div>
                            <div class="col">
                                <input class="form-control js-max" type="float" name="max" value="" id="max" placeholder="Max (°C)"/>
                            </div>
                            <div class="col">
                                <input class="form-control btn btn-primary" type="submit" value="Update"/>
                            </div>
                        </div>
                        <div style="margin-top: 1em;" class="js-alert"></div>
                    </form>
                </div>
            </div>
        </div>
        <script
            src="https://code.jquery.com/jquery-3.3.1.min.js"
            integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
            crossorigin="anonymous"
        ></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/js/bootstrap.min.js" integrity="sha384-ChfqqxuZUCnJSK3+MXmPNIyE6ZbWh2IMqE241rYiqJxyMiZ6OW/JmZQ5stwEULTy" crossorigin="anonymous"></script>
        <script type="text/javascript">
            jQuery(function() {
                jQuery('#form-controller').hide();

                jQuery('#form-login').submit(function() {
                    jQuery.ajax({
                        url: '/get.php',
                        data: jQuery('#form-login').serialize(),
                        dataType: 'json'
                    }).done(function(data) {
                        jQuery('#form-login .js-alert').text('');
                        switch (data.status) {
                            case 'error':
                                jQuery('#form-controller').hide();
                                for (var i in data.errors) {
                                    var error = data.errors[i];

                                    jQuery('#form-login .js-alert').append(
                                        '<div class="alert alert-danger alert-dismissible fade show" role="alert">'
                                            + error
                                            + '<button type="button" class="close" data-dismiss="alert" aria-label="Close">'
                                                + '<span aria-hidden="true">&times;</span>'
                                            + '</button>'
                                            + '</div>'
                                    );
                                }
                                break;
                            case 'success':
                                console.log(data);
                                jQuery('#form-controller .js-name').val(jQuery('#form-login .js-name').val());
                                jQuery('#form-controller .js-token').val(jQuery('#form-login .js-token').val());
                                jQuery('#form-controller .js-min').val(data.min);
                                jQuery('#form-controller .js-max').val(data.max);
                                jQuery('#form-controller').show();
                                break;
                        }
                    });

                    return false;
                });

                jQuery('#form-controller').submit(function() {
                    jQuery.ajax({
                        url: '/put.php',
                        data: jQuery('#form-controller').serialize(),
                        dataType: 'json'
                    }).done(function(data) {
                        jQuery('#form-controller .js-alert').text('');
                        switch (data.status) {
                            case 'error':
                                for (var i in data.errors) {
                                    var error = data.errors[i];

                                    jQuery('#form-controller .js-alert').append(
                                        '<div class="alert alert-danger alert-dismissible fade show" role="alert">'
                                            + error
                                            + '<button type="button" class="close" data-dismiss="alert" aria-label="Close">'
                                                + '<span aria-hidden="true">&times;</span>'
                                            + '</button>'
                                            + '</div>'
                                    );
                                }
                                break;
                            case 'success':
                                jQuery('#form-controller .js-alert').append(
                                        '<div class="alert alert-success alert-dismissible fade show" role="alert">'
                                            + 'Success'
                                            + '<button type="button" class="close" data-dismiss="alert" aria-label="Close">'
                                                + '<span aria-hidden="true">&times;</span>'
                                            + '</button>'
                                            + '</div>'
                                );
                                console.log(data);
                                break;
                        }
                    });

                    return false;
                });
            });
        </script>
    </body>
</html>

Enfin il faut rajouter dans le cron un appel toute les minutes du script :

Code : Tout sélectionner

* * * * * python3 script.py
Dès que j'aurai du temps, j'améliorerai le script pour ne plus le faire dépendre d'internet. Aussi pourquoi par y rajouter une commande manuelle (écran + 4 boutons).

Pour le moment je log les actions du script et les éléments sont presque tout le temps éteint (ma cave est à 18 et je demande une température entre 18 et 20).

Voici la lien vers les fp amazon :
* pack de résistances :
* relais :
* prise usb :
* sonde :
*
* plaque chauffante :
Avatar de l’utilisateur
Stef26
Ch'ti nouveau
Messages : 52
Inscrit depuis : 5 ans 6 mois
Mon équipement : Grainfather
Grainfather sparge water heater
Ispindle (x3)
Seau plastique pour la fermentation
Brasseur : Amateur
Localisation : Valence
A remercié : 2 fois
A été remercié : 1 fois

Re: DIY : Contrôleur de température à base de raspberrypi

Message par Stef26 »

Bien cool ce diy. Prévois si ce n'est pas déjà fait, une temporisation pour empêcher le groupe de démarrer trop souvent. Je testerais un de ces jours ce montage.
Madef
Ch'ti nouveau
Messages : 29
Inscrit depuis : 5 ans 1 mois
Mon équipement : Cuve d’ébullitions / de chauffe MegaPot 15 gallons
2 cuves d'empâtage Fermenter's Favorites de 10 gallons dont une avec fond filtrant
Plaque induction 3500W
Fermenteur PET avec robinet Big Mouth Bubbler 6,5 gallons (x3)
Brasseur : Amateur
Localisation : 22 rue des Azalees, 92230 Gennevilliers
A remercié : 4 fois
A été remercié : 3 fois

Re: DIY : Contrôleur de température à base de raspberrypi

Message par Madef »

Merci. C'est plus ou moins le cas. La température est vérifiée toutes les minutes. Et je défini un min/max. Je défini un écart de deux degrés (mais je peux définir un écart plus faible). Quand j'ai testé avec des valeurs plus faible que la cave le frigo fonctionnait quelques minutes tous les quarts d'heure environ. Je verrai a l'usage si cela suffit ou s'il faut créer une véritable tempo.
Madef
Ch'ti nouveau
Messages : 29
Inscrit depuis : 5 ans 1 mois
Mon équipement : Cuve d’ébullitions / de chauffe MegaPot 15 gallons
2 cuves d'empâtage Fermenter's Favorites de 10 gallons dont une avec fond filtrant
Plaque induction 3500W
Fermenteur PET avec robinet Big Mouth Bubbler 6,5 gallons (x3)
Brasseur : Amateur
Localisation : 22 rue des Azalees, 92230 Gennevilliers
A remercié : 4 fois
A été remercié : 3 fois

Re: DIY : Contrôleur de température à base de raspberrypi

Message par Madef »

J'ai fait quelques modifications sur le script python afin de pouvoir être utilisé sans avoir à mettre en place un site web pour le gérer. Il est possible de la faire depuis cette URL : http://tempcontroller.labierequiflotte.fr.


Image

Le site web affiche en direct l'état. Si la température est bleue, alors le système refroidi, rouge il chauffe et noire lorsqu'il ne fait rien.

Image


En bas de l'écran est affiché l'historique avec les températures (min, max, moyenne) et le temps de fonctionnement du système.


Sur le site, cliquez sur "New" pour créer un nouveau token. Copiez le token. Sur votre Raspberrypi, créer un conf.py.
Modifiez le code en rajoutant votre token :

Code : Tout sélectionner

#!/usr/bin/env python3

SENSOR = "28-021091777367"
OUTPUT_HEAT = 17
OUTPUT_COOL = 27
GET_URL = "http://tempcontroller.labierequiflotte.fr/get.php"
HISTORY_URL = "http://tempcontroller.labierequiflotte.fr/addHistory.php"
TOKEN = 'votre token ici'
Et voici le script.py :

Code : Tout sélectionner

#!/usr/bin/env python3

import RPi.GPIO as GPIO
import requests
import time
import json
import conf
import datetime
import sys
from pprint import pprint

# Retrieve last triggers
try:
    f = open(".last","r")
    trigger = json.loads(f.read())
    f.close()
except:
    trigger = {}

GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(conf.OUTPUT_COOL, GPIO.OUT)
GPIO.setup(conf.OUTPUT_HEAT, GPIO.OUT)

now = str(datetime.datetime.now())
currentDate = str(datetime.datetime.now()).split(' ')[0]

# Get current temp
def getTemp(sensorId):
    fichSonde = open("/sys/bus/w1/devices/"+sensorId+"/w1_slave")
    contenu = fichSonde.read()
    fichSonde.close()

    # On 'nettoie' le contenu pour ne recuperer que la temperature
    secondeLigne = contenu.split("\n")[1]
    temperatureData = secondeLigne.split(" ")[9]
    temperature = float(temperatureData[2:])
    temperature = temperature / 1000
    return temperature

# Get trigger temps on internet
try:
    result = requests.get(conf.GET_URL + "?token=" + conf.TOKEN).json()
    trigger['min'] = result['min']
    trigger['max'] = result['max']
except:
    if ('min' not in trigger):
        sys.exit("Cannot get trigger temp")

if ('history' not in trigger) :
    trigger['history'] = {}

if (currentDate not in trigger['history']) :
    trigger['history'] = {};
    trigger['history'][currentDate] = {} # We do not keep previous history
    trigger['history'][currentDate]['time_cool'] = 0;
    trigger['history'][currentDate]['time_heat'] = 0;
    trigger['history'][currentDate]['time_none'] = 0;
    trigger['history'][currentDate]['total'] = 0;
    trigger['history'][currentDate]['avg'] = 0;
    trigger['history'][currentDate]['min'] = -1;
    trigger['history'][currentDate]['max'] = -1;
    trigger['history'][currentDate]['count'] = 0;

temp = getTemp(conf.SENSOR)

trigger['history'][currentDate]['count'] += 1
trigger['history'][currentDate]['total'] += temp
trigger['history'][currentDate]['avg'] = trigger['history'][currentDate]['total'] / trigger['history'][currentDate]['count']

if (trigger['history'][currentDate]['min'] == -1 or trigger['history'][currentDate]['min'] > temp):
    trigger['history'][currentDate]['min'] = temp

if (trigger['history'][currentDate]['max'] == -1 or trigger['history'][currentDate]['max'] < temp):
    trigger['history'][currentDate]['max'] = temp

# Active/Disable relay modules
f = open("log.csv","a+")
if temp > (trigger['max']):
    GPIO.output(conf.OUTPUT_HEAT, 0)
    GPIO.output(conf.OUTPUT_COOL, 1)
    print(str(trigger['min'])+','+str(trigger['max'])+','+str(temp)+','+now+',cool')
    f.write(str(trigger['min'])+','+str(trigger['max'])+','+str(temp)+','+now+',cool\n')
    trigger['history'][currentDate]['time_cool'] += 1
elif temp < (trigger['min']):
    GPIO.output(conf.OUTPUT_HEAT, 1)
    GPIO.output(conf.OUTPUT_COOL, 0)
    print(str(trigger['min'])+','+str(trigger['max'])+','+str(temp)+','+now+',heat')
    f.write(str(trigger['min'])+','+str(trigger['max'])+','+str(temp)+','+now+',heat\n')
    trigger['history'][currentDate]['time_heat'] += 1
else:
    GPIO.output(conf.OUTPUT_HEAT, 0)
    GPIO.output(conf.OUTPUT_COOL, 0)
    print(str(trigger['min'])+','+str(trigger['max'])+','+str(temp)+','+now+',none')
    f.write(str(trigger['min'])+','+str(trigger['max'])+','+str(temp)+','+now+',none\n')
    trigger['history'][currentDate]['time_none'] += 1
f.close()

f = open(".last","w+")
f.write(json.dumps(trigger))
f.close()

# Send History
requests.get(conf.HISTORY_URL + '?token=' + conf.TOKEN + '&date=' + currentDate + '&min=' + str(round(trigger['history'][currentDate]['min'], 4)) + '&max=' + str(round(trigger['history'][currentDate]['max'], 4)) + '&avg=' + str(round(trigger['history'][currentDate]['avg'], 4)) + '&time_cool=' + str(trigger['history'][currentDate]['time_cool']) + '&time_heat=' + str(trigger['history'][currentDate]['time_heat']) + '&time_none=' + str(trigger['history'][currentDate]['time_none']) + '&current=' + str(round(temp, 4)))
Avatar de l’utilisateur
Stef26
Ch'ti nouveau
Messages : 52
Inscrit depuis : 5 ans 6 mois
Mon équipement : Grainfather
Grainfather sparge water heater
Ispindle (x3)
Seau plastique pour la fermentation
Brasseur : Amateur
Localisation : Valence
A remercié : 2 fois
A été remercié : 1 fois

Re: DIY : Contrôleur de température à base de raspberrypi

Message par Stef26 »

Je suis en train de tester, cela fonctionne bien.
Pièces jointes
Capture.JPG
Répondre

Créer un compte ou se connecter pour rejoindre la discussion

Vous devez être membre pour pouvoir répondre

Créer un compte

Vous n‘êtes pas membre ? Inscrivez-vous pour rejoindre notre communauté
Les membres peuvent créer leurs propres sujets et s‘abonner à des sujets
C‘est gratuit et cela ne prend qu‘une minute

Inscription

Se connecter