Raspberry Pi Pico W – Zeit aus dem Internet lesen und auf einem Display anzeigen

Wie man die Zeit von Time Server aus dem Internet lädt und mit dem neuen Raspberry Pi Pico W auf einem LCD-Display per I²C anzeigen lassen kann, möchte ich dir in diesem ausführlichen Beitrag zeigen.

Den neuen Raspberry Pi Pico W mit WiFi Support habe ich dir bereits in folgenden Beiträgen vorgestellt und einige kleine Projekte gezeigt.

Benötigte Ressourcen für dieses Projekt

Für dieses Projekt benötigst du, bzw. habe ich verwendet:

Aufbau der Schaltung, Raspberry Pi Pico W mit LCD-Display

In diesem Beitrag verwende ich ein einfaches 2×16 Zeichen LCD-Display mit I²C Schnittstelle. Es gibt jedoch auch größere Displays mit mehr Zeilen & Zeichen, wo du dann auch mehr Informationen anzeigen lassen kannst.

Raspberry Pi Pico mit LCD-Display via I²C
Raspberry Pi Pico mit LCD-Display via I²C

Der Raspberry Pi Pico W hat 2 I²C Schnittstellen, welche du über die nachfolgenden Pins erreichen kannst:

  • I²C 0
    • SDA – GP0, GP8, GP12, GP16, GP20
    • SCL – GP1, GP9, GP13, GP17, GP21
  • I²C 1
    • SDA – GP2, GP6, GP10, GP14, GP18, GP26
    • SCL – GP3, GP7, GP11, GP15, GP19, GP27
Auf der Schaltung - Raspberry Pi Pico W mit 2x20 Zeichen LCD-Display
Auf der Schaltung – Raspberry Pi Pico W mit 2×20 Zeichen LCD-Display

Pinout des Raspberry Pi Pico (W)

Der neue Raspberry Pi Pico W ist mit dem Vorgängermodell von den Pins identisch, d.h. du kannst deine bestehenden Projekte recht einfach um die WiFi Fähigkeit erweitern.

Pinout des Raspberry PI Pico
Pinout des Raspberry PI Pico

Erweiterungsplatine für den Raspberry Pi Pico

Für das Vorgängermodell dem Raspberry Pi Pico von 2021 habe ich ein kleines Board entwickelt welches mit die I²C Pins sowie ein paar GPIOs auf Groove Konnektoren bereitstellt.

Raspberry Pi Pico W auf DIY Expansionboard
Raspberry Pi Pico W auf DIY Expansionboard

I²C Adapter für LCD-Display

Das verwendete LCD-Display hat einen kleinen I²C Adapter auf der Rückseite verbaut. Dieser Adapter verfügt über die Pins SDA, SCL, VCC und GND sowie über einen Drehpotentiometer (blaues Bauteil im Bild) über welchen die Helligkeit der Schrift gesteuert werden kann.

I²C Adapter für das LCD-Display
I²C Adapter für das LCD-Display

Programmieren des LCD-Displays in der Thonny IDE mit MicroPython

Für die Programmierung nutze ich die Thonny IDE welche kostenfrei unter https://thonny.org/ für Microsoft Windows, macOS & auch Linux heruntergeladen werden kann.

Download & kopieren des Modules für das LCD-Display

Für die Programmierung des LCD-Displays verwende ich ein Modul, welches vom GitHub Repository dhylands/python_lcd geladen werden kann.

Von diesem Repository benötigen wir jedoch nur die beiden Dateien:

Diese beiden Dateien müssen wir auf dem Pi Pico im Ordner „lib“ speichern. Da wir jedoch keinen direkten Zugriff auf diesem Ordner haben, müssen wir die beiden Dateien in einem Editor öffnen (zbsp. Notepad++). Im geöffneten Editor markieren und kopieren, wir den gesamten Text in einem geöffneten Tab in der Thonny IDE und speichern dieses in den Ordner „lib“ unter den jeweiligen Dateinamen.

Troubleshooting

Beim Programmieren sind mir einige Fehlermeldungen ausgegeben worden, welche ich dir hier gerne zeigen möchte.

OSError 28

Diesen Fehler erhältst du, wenn der Speicher des Mikrocontrollers aufgebraucht ist. D.h. du musst Speicher freigeben, um diesen Fehler zu beheben.

Dieses können zbsp. nicht benötigte Bibliotheken sein, welche auf dem Mikrocontroller abgelegt wurden.

ValueError: bad SCL pin / bad SDA pin

Die konfigurierte I²C Verbindung ist nicht lesbar. Hier musst du deine Verkabelung und die verwendete Adresse prüfen.

Quellcode – „Hello World!“ auf dem LCD-Display

Schreiben wir zunächst in jede Zeile des Displays ein paar Zeichen.

from time import sleep
from machine import I2C, Pin
from machine_i2c_lcd import I2cLcd

i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=100000)
lcd = I2cLcd(i2c, 0x27, 2, 16)

zeile1  = 'Hello World!';
zeile2 = 'from Pi Pico W'

lcd.putstr(zeile1 + "\n" + zeile2)
sleep(2)
Auf der Schaltung - Raspberry Pi Pico W mit 2x20 Zeichen LCD-Display
Auf der Schaltung – Raspberry Pi Pico W mit 2×20 Zeichen LCD-Display

Auslesen einer Zeit vom Server

Als Dienst nutze ich den Server http://worldtimeapi.org welchen wir in der URL die Zeitzone übergeben wie zbsp. http://worldtimeapi.org/api/timezone/Europe/Berlin und als Antwort ein JSON mit den Daten erhalten.

Damit wir diesen HTTP Request absenden und einen Response vom Server empfangen können, müssen wir uns zu einem bestehenden WLAN Netzwerk verbunden haben. Wie du das machst, habe ich dir bereits im Beitrag Raspberry Pi Pico W – Webserver programmieren gezeigt.

Hier der Vollständigkeit der Code zum Herstellen einer Verbindung zu einem WLAN Netzwerk.

import network
import time
ssid = '*****'
password = '*******'
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)
print("Waiting to connect:")
while not wlan.isconnected() and wlan.status() >= 0:
    print(".", end="")   
    time.sleep(1)
print("")
print(wlan.ifconfig())

Wenn diese Verbindung aufgebaut ist, dann müssen wir einen Request an die oben genannte Adresse senden und den Response (ein JSON) auswerten.

Die Funktion zum Absenden des HTTP Requests und Empfangen des HTTP Response habe ich aus der offiziellen Dokumentation unter https://docs.micropython.org/en/latest/esp8266/tutorial/network_tcp.html entnommen.

# Funktion zum absenden eines HTTP Request und
# Rückgabe des HTTP Response
# Quelle: https://docs.micropython.org/en/latest/esp8266/tutorial/network_tcp.html
def http_get(url):
    result = ''
    _, _, host, path = url.split('/', 3)
    addr = socket.getaddrinfo(host, 80)[0][-1]
    s = socket.socket()
    s.connect(addr)
    s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8'))
    while True:
        data = s.recv(100)
        if data:
            result = result + str(data, 'utf8')
        else:
            break
    s.close()
    return result

# Ermitteln des JSONs aus dem HTTP Response
def findJson(response):
    txt = 'abbreviation'
    return response[response.find(txt)-2:]

Aus diesem JSON Response können wir nun diverse Informationen entnehmen.

In meinem Fall interessiere ich mich für den UNIX Timestamp „unixtime“.

{
   "abbreviation":"CEST",
   "client_ip":"80.128.77.199",
   "datetime":"2022-07-25T14:22:21.415593+02:00",
   "day_of_week":1,
   "day_of_year":206,
   "dst":true,
   "dst_from":"2022-03-27T01:00:00+00:00",
   "dst_offset":3600,
   "dst_until":"2022-10-30T01:00:00+00:00",
   "raw_offset":3600,
   "timezone":"Europe/Berlin",
   "unixtime":1658751741,
   "utc_datetime":"2022-07-25T12:22:21.415593+00:00",
   "utc_offset":"+02:00",
   "week_number":30
}

Damit die Daten aus dem JSON (eigentlich ein einfacher String) ausgelesen werden kann, wandeln wir dieses in ein Dictionary um.

import json
aDict = json.loads(jsonData)

Nun können wir auf die Werte recht einfach mit dem entsprechenden Key zugreifen und den UNIX Timestamp in ein Tupel mit den Datum & Zeit Werten umwandeln.

# parsen des Zeitstempels
# Parameter ist das JSON als Dictionary
def parseDateTimeStr(responeDict):
    # umwandeln des UNIX Timestamp in eine Liste aus Datum & Zeit Werten
    dateTime = time.localtime(int(responeDict['unixtime']))
    year = dateTime[0]
    month = dateTime[1]
    dayOfMonth = dateTime[2]
    
    # Wenn der Monat kleiner als 10 ist,
    # dann eine führende Null anhängen
    if month < 10:
        month = str('0' + str(month))
        
    if dayOfMonth < 10:
        dayOfMonth = str('0' + str(dayOfMonth))

    dateStr = str(dayOfMonth)+'.'+str(month)+'.'+str(year)

    hour = dateTime[3]
    minutes = dateTime[4]

    # offset für die Uhrzeit auslesen
    timeOffset = responeDict['utc_offset']
    # Wenn der Offset mit einem Minus beginnt
    # dann soll der Wert abgezogen werden
    if timeOffset[0:1] == '-':
        hour = hour - int(timeOffset[1:3])
    elif timeOffset[0:1] == '+':
        # beginnt der Offset mit einem "+"
        # dann soll die Zeit addiert werden
        hour = hour + int(timeOffset[1:3])

    if hour < 10:
        hour = str('0' + str(hour))

    if minutes < 10:
        minutes = str('0' + str(minutes))
    
    timeStr = str(hour)+':'+str(minutes)
    # auslesen der Zeitzone
    timezone = responeDict['timezone']
    # zurückgeben der Zeitzone, des Datums sowie der Uhrzeit
    return timezone, dateStr, timeStr

Die ermittelten Werte für timezone, dateStr und timeStr speichern wir uns in einer Variable welche wir auf dem Display dann mit der Funktion „displayDateTime“ anzeigen lassen wollen.

In der Funktion wird die Zeichenkette für die Timezone in min. 16 Zeichen umgewandelt bzw. es werden entsprechende Leerzeichen ergänzt.

Der Hintergrund ist das wenn es weniger Zeichen sind, dann wird kein vernünftiger & zuverlässiger Zeilenumbruch gemacht. Das ist vor allem beim Aktualisieren des LCD-Displays aufgefallen.

# parsen des Zeitstempels aus dem Dictionary
timezone, dateStr, timeStr = parseDateTimeStr(aDict)
# ausgeben der Werte auf dem LCD-Display
displayDateTime(timezone, dateStr, timeStr)
# Anzeigen der Daten auf dem Display
# Hinweis: Wenn die Zeichen der Zeitzone
# länger als 16 Zeichen ist, dann wird
# diese automatisch umgebrochen!
def displayDateTime(timezone, dateStr, timeStr):
    timezone = "{:<16}".format(timezone)
    lcd.putstr(timezone +"\n" +dateStr + " " + timeStr)
    time.sleep(2)

Die Main Funktion mit den Aufrufen der einzelnen Funktionen und die Endlosschleife.

# Main Funktion
def main():
    # Aufbau der Wifi-Verbindung
    wifiConnect()
    # Auslesen des HTTP Response
    response = http_get(url)
    # ermitteln des JSONs
    jsonData = findJson(response)
    # umwandeln des JSONs in ein Dictionary
    aDict = json.loads(jsonData)
    # parsen des Zeitstempels aus dem Dictionary
    timezone, dateStr, timeStr = parseDateTimeStr(aDict)
    # ausgeben der Werte auf dem LCD-Display
    displayDateTime(timezone, dateStr, timeStr)

# Aufrufen der Funktion main()
while True:
    main()
    # eine Pause von 60 Sekunden einlegen
    # Der Server leht die Verbindung ab wenn der Intervall
    # des Zugriffs zu klein ist.
    time.sleep(60)

Fertiges Programm

Hier nun das fertige Programm zum einfachen Download:

import network
import socket
import time
import json
from machine import I2C, Pin
from machine_i2c_lcd import I2cLcd

#Zugangsdaten zum WLAN Netzwerk
ssid = 'FRITZBox7590GI_EXT'
password = '22894580214767401850'

#LCD-Display
lines = 2
cols = 16

i2c = I2C(0, sda=Pin(0), scl=Pin(1), freq=100000)
lcd = I2cLcd(i2c, 0x27, lines, cols)

#Adresse welche uns das JSON mit den Zeitdaten liefert
service = 'http://worldtimeapi.org/api/timezone/'
url = service + 'Europe/Berlin'

# Aufbau einer WiFi Verbindung
def wifiConnect():
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(ssid, password)

    print("Waiting to connect:")
    while not wlan.isconnected() and wlan.status() >= 0:
        print(".", end="")   
        time.sleep(1)
    print("")
    print(wlan.ifconfig())

# Funktion zum absenden eines HTTP Request und
# Rückgabe des HTTP Response
# Quelle: https://docs.micropython.org/en/latest/esp8266/tutorial/network_tcp.html
def http_get(url):
    result = ''
    _, _, host, path = url.split('/', 3)
    addr = socket.getaddrinfo(host, 80)[0][-1]
    s = socket.socket()
    s.connect(addr)
    s.send(bytes('GET /%s HTTP/1.0\r\nHost: %s\r\n\r\n' % (path, host), 'utf8'))
    while True:
        data = s.recv(100)
        if data:
            result = result + str(data, 'utf8')
        else:
            break
    s.close()
    return result

# Ermitteln des JSONs aus dem HTTP Response
def findJson(response):
    txt = 'abbreviation'
    return response[response.find(txt)-2:]

# parsen des Zeitstempels
# Parameter ist das JSON als Dictionary
def parseDateTimeStr(responeDict):
    # umwandeln des UNIX Timestamp in eine Liste aus Datum & Zeit Werten
    dateTime = time.localtime(int(responeDict['unixtime']))
    year = dateTime[0]
    month = dateTime[1]
    dayOfMonth = dateTime[2]
    
    # Wenn der Monat kleiner als 10 ist,
    # dann eine führende Null anhängen
    if month < 10:
        month = str('0' + str(month))
        
    if dayOfMonth < 10:
        dayOfMonth = str('0' + str(dayOfMonth))

    dateStr = str(dayOfMonth)+'.'+str(month)+'.'+str(year)

    hour = dateTime[3]
    minutes = dateTime[4]

    # offset für die Uhrzeit auslesen
    timeOffset = responeDict['utc_offset']
    # Wenn der Offset mit einem Minus beginnt
    # dann soll der Wert abgezogen werden
    if timeOffset[0:1] == '-':
        hour = hour - int(timeOffset[1:3])
    elif timeOffset[0:1] == '+':
        # beginnt der Offset mit einem "+"
        # dann soll die Zeit addiert werden
        hour = hour + int(timeOffset[1:3])

    if hour < 10:
        hour = str('0' + str(hour))

    if minutes < 10:
        minutes = str('0' + str(minutes))
    
    timeStr = str(hour)+':'+str(minutes)
    # auslesen der Zeitzone
    timezone = responeDict['timezone']
# zurückgeben der Zeitzone, des Datums sowie der Uhrzeit
    return timezone, dateStr, timeStr

# Anzeigen der Daten auf dem Display
# Hinweis: Wenn die Zeichen der Zeitzone
# länger als 16 Zeichen ist, dann wird
# diese automatisch umgebrochen!
def displayDateTime(timezone, dateStr, timeStr):
    timezone = "{:<16}".format(timezone)
    lcd.putstr(timezone +"\n" +dateStr + " " + timeStr)
    time.sleep(2)

# Main Funktion
def main():
    # Aufbau der Wifi-Verbindung
    wifiConnect()
    # Auslesen des HTTP Response
    response = http_get(url)
    # ermitteln des JSONs
    jsonData = findJson(response)
    # umwandeln des JSONs in ein Dictionary
    aDict = json.loads(jsonData)
    # parsen des Zeitstempels aus dem Dictionary
    timezone, dateStr, timeStr = parseDateTimeStr(aDict)
    # ausgeben der Werte auf dem LCD-Display
    displayDateTime(timezone, dateStr, timeStr)

# Aufrufen der Funktion main()
while True:
    main()
    # eine Pause von 60 Sekunden einlegen
    # Der Server leht die Verbindung ab wenn der Intervall
    # des Zugriffs zu klein ist.
    time.sleep(60)

2 Kommentare

Kommentar hinterlassen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert