Aussehen
Eine Messstation mit dem Calliope
Wie verändert sich über den Tag die Temperatur in deinem Raum? Das kannst du fühlen, aber auch messen. Ein Calliope mini misst die Temperatur und schickt sie durch das USB-Kabel zu deinem Python-Programm. Das legt die Werte in einer Datei ab und zeichnet daraus eine Kurve.
Der Calliope hat den Temperatursensor schon eingebaut, du musst nichts verkabeln. Die Firmware und den Editor für den Calliope liefert MicroBlocks (Lizenz MPL 2.0). Wie MicroBlocks mit dem Rechner redet, steht in seinem Quelltext. Das Programm zum Lesen in Stufe 2 ist für dieses Buch geschrieben.
Das bauen wir in vier Stufen.
Was du brauchst
- Einen Calliope mini (V1, V2 oder V3) mit USB-Kabel.
- Die Module
pyserialundmatplotlib. Wie man Module installiert, steht auf der Übersicht der Projekte.
Zwei Programme, zwei Orte
Ein Programm aus Blöcken landet auf dem Calliope und bleibt dort gespeichert. Ein Python-Programm läuft auf dem Rechner. Die beiden sehen nichts voneinander, außer dem, was der Calliope durch das Kabel schickt.
Stufe 1: Den Calliope vorbereiten
Auf dem Calliope läuft ein Programm, das ihn selbst betreibt: die Firmware. Sie nimmt die Programme entgegen, die du ihm schickst. Für dieses Projekt bekommt er einmal die Firmware von MicroBlocks. Mit ihr programmierst du ihn aus Blöcken, und sie schickt Messwerte durch das USB-Kabel zum Rechner.
Öffne den Editor microblocks.fun/run in Chrome oder Edge. Nur diese beiden Browser dürfen auf den USB-Anschluss zugreifen.
Stecke den Calliope an. Klicke im Editor oben auf das Zahnrad und dann auf aktualisiere Firmware auf dem Board.

Wähle in der Liste Calliope und bestätige mit OK.

Der Editor lässt dich eine Datei mit der Endung
.hexspeichern. Das ist die Firmware. Der Calliope erscheint am Rechner als Laufwerk MINI. Kopiere die Datei dorthin, der Calliope startet danach neu.
Das ist nur einmal nötig.
Verbinde jetzt den Editor mit dem Calliope: verbinden → verbinden (USB). Wie das aussieht, steht im Kapitel Editor. Dann baust du dieses Programm:

wenn gestartet und fortlaufend lassen den Calliope die Blöcke darunter immer wieder ausführen. Temperatur (°C) aus der Kategorie Eingabe fragt den Sensor, sage aus Ausgabe schickt den Wert hinaus, und warte 5000 Millisekunden lässt fünf Sekunden vergehen.
Statt es zu bauen, kannst du die Datei messstation-programm.ubp herunterladen und in den Editor ziehen.
Der Editor überträgt jede Änderung sofort auf den Calliope. Klicke auf den Block wenn gestartet, dann läuft das Programm, und neben sage erscheint eine Sprechblase mit der Temperatur, alle fünf Sekunden neu. Das Programm liegt jetzt auf dem Calliope und startet nach jedem Einschalten von selbst.
Probier es aus
- Welche Temperatur zeigt die Sprechblase?
- Nimm den Calliope in die Hand und warte eine Minute. Was passiert mit dem Wert?
- Lies die Temperatur an einem Fenster, in der Sonne und neben einer Heizung ab. Notiere die Werte.
Der Sensor misst den Chip
Der Temperatursensor sitzt im Prozessor des Calliope. Er misst, wie warm der Chip ist, nicht die Luft. Der Chip erwärmt sich beim Arbeiten selbst, deshalb liegt der Wert meist etwas über der Raumtemperatur. Für die Kurve am Ende ist das kein Problem: Wir wollen ja sehen, wie sich die Temperatur verändert.
Der Calliope meldet die Temperatur in ganzen Grad. Kleinere Unterschiede sieht deine Kurve nicht.
Stufe 2: Die Werte im Python-Programm
Bisher zeigt nur der Editor die Zahl. Jetzt soll sie in deinem Python-Programm ankommen. Der Calliope schickt jeden Wert als kleines Paket durch das USB-Kabel, aber nur, solange der Rechner sich meldet: Mindestens alle fünf Sekunden muss eine Nachricht von dort kommen. Dein Programm meldet sich deshalb jede Sekunde und holt aus den ankommenden Paketen den Text heraus. Das erledigt die Funktion messwerte. Sie liefert jeden Wert, den der Calliope schickt, als Text.
Dein Programm braucht noch den Anschluss, an dem der Calliope hängt. So findest du ihn:
python
import serial.tools.list_ports
for anschluss in serial.tools.list_ports.comports():
print(anschluss.device, "-", anschluss.description)Lass es zweimal laufen, einmal mit angeschlossenem und einmal mit abgezogenem Calliope. Die Zeile, die nur beim ersten Mal erscheint, ist dein Calliope. Unter Windows heißt der Anschluss etwa COM5, unter Linux etwa /dev/ttyACM0.
Das Programm zum Lesen. Die Kommentare erklären, was messwerte Schritt für Schritt tut:
python
import time
import serial # kommt aus dem Modul pyserial
ANSCHLUSS = "COM5" # hier deinen Anschluss eintragen
GESCHWINDIGKEIT = 115200 # Baud: so viele Zeichen pro Sekunde gehen durch das Kabel
PING = bytes([0xFA, 26, 0]) # die Nachricht "ping": Anfang 0xFA, Art 26, Nummer 0
def messwerte(anschluss):
"""Liefert jeden Text, den der Calliope mit dem Block "sage" ausgibt."""
verbindung = serial.Serial(anschluss, GESCHWINDIGKEIT, timeout=0.05)
puffer = bytearray() # hier sammeln sich die Bytes, die schon angekommen sind
letzter_ping = 0
try:
while True:
# Der Calliope redet nur, wenn er in den letzten fünf Sekunden etwas vom
# Rechner gehört hat. Darum meldet sich dieses Programm jede Sekunde.
if time.time() - letzter_ping >= 1:
verbindung.write(PING)
letzter_ping = time.time()
# Alles holen, was gerade angekommen ist (höchstens 0,05 Sekunden warten).
puffer += verbindung.read(4096)
# Aus dem Puffer ganze Nachrichten herausschneiden. Kommt eine Nachricht
# in zwei Teilen an, wartet die Schleife auf den Rest.
while True:
# Bytes überspringen, bis eine Nachricht beginnt:
# 0xFA beginnt eine kurze Nachricht, 0xFB eine lange.
while puffer and puffer[0] not in (0xFA, 0xFB):
del puffer[0]
if len(puffer) < 3:
break
if puffer[0] == 0xFA:
# Kurz: drei Bytes (Anfang, Art, Nummer), ohne Inhalt.
art, kopf, laenge = puffer[1], 3, 0
else:
# Lang: fünf Bytes Kopf, zwei davon nennen die Länge des Inhalts.
if len(puffer) < 5:
break
art, kopf, laenge = puffer[1], 5, puffer[3] | (puffer[4] << 8)
if len(puffer) < kopf + laenge:
break # Die Nachricht ist noch nicht vollständig da.
inhalt = bytes(puffer[kopf:kopf + laenge])
del puffer[:kopf + laenge]
# Art 20 ist ein ausgegebener Wert (der Block "sage"). Das erste Byte
# des Inhalts nennt den Typ des Werts, die 2 heißt "Text".
if art == 20 and inhalt[:1] == b"\x02":
# yield gibt den Text an die for-Schleife des aufrufenden
# Programms. Danach macht die Funktion an dieser Stelle weiter.
yield inhalt[1:].decode("utf-8")
finally:
verbindung.close() # Der Anschluss wird frei, auch wenn die Schleife endet.
for text in messwerte(ANSCHLUSS):
print(text)messwerte(ANSCHLUSS) wartet, bis der Calliope den nächsten Wert schickt, und liefert ihn als Text. Die Schleife am Ende läuft deshalb, bis du das Programm mit Stopp beendest.
Der Anschluss kann nur einer
Solange der Editor mit dem Calliope verbunden ist, kann dein Programm den Anschluss nicht öffnen. Schließe den Tab mit dem Editor, bevor du das Programm startest. Das Programm auf dem Calliope bleibt erhalten.
Probier es aus
- Starte das Programm. Erscheint nach spätestens fünf Sekunden die erste Zahl in der Konsole?
- Nimm den Calliope in die Hand. Wie lange dauert es, bis die Zahl steigt?
Stufe 3: Messen und speichern
Eine Kurve braucht zu jedem Wert die Zeit, zu der er gemessen wurde. Der Calliope schickt nur den Wert. Die Zeit nimmt dein Programm selbst: Es hält fest, wie viele Sekunden seit dem Start vergangen sind, wenn ein Wert ankommt, und schreibt beides in eine Datei, eine Zeile pro Wert.
So sieht die Datei am Ende aus:
sekunden;temperatur
4;24
9;24
14;25
…CSV-Datei
Eine CSV-Datei ist reiner Text: eine Tabellenzeile pro Zeile, die Spalten durch Semikolons getrennt. Jede Tabellenkalkulation kann sie öffnen.
Direkt vor die Schleife am Ende kommen die Einstellungen und die erste Zeile der Datei:
python
DATEI = "raum.csv"
DAUER = 3600 # so viele Sekunden wird gemessen
open(DATEI, "w").write("sekunden;temperatur\n")
start = time.time()Die Schleife wird länger:
python
for text in messwerte(ANSCHLUSS):
sekunden = round(time.time() - start)
open(DATEI, "a").write(str(sekunden) + ";" + text + "\n")
print(sekunden, text)
if sekunden >= DAUER:
breakopen(DATEI, "w") legt die Datei neu an, eine vorhandene Datei gleichen Namens wird dabei leer. "a" hängt dagegen an das Ende der Datei an. time.time() liefert die aktuelle Zeit in Sekunden. Zieht man die Startzeit davon ab, bleiben die Sekunden seit dem Start, und round rundet sie auf ganze Sekunden. break verlässt die Schleife, sobald die Dauer erreicht ist.
Das ganze Programm dieser Stufe
python
import time
import serial # kommt aus dem Modul pyserial
ANSCHLUSS = "COM5" # hier deinen Anschluss eintragen
GESCHWINDIGKEIT = 115200 # Baud: so viele Zeichen pro Sekunde gehen durch das Kabel
PING = bytes([0xFA, 26, 0]) # die Nachricht "ping": Anfang 0xFA, Art 26, Nummer 0
def messwerte(anschluss):
"""Liefert jeden Text, den der Calliope mit dem Block "sage" ausgibt."""
verbindung = serial.Serial(anschluss, GESCHWINDIGKEIT, timeout=0.05)
puffer = bytearray() # hier sammeln sich die Bytes, die schon angekommen sind
letzter_ping = 0
try:
while True:
# Der Calliope redet nur, wenn er in den letzten fünf Sekunden etwas vom
# Rechner gehört hat. Darum meldet sich dieses Programm jede Sekunde.
if time.time() - letzter_ping >= 1:
verbindung.write(PING)
letzter_ping = time.time()
# Alles holen, was gerade angekommen ist (höchstens 0,05 Sekunden warten).
puffer += verbindung.read(4096)
# Aus dem Puffer ganze Nachrichten herausschneiden. Kommt eine Nachricht
# in zwei Teilen an, wartet die Schleife auf den Rest.
while True:
# Bytes überspringen, bis eine Nachricht beginnt:
# 0xFA beginnt eine kurze Nachricht, 0xFB eine lange.
while puffer and puffer[0] not in (0xFA, 0xFB):
del puffer[0]
if len(puffer) < 3:
break
if puffer[0] == 0xFA:
# Kurz: drei Bytes (Anfang, Art, Nummer), ohne Inhalt.
art, kopf, laenge = puffer[1], 3, 0
else:
# Lang: fünf Bytes Kopf, zwei davon nennen die Länge des Inhalts.
if len(puffer) < 5:
break
art, kopf, laenge = puffer[1], 5, puffer[3] | (puffer[4] << 8)
if len(puffer) < kopf + laenge:
break # Die Nachricht ist noch nicht vollständig da.
inhalt = bytes(puffer[kopf:kopf + laenge])
del puffer[:kopf + laenge]
# Art 20 ist ein ausgegebener Wert (der Block "sage"). Das erste Byte
# des Inhalts nennt den Typ des Werts, die 2 heißt "Text".
if art == 20 and inhalt[:1] == b"\x02":
# yield gibt den Text an die for-Schleife des aufrufenden
# Programms. Danach macht die Funktion an dieser Stelle weiter.
yield inhalt[1:].decode("utf-8")
finally:
verbindung.close() # Der Anschluss wird frei, auch wenn die Schleife endet.
DATEI = "raum.csv"
DAUER = 3600 # so viele Sekunden wird gemessen
open(DATEI, "w").write("sekunden;temperatur\n")
start = time.time()
for text in messwerte(ANSCHLUSS):
sekunden = round(time.time() - start)
open(DATEI, "a").write(str(sekunden) + ";" + text + "\n")
print(sekunden, text)
if sekunden >= DAUER:
breakJetzt misst du richtig: Stelle den Calliope an einen Platz im Raum, an dem er bleiben kann, und starte das Programm. DAUER sind die Sekunden, die gemessen wird: Eine Stunde sind 3600, ein Schulvormittag von 8 bis 12 Uhr 14400. Die Messung steht in raum.csv, neben deinem Programm.
Probier es aus
- Setze
DAUER = 60, starte das Programm und öffne danachraum.csvin einem Texteditor. Passt die Zahl der Zeilen zu einem Wert alle fünf Sekunden? - Starte das Programm zweimal hintereinander. Warum steht in der Datei trotzdem nur eine Messung?
Stufe 4: Die Kurve zeichnen
Gemessen ist gemessen. Zeichnen kannst du so oft, wie du willst, und der Calliope muss dafür nicht angeschlossen sein. Das Zeichnen ist deshalb ein neues Programm in einer eigenen Datei, direkt neben raum.csv. Es liest die Datei Zeile für Zeile, sammelt die Zeiten und die Temperaturen in zwei Listen und gibt sie an matplotlib.
python
import matplotlib.pyplot as plt
zeilen = open("raum.csv").read().splitlines()
minuten = []
temperaturen = []
for zeile in zeilen[1:]:
teile = zeile.split(";")
minuten.append(int(teile[0]) / 60)
temperaturen.append(float(teile[1]))
plt.plot(minuten, temperaturen, "o-")
plt.xlabel("Minuten")
plt.ylabel("Temperatur in °C")
plt.title("Messstation")
plt.show()zeilen[1:] lässt die Kopfzeile aus. zeile.split(";") schneidet jede Zeile am Semikolon in zwei Teile: vorn die Sekunden, hinten die Temperatur. int und float machen daraus Zahlen. Die Sekunden werden durch 60 geteilt, damit die Achse in Minuten beschriftet ist. "o-" zeichnet einen Punkt pro Messwert und eine Linie dazwischen.
Probier es aus
- Zeichne deine Kurve. Wie hat sich die Temperatur während der Messung verändert? Beschreibe es in einem Satz.
- Die Kurve besteht aus Stufen. Woran liegt das?
- Was ändert sich an der Kurve, wenn du
"o-"durch"-"oder"o"ersetzt?