Einfaches UV-Monitoring: Wie Sie den SI1145-Sensor mit einem Raspberry Pi und weewx nutzen


Software • von Sven Reifschneider • 01. August 2023 • 2 Kommentare
#software #anleitung #weewx #wetter #raspberrypi

In einer Welt, die immer datenorientierter wird, spielt die präzise Erfassung von Umweltdaten eine immer wichtigere Rolle. Speziell in der Wetterüberwachung ist die Erfassung des UV-Index von entscheidender Bedeutung, nicht nur für die wissenschaftliche Forschung, sondern auch für die allgemeine Gesundheit und Sicherheit. In diesem Beitrag werden wir uns darauf konzentrieren, wie Sie einen SI1145 UV-Sensor mit einem Raspberry Pi und weewx kombinieren können, um eine erweiterte Wetterüberwachungsstation zu schaffen.

Gerade in der IoT-Landschaft ist der Raspberry Pi eine Komponente, die sowohl leistungsstark als auch kosteneffizient ist. Kombiniert mit dem bewährten SI1145 UV-Sensor bietet er eine leistungsstarke Lösung für die UV-Überwachung. Wir führen Sie durch die technischen Schritte, von der Einrichtung des Raspberry Pi bis zur Datenintegration in weewx.

Der Raspberry Pi + SI1145

Ein Raspberry Pi, strategisch auf dem Dach positioniert und mit einer Vielzahl von Sensoren ausgestattet, dient als Basis unserer erweiterten Wetterstation. Obwohl es mehrere Sensoren gibt, die wir verwenden könnten, liegt unser Fokus auf dem UV-Sensor SI1145. Dieser Sensor ist zwar nicht der modernste auf dem Markt, hat sich jedoch als zuverlässig und recht präzise erwiesen. Natürlich kann man diesen nicht mit den teuren Modulen für professionelle Wetterstationen vergleichen, aber für einen groben UV-Index-Wert ist dieser Sensor gut geeignet.

Das Prinzip ist mit anderen Sensoren identisch, nur dass diese im Python-Skript anders angesprochen werden müssen. Dafür haben diese großteils schon passende Bibliotheken, sodass die Nutzung sehr einfach ist.

Seitens der Hardware wird der Sensor einfach via I2C an den Raspberry Pi angeschlossen. Dies sollte bei anderen Systemen dieser Art genauso funktionieren, da die I2C Schnittstelle vom Skript direkt angesprochen wird.

Seitens der Software werden Python3 (bei den meisten Linux-Installationen bereits vorhanden), sowie ein Webserver (wir empfehlen nginx, einfach zu installieren via sudo apt install nginx-full) benötigt. Python benötigt die Bibliothek Adafruit-GPIO welche via sudo pip3 install Adafruit-GPIO installiert werden kann.

Das erste, was Sie dann tun müssen, ist ein Python-Skript auf Ihrem Raspberry Pi abzulegen, das den SI1145 ausliest und den aktuellen Wert in einer JSON-Datei bereitstellt. Der Code für dieses Skript nutzt die Python_SI1145-Klasse von Joe Gutting auf Github (MIT-Lizenz), die von uns für Python3 angepasst wurde. Diese macht den Großteil des Skriptes aus, das Auslesen und Abspeichern findet am Ende des Skriptes statt.

#!/usr/bin/python
# Fetching the UV index from the SI1145 module
import datetime

# SI1145 class
# Source: https://github.com/THP-JOE/Python_SI1145
# Author: Joe Gutting, adjusted for Python3 by Neoground GmbH
import logging
import time
import json

import Adafruit_GPIO.I2C as I2C

# COMMANDS
SI1145_PARAM_QUERY = 0x80
SI1145_PARAM_SET = 0xA0
SI1145_NOP = 0x0
SI1145_RESET = 0x01
SI1145_BUSADDR = 0x02
SI1145_PS_FORCE = 0x05
SI1145_ALS_FORCE = 0x06
SI1145_PSALS_FORCE = 0x07
SI1145_PS_PAUSE = 0x09
SI1145_ALS_PAUSE = 0x0A
SI1145_PSALS_PAUSE = 0xB
SI1145_PS_AUTO = 0x0D
SI1145_ALS_AUTO = 0x0E
SI1145_PSALS_AUTO = 0x0F
SI1145_GET_CAL = 0x12

# Parameters
SI1145_PARAM_I2CADDR = 0x00
SI1145_PARAM_CHLIST = 0x01
SI1145_PARAM_CHLIST_ENUV = 0x80
SI1145_PARAM_CHLIST_ENAUX = 0x40
SI1145_PARAM_CHLIST_ENALSIR = 0x20
SI1145_PARAM_CHLIST_ENALSVIS = 0x10
SI1145_PARAM_CHLIST_ENPS1 = 0x01
SI1145_PARAM_CHLIST_ENPS2 = 0x02
SI1145_PARAM_CHLIST_ENPS3 = 0x04

SI1145_PARAM_PSLED12SEL = 0x02
SI1145_PARAM_PSLED12SEL_PS2NONE = 0x00
SI1145_PARAM_PSLED12SEL_PS2LED1 = 0x10
SI1145_PARAM_PSLED12SEL_PS2LED2 = 0x20
SI1145_PARAM_PSLED12SEL_PS2LED3 = 0x40
SI1145_PARAM_PSLED12SEL_PS1NONE = 0x00
SI1145_PARAM_PSLED12SEL_PS1LED1 = 0x01
SI1145_PARAM_PSLED12SEL_PS1LED2 = 0x02
SI1145_PARAM_PSLED12SEL_PS1LED3 = 0x04

SI1145_PARAM_PSLED3SEL = 0x03
SI1145_PARAM_PSENCODE = 0x05
SI1145_PARAM_ALSENCODE = 0x06

SI1145_PARAM_PS1ADCMUX = 0x07
SI1145_PARAM_PS2ADCMUX = 0x08
SI1145_PARAM_PS3ADCMUX = 0x09
SI1145_PARAM_PSADCOUNTER = 0x0A
SI1145_PARAM_PSADCGAIN = 0x0B
SI1145_PARAM_PSADCMISC = 0x0C
SI1145_PARAM_PSADCMISC_RANGE = 0x20
SI1145_PARAM_PSADCMISC_PSMODE = 0x04

SI1145_PARAM_ALSIRADCMUX = 0x0E
SI1145_PARAM_AUXADCMUX = 0x0F

SI1145_PARAM_ALSVISADCOUNTER = 0x10
SI1145_PARAM_ALSVISADCGAIN = 0x11
SI1145_PARAM_ALSVISADCMISC = 0x12
SI1145_PARAM_ALSVISADCMISC_VISRANGE = 0x20

SI1145_PARAM_ALSIRADCOUNTER = 0x1D
SI1145_PARAM_ALSIRADCGAIN = 0x1E
SI1145_PARAM_ALSIRADCMISC = 0x1F
SI1145_PARAM_ALSIRADCMISC_RANGE = 0x20

SI1145_PARAM_ADCCOUNTER_511CLK = 0x70

SI1145_PARAM_ADCMUX_SMALLIR = 0x00
SI1145_PARAM_ADCMUX_LARGEIR = 0x03

# REGISTERS
SI1145_REG_PARTID = 0x00
SI1145_REG_REVID = 0x01
SI1145_REG_SEQID = 0x02

SI1145_REG_INTCFG = 0x03
SI1145_REG_INTCFG_INTOE = 0x01
SI1145_REG_INTCFG_INTMODE = 0x02

SI1145_REG_IRQEN = 0x04
SI1145_REG_IRQEN_ALSEVERYSAMPLE = 0x01
SI1145_REG_IRQEN_PS1EVERYSAMPLE = 0x04
SI1145_REG_IRQEN_PS2EVERYSAMPLE = 0x08
SI1145_REG_IRQEN_PS3EVERYSAMPLE = 0x10

SI1145_REG_IRQMODE1 = 0x05
SI1145_REG_IRQMODE2 = 0x06

SI1145_REG_HWKEY = 0x07
SI1145_REG_MEASRATE0 = 0x08
SI1145_REG_MEASRATE1 = 0x09
SI1145_REG_PSRATE = 0x0A
SI1145_REG_PSLED21 = 0x0F
SI1145_REG_PSLED3 = 0x10
SI1145_REG_UCOEFF0 = 0x13
SI1145_REG_UCOEFF1 = 0x14
SI1145_REG_UCOEFF2 = 0x15
SI1145_REG_UCOEFF3 = 0x16
SI1145_REG_PARAMWR = 0x17
SI1145_REG_COMMAND = 0x18
SI1145_REG_RESPONSE = 0x20
SI1145_REG_IRQSTAT = 0x21
SI1145_REG_IRQSTAT_ALS = 0x01

SI1145_REG_ALSVISDATA0 = 0x22
SI1145_REG_ALSVISDATA1 = 0x23
SI1145_REG_ALSIRDATA0 = 0x24
SI1145_REG_ALSIRDATA1 = 0x25
SI1145_REG_PS1DATA0 = 0x26
SI1145_REG_PS1DATA1 = 0x27
SI1145_REG_PS2DATA0 = 0x28
SI1145_REG_PS2DATA1 = 0x29
SI1145_REG_PS3DATA0 = 0x2A
SI1145_REG_PS3DATA1 = 0x2B
SI1145_REG_UVINDEX0 = 0x2C
SI1145_REG_UVINDEX1 = 0x2D
SI1145_REG_PARAMRD = 0x2E
SI1145_REG_CHIPSTAT = 0x30

# I2C Address
SI1145_ADDR = 0x60

class SI1145(object):
    def __init__(self, address=SI1145_ADDR, busnum=I2C.get_default_bus()):
        self._logger = logging.getLogger('SI1145')

        # Create I2C device.
        self._device = I2C.Device(address, busnum)

        # reset device
        self._reset()

        # Load calibration values.
        self._load_calibration()

    # device reset
    def _reset(self):
        self._device.write8(SI1145_REG_MEASRATE0, 0)
        self._device.write8(SI1145_REG_MEASRATE1, 0)
        self._device.write8(SI1145_REG_IRQEN, 0)
        self._device.write8(SI1145_REG_IRQMODE1, 0)
        self._device.write8(SI1145_REG_IRQMODE2, 0)
        self._device.write8(SI1145_REG_INTCFG, 0)
        self._device.write8(SI1145_REG_IRQSTAT, 0xFF)

        self._device.write8(SI1145_REG_COMMAND, SI1145_RESET)
        time.sleep(.01)
        self._device.write8(SI1145_REG_HWKEY, 0x17)
        time.sleep(.01)

    # write Param
    def writeParam(self, p, v):
        self._device.write8(SI1145_REG_PARAMWR, v)
        self._device.write8(SI1145_REG_COMMAND, p | SI1145_PARAM_SET)
        paramVal = self._device.readU8(SI1145_REG_PARAMRD)
        return paramVal

    # load calibration to sensor
    def _load_calibration(self):
        # /***********************************/
        # Enable UVindex measurement coefficients!
        self._device.write8(SI1145_REG_UCOEFF0, 0x29)
        self._device.write8(SI1145_REG_UCOEFF1, 0x89)
        self._device.write8(SI1145_REG_UCOEFF2, 0x02)
        self._device.write8(SI1145_REG_UCOEFF3, 0x00)

        # Enable UV sensor
        self.writeParam(SI1145_PARAM_CHLIST,
                        SI1145_PARAM_CHLIST_ENUV | SI1145_PARAM_CHLIST_ENALSIR | SI1145_PARAM_CHLIST_ENALSVIS | SI1145_PARAM_CHLIST_ENPS1)

        # Enable interrupt on every sample
        self._device.write8(SI1145_REG_INTCFG, SI1145_REG_INTCFG_INTOE)
        self._device.write8(SI1145_REG_IRQEN, SI1145_REG_IRQEN_ALSEVERYSAMPLE)

        # /****************************** Prox Sense 1 */

        # Program LED current
        self._device.write8(SI1145_REG_PSLED21, 0x03)  # 20mA for LED 1 only
        self.writeParam(SI1145_PARAM_PS1ADCMUX, SI1145_PARAM_ADCMUX_LARGEIR)

        # Prox sensor #1 uses LED #1
        self.writeParam(SI1145_PARAM_PSLED12SEL, SI1145_PARAM_PSLED12SEL_PS1LED1)

        # Fastest clocks, clock div 1
        self.writeParam(SI1145_PARAM_PSADCGAIN, 0)

        # Take 511 clocks to measure
        self.writeParam(SI1145_PARAM_PSADCOUNTER, SI1145_PARAM_ADCCOUNTER_511CLK)

        # in prox mode, high range
        self.writeParam(SI1145_PARAM_PSADCMISC, SI1145_PARAM_PSADCMISC_RANGE | SI1145_PARAM_PSADCMISC_PSMODE)
        self.writeParam(SI1145_PARAM_ALSIRADCMUX, SI1145_PARAM_ADCMUX_SMALLIR)

        # Fastest clocks, clock div 1
        self.writeParam(SI1145_PARAM_ALSIRADCGAIN, 0)

        # Take 511 clocks to measure
        self.writeParam(SI1145_PARAM_ALSIRADCOUNTER, SI1145_PARAM_ADCCOUNTER_511CLK)

        # in high range mode
        self.writeParam(SI1145_PARAM_ALSIRADCMISC, SI1145_PARAM_ALSIRADCMISC_RANGE)

        # fastest clocks, clock div 1
        self.writeParam(SI1145_PARAM_ALSVISADCGAIN, 0)

        # Take 511 clocks to measure
        self.writeParam(SI1145_PARAM_ALSVISADCOUNTER, SI1145_PARAM_ADCCOUNTER_511CLK)

        # in high range mode (not normal signal)
        self.writeParam(SI1145_PARAM_ALSVISADCMISC, SI1145_PARAM_ALSVISADCMISC_VISRANGE)

        # measurement rate for auto
        self._device.write8(SI1145_REG_MEASRATE0, 0xFF)  # 255 * 31.25uS = 8ms

        # auto run
        self._device.write8(SI1145_REG_COMMAND, SI1145_PSALS_AUTO)

    # returns the UV index * 100 (divide by 100 to get the index)
    def readUV(self):
        return self._device.readU16LE(0x2C)

    # returns visible + IR light levels
    def readVisible(self):
        return self._device.readU16LE(0x22)

    # returns IR light levels
    def readIR(self):
        return self._device.readU16LE(0x24)

    # Returns "Proximity" - assumes an IR LED is attached to LED
    def readProx(self):
        return self._device.readU16LE(0x26)

try:
    # Measure UV
    sensor = SI1145()
    uvIndex = sensor.readUV() / 100.0

    # SAVE
    data = {
        "time": datetime.datetime.now().isoformat(),
        "uv": uvIndex
    }
    json_data = json.dumps(data, indent=4)

    f = open('/var/www/sensors.json', 'w')
    f.write(json_data)
    f.close()

    print('Success')
except IOError as err:
    print('Error: ' + str(err))

Nachdem das Skript beispielsweise unter /opt/read_uv_index.py abgelegt ist, muss die Crontab des Raspberry Pi angepasst werden, damit das Skript jede Minute ausgeführt wird. So wird die JSON-Datei, die den aktuellen UV-Index enthält, kontinuierlich aktualisiert.

In die Datei /etc/crontab einfügen und darauf achten, dass am Ende der Datei eine leere Zeile vorhanden ist:

*  *    * * *   username    /usr/bin/python3 /opt/read_uv_index.py > /dev/null 2>&1

Dabei sollte der Standard-Nutzer genommen werden (hier username), dem auch das Python-Skript gehört.

Das Skript sollte nun in der nächsten Minute laufen und eine JSON-Datei unter /var/www/sensors.json ablegen.

Falls Fehler auftreten, kann das Skript jederzeit manuell mit dem Befehl aus der crontab aufgerufen werden.

Außerhalb des Raspberry Pi lassen sich die Daten nun über jeden Browser abrufen, indem man die JSON Datei über den Webserver abruft: http://ip-vom-raspberry-pi/sensors.json.

Integration in Weewx

Nun muss der UV-Index weewx bereitgestellt werden. Bei mir ist dies ein weiterer Linux-Server, auf dem weewx läuft und wo per USB eine Wetterstation angeschlossen ist.

Hier muss ein Python-Skript abgelegt werden. Dieses Skript ist verantwortlich für das Herunterladen der JSON-Datei vom Raspberry Pi und das Speichern des UV-Index in der weewx-Datenbank.

Im Ordner /usr/share/weewx/user als root die Datei raspisensors.py ablegen. Inhalt des Skripts:

import json
import syslog
import weewx
import urllib.request
from weewx.wxengine import StdService

class RaspisensorsService(StdService):
    def __init__(self, engine, config_dict):
        super(RaspisensorsService, self).__init__(engine, config_dict)
        d = config_dict.get('RaspisensorsService', {})
        self.bind(weewx.NEW_ARCHIVE_RECORD, self.read_file)
    def read_file(self, event):
        try:
            with urllib.request.urlopen("http://ip-vom-raspberry-pi/sensors.json") as url:
                value = url.read()
                sensors = json.loads(value)

                if "uv" in sensors:
                    event.record['UV'] = float(sensors['uv'])

        except Exception as e:
            syslog.syslog(syslog.LOG_ERR, "Cannot read sensors: " + str(e))

Im Skript muss die IP-Adresse des Raspberry PI angepasst werden. Das Skript läuft bei jedem neuen Archive Record, welcher von der Wetterstation angelegt wird, und ergänzt den neuen Datensatz um den UV-Index, wenn dieser Wert in der JSON-Datei vorhanden ist.

Damit das Skript auch weewx bekannt ist, ist die weewx.conf anzupassen. Dort ganz unten in der [Engine] Section, der [[Services]] Sub-Section unter data_services diesen Wert ergänzen (oder falls nicht vorhanden, auf diesen Wert setzen): user.raspisensors.RaspisensorsService. Der Abschnitt sieht bei mir auszugsweise so aus:

[Engine]
    # The following section specifies which services should be run and in what order.
    [[Services]]
        prep_services = weewx.engine.StdTimeSynch
        data_services = user.raspisensors.RaspisensorsService
        process_services = ...
        ...

Jetzt muss weewx nur noch neugestartet werden, und der UV-Index wird erfasst: systemctl restart weewx.

Fazit

Die Integration des SI1145 UV-Sensors mit einem Raspberry Pi und weewx ist nicht nur eine interessante technische Aufgabe, sondern auch eine hochfunktionale Ergänzung zu jeder Wetterstation. Durch die präzise Erfassung und Bereitstellung des UV-Index verbessert diese Konfiguration die Datenqualität und bietet gleichzeitig eine kosteneffektive Lösung für ein zunehmend wichtiges Anwendungsgebiet.

Die Skripte sind so angelegt, dass auch weitere Sensoren einfach ergänzt werden können. So handhaben wir dies beispielsweise mit einem Lux-Sensor.

Der SI1145 liefert uns jetzt jede Minute den aktuellen UV-Index. Da hierbei keine Trägheit oder ähnliches vorhanden ist, wie bei manch kommerziellem Sensor, sehen wir Spikes und Peaks und können so auch recht gut beurteilen, wie viel direkte Sonne an einem Tag vorhanden war und wann beispielsweise Wolken diese verdeckt haben.

Der UV-Index bei unserer Wetterstation funktioniert genau so und liefert bisher immer zufriedenstellende und realistische Werte, und dies für wenige Euro.

Haben Sie Fragen zu diesem Thema oder benötigen Sie weitere Unterstützung? Wir laden Sie herzlich ein, Ihre Gedanken und Fragen in den Kommentaren zu teilen.

Dieser Beitrag wurde mit Unterstützung von künstlicher Intelligenz (GPT-4) erstellt. Titelfoto von Andrey Grinkevich auf Unsplash.


Sven
Über den Autor

Sven Reifschneider

Gude! Ich bin der Gründer und Geschäftsführer der Neoground GmbH, IT-Visionär und leidenschaftlicher Fotograf. Hier teile ich meine Expertise und Begeisterung für innovative IT-Lösungen, die Unternehmen in der digitalen Ära voranbringen, verflechte sie mit meiner Leidenschaft für das Visuelle und öffne den Vorhang zu einem Universum, in dem Pixel und Ästhetik gleichermaßen zuhause sind.

Mit einem Standbein in der malerischen Wetterau unweit von Frankfurt und einem Blick, der stets über den Horizont hinausgeht, lade ich Sie ein, gemeinsam die Facetten der digitalen Transformation und neuester Technologien zu entdecken. Sind Sie bereit, den nächsten Schritt in die digitale Zukunft zu gehen? Folgen Sie dem Pfad der Neugier und lassen Sie uns gemeinsam Innovationen gestalten.


2 Kommentare

Kommentar hinzufügen

In deinem Kommentar können Sie **Markdown** nutzen. Ihre E-Mail-Adresse wird nicht veröffentlicht. Mehr zum Datenschutz finden Sie in der Datenschutzerklärung.

18. März 2024, 08:01 Uhr
W. Spitzner

Hallo,
super Anleitung, doch bei mir gibt es Probleme.
Import weewx gibt aus : no Module weewx
Import urllib.request: No module named request

können Sie mir sagen wo das Problem liegt?
Ich benutze Weewx 4.4.0
Danke

20. März 2024, 11:20 Uhr
Sven Reifschneider

Hallo und vielen Dank für den Kommentar!

Wie es scheint, versuchen Sie das Skript raspisensors.py auszuführen. Diese Python-Datei ist jedoch als Plugin für weewx gedacht und läuft nur innerhalb von weewx, nachdem die Datei in der weewx.conf hinterlegt und weewx neu gestartet wurde. Jedes Mal, wenn ein neuer Eintrag mit den Werten der Wetterstation in die Datenbank geschrieben wird, wird das Skript ausgeführt, welches den Wert für das "UV" Feld hinterlegt.

Außerdem scheint es, dass in der Python-Umgebung urllib nicht verfügbar ist. Unter Debian müsste daher wahrscheinlich noch das Paket python3-urllib3 installiert werden.