RaspberryPi + DS18B20 = ThermometerPi

Ich bin vor einigen Wochen über einen Blogpost gestoßen, in dem erklärt wurde, wie man aus einem RaspberryPi und einem Wärmesensor (DS18B20) sich ein eigenes Thermometer bauen kann. Dieser Idee bin ich nachgegangen und möchte in diesem Blogpost beschreiben, welche Tweaks dabei nötig waren.

Irgendwo auf Twitter oder einem anderen Medium habe ich ein Link zu dem Blogpost „[Tutorial] DS18S20 Temperatur Sensor an einem Raspberry Pi“ gefunden. Das hat mich so sehr fasziniert, dass ich das nachbauen wollte.

Dazu bestellte ich mir auf Amazon zunächst 10 solcher Sensoren mit entsprechend langem Kabel und Abdichtung: 10 x DS18B20 Digitale Temperaturfühler für ~15€. Dazu kamen noch ein paar 4.7k Ohm Widerstände. Nach ein paar Tagen war alles in meinem Briefkasten. Angeblich kann man sich von Maximintegrated ein kostenloses Sample zuschicken lassen. Allerdings kam bei mir bisher nichts an, vielleicht auch einfach Pech oder ein Fehler auf meiner Seite.

Aufbau

Für die Verkabelung des Sensors mit dem RaspberryPi bin ich den Schritten in dem oben genannten Blogpost gefolgt. Für mehr Informationen möchte ich dorthin verweisen.

Quelle: https://kopfkino.irosaurus.com/tutorial-ds18s20-temperatur-sensor-an-einem-raspberry-pi/

Quelle: https://kopfkino.irosaurus.com/tutorial-ds18s20-temperatur-sensor-an-einem-raspberry-pi/

Wichtig ist scheinbar nur, dass man den GPIO-Pin 4 nutzt. Ich habe es selbst nicht mit anderen GPIO-Pins probiert. Wenn es bei euch mit anderen klappt, bitte einen kurzen Kommentar hinterlassen.

Konfiguration

Ich habe auf meinem RaspberryPi Archlinux mit einem halbwegs aktuellen Kernel am Laufen. Das führte dazu, dass die weiteren Schritte in dem oben genannten Blogpost nicht funktionierten. Nachdem ich ein wenig recherchiert habe, kam ich zu einer klein wenig modifizierten Lösung.

Zunächst muss man in die /boot/config.txt folgende Zeile anhängen:

dtoverlay=w1-gpio,gpiopin=4,pullup=on

Danach muss man noch Kernelmodule laden. Dies erledigt man am Besten direkt beim Booten, in dem man folgendes in die Datei /etc/modules einträgt:

w1-gpio pullup=1
w1_therm

Nach einem Neustart sollte der Sensor unter  „/sys/bus/w1/devices/<sensor-id>“ verfügbar sein. Mit folgendem Einzeiler kriegt man die aktuelle Temperatur heraus:

$> cat /sys/bus/w1/devices/28-041621eb50ff/w1_slave | grep -oP "t=(\d+)" | sed -e "s/t=//g"
21687

Automatisierung & Webseite

Ich wollte mir eine kleine grafische Ansicht über den Temperaturverlauf bauen. Dazu entschied ich mich, die Daten von zwei Sensoren (1x Indoor, 1x Outdoor) alle 30 Sekunden auszulesen und in eine SQLite3-Datenbank zu schreiben.

Dazu habe ich zwei Scripte unter „/opt/thermo/“ zu liegen. Das Erste kümmert sich nur um das Auslesen der Daten und heißt „data.sh“:

#!/bin/bash

outdoor=$(cat /sys/bus/w1/devices/28-041621eb50ff/w1_slave | grep -oP "t=(\d+)" | sed -e "s/t=//g")
indoor=$(cat /sys/bus/w1/devices/28-041621fadbff/w1_slave | grep -oP "t=(\d+)" | sed -e "s/t=//g")
seconds=$(TZ=Europe/Berlin date +"%s")

echo "$seconds,$indoor,$outdoor"

Die IDs müssen natürlich entsprechend angepasst werden.

Das zweite Script „insertdata.sh“ ruft „data.sh“ auf und schreibt die Daten in eine SQLite3-Datenbank.

#!/bin/bash
olddir=$(pwd)
cd /opt/thermo/
sqlite3 thermo.sqlite3 "insert into temperatures VALUES ($(./data.sh));"
cd "$olddir"

Da Archlinux mit Systemd kommt, können wir dessen Timer nutzen, um die Aufgabe alle 30 Sekunden abzufeuern. Dazu legen wir zunächst den Service unter „/etc/systemd/system/thermo.service“ an:

[Unit]
Description=Thermometer data collector

[Service]
Type=oneshot
ExecStart=/bin/bash /opt/thermo/insertdata.sh

Der Timer „/etc/systemd/system/thermo.timer“ führt diesen Service dann periodisch aus:

[Unit]
Description=Thermo data collector timer

[Timer]
Persistent=true
OnUnitActiveSec=30s
OnBootSec=30s

[Install]
WantedBy=timers.target

Diesen muss man mit einem kurzem Kommando aktivieren:

sudo systemctl enable thermo.timer

Zuletzt fehlte nur noch eine kleine Webseite, die die Daten aus der SQLite-Datenbank ausliest und anzeigt. Dazu habe ich einfach Apache + PHP installiert und folgende „index.php“ im DocumentRoot abgelegt.

thermo-website

<html>
<head>
<!-- Latest compiled and minified CSS -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" integrity="sha384-1q8mTJOASx8j1Au+a5WDVnPi2lkFfwwEAa8hDDdjZlpLegxhjVME1fgjWPGmkzs7" crossorigin="anonymous">

<!-- Optional theme -->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap-theme.min.css" integrity="sha384-fLW2N01lMqjakBkx3l/M9EahuwpSfeNvV63J5ezn3uZzapT0u7EYsXMjQV+0En5r" crossorigin="anonymous">

<!-- Latest compiled and minified JavaScript -->
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" integrity="sha384-0mSbJDEHialfmuBBQP6A4Qrprq5OVfW37PRR3j5ELqxss1yVqOtnepnHVP9aJ7xS" crossorigin="anonymous"></script>

<script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.0.0/Chart.js"></script>
</head>
<body class="container">
<?php
$db = new SQLite3('/opt/thermo/thermo.sqlite3');
$beginOfDay = strtotime("midnight", time());

$current = $db->query('SELECT * FROM temperatures order by timestamp desc limit 1');
$row = $current->fetchArray();

$currentDate = date("H:i:s - d.m.Y", $row['timestamp']);
$currentIndoor = round($row['indoor']/1000,3);
$currentOutdoor = round($row['outdoor']/1000,3);

$minmax = $db->query("SELECT MIN(indoor) as minIndoor, MIN(outdoor) as minOutdoor, MAX(indoor) as maxIndoor, MAX(outdoor) as maxOutdoor FROM temperatures where timestamp >= $beginOfDay order by timestamp desc");
$row = $minmax->fetchArray();

$minIndoor = round($row['minIndoor']/1000,3);
$maxIndoor = round($row['maxIndoor']/1000,3);
$minOutdoor = round($row['minOutdoor']/1000,3);
$maxOutdoor = round($row['maxOutdoor']/1000,3);

echo "<h1>Last measurement: $currentDate</h1>";
echo "<table class='table'><tr><th>Location</th><th>Current °C</th><th>Todays Min °C</th><th>Todays Max °C</th></tr>";
echo "<tr><td>Indoor</td><td>$currentIndoor</td><td>$minIndoor</td><td>$maxIndoor</td>";
echo "<tr><td>Outdoor</td><td>$currentOutdoor</td><td>$minOutdoor</td><td>$maxOutdoor</td>";
echo "</table>";

?>
<canvas id="myChart" width="300" height="100"></canvas>

<?php
$minmax = $db->query("SELECT * FROM temperatures where timestamp >= $beginOfDay order by timestamp asc");
$labels = array();
$indoor = array();
$outdoor = array();
$diff = array();
while($row = $minmax->fetchArray()) {
    $labels[] = date("H:i:s", $row['timestamp']);
    $indoor[] = round($row['indoor']/1000,3);
    $outdoor[] = round($row['outdoor']/1000,3);
    $diff[] = abs(round((abs($row['indoor']) - abs($row['outdoor']))/1000,3));
}
?>

<script>
var data = {
    labels: <?php echo json_encode($labels); ?>,
    datasets: [
        {
            label: "Indoor",
            fill: false,
            lineTension: 0.1,
            backgroundColor: "rgb(209,12,8)",
            borderColor: "rgb(209,12,8)",
            borderCapStyle: 'butt',
            borderDash: [],
            borderDashOffset: 0.0,
            borderJoinStyle: 'miter',
            pointBorderColor: "rgb(209,12,8)",
            pointBackgroundColor: "#fff",
            pointBorderWidth: 1,
            pointHoverRadius: 5,
            pointHoverBackgroundColor: "rgb(209,12,8)",
            pointHoverBorderColor: "rgba(220,220,220,1)",
            pointHoverBorderWidth: 2,
            pointRadius: 1,
            pointHitRadius: 10,
            data: <?php echo json_encode($indoor);?>,
        },
        {
            label: "Outdoor",
            fill: false,
            lineTension: 0.1,
            backgroundColor: "rgb(8,209,8)",
            borderColor: "rgb(8,209,8)",
            borderCapStyle: 'butt',
            borderDash: [],
            borderDashOffset: 0.0,
            borderJoinStyle: 'miter',
            pointBorderColor: "rgb(8,209,8)",
            pointBackgroundColor: "#fff",
            pointBorderWidth: 1,
            pointHoverRadius: 5,
            pointHoverBackgroundColor: "rgb(8,209,8)",
            pointHoverBorderColor: "rgba(220,220,220,1)",
            pointHoverBorderWidth: 2,
            pointRadius: 1,
            pointHitRadius: 10,
            data: <?php echo json_encode($outdoor);?>,
        },
        {
            label: "Difference",
            fill: false,
            lineTension: 0.1,
            backgroundColor: "rgba(75,192,192)",
            borderColor: "rgba(75,192,192)",
            borderCapStyle: 'butt',
            borderDash: [],
            borderDashOffset: 0.0,
            borderJoinStyle: 'miter',
            pointBorderColor: "rgba(75,192,192)",
            pointBackgroundColor: "#fff",
            pointBorderWidth: 1,
            pointHoverRadius: 5,
            pointHoverBackgroundColor: "rgba(75,192,192)",
            pointHoverBorderColor: "rgba(220,220,220,1)",
            pointHoverBorderWidth: 2,
            pointRadius: 1,
            pointHitRadius: 10,
            data: <?php echo json_encode($diff);?>,
        }
    ]
};

var ctx = document.getElementById("myChart");
var myLineChart = new Chart(ctx, {
    type: 'line',
    data: data,
});
</script>
</body>
</html>

~ Sebastian