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 :
Voici le schéma électrique (fait à la main désolé) :
Voici la photo du contrôleur ouvert :
Et la photo du tout installé :
La capteur qui pour le moment est "à l’arrache" dans le frigo :
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'
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">×</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">×</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">×</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
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 :