How to integrate a ELV WDE1 in HA

Hello,
I have a weather station with 8 room sensors from ELV and a WDE1 which is connected via USB to a computer.

https://de.elv.com/elv-usb-wetterdaten-empfaenger-usb-wde1-2-komplettbausatz-104657.

On my old windows based pc i run a powershell script, which read the data from the serial port and store the data in a sqlite database.

Now I want to connect this to a raspi, running HA.

Who can help me?

It took some time, but now I have wrote a python script, to read the data from a serial port and publish these via mqtt to HA.

In configuration.yaml some sensors have to be defined.

mqtt:
  sensor:
    #ELV Sensoren =========================================
    # Schlafzimmer
    - name: "Schlafzimmer Temperatur"
      state_topic: "stat/Elv/Schlafzimmer/Temperatur"
      icon: mdi:thermometer
      state_class: measurement
      device_class: temperature
      unit_of_measurement: "°C"

    - name: "Schlafzimmer Feuchtigkeit"
      state_topic: "stat/Elv/Schlafzimmer/Feuchtigkeit"
      icon: mdi:cloud-percent
      state_class: measurement
      device_class: humidity
      unit_of_measurement: "%"

    # HomeOffice
    - name: "HomeOffice Temperatur"
      state_topic: "stat/Elv/HomeOffice/Temperatur"
      icon: mdi:thermometer
      state_class: measurement
      device_class: temperature
      unit_of_measurement: "°C"

    - name: "HomeOffice Feuchtigkeit"
      state_topic: "stat/Elv/HomeOffice/Feuchtigkeit"
      icon: mdi:cloud-percent
      state_class: measurement
      device_class: humidity
      unit_of_measurement: "%"

    # Kinderzimmer
    - name: "Kinderzimmer Temperatur"
      state_topic: "stat/Elv/Kinderzimmer/Temperatur"
      icon: mdi:thermometer
      state_class: measurement
      device_class: temperature
      unit_of_measurement: "°C"

    - name: "Kinderzimmer Feuchtigkeit"
      state_topic: "stat/Elv/Kinderzimmer/Feuchtigkeit"
      icon: mdi:cloud-percent
      state_class: measurement
      device_class: humidity
      unit_of_measurement: "%"

    # Bad Oben
    - name: "Bad Oben Temperatur"
      state_topic: "stat/Elv/Bad Oben/Temperatur"
      icon: mdi:thermometer
      state_class: measurement
      device_class: temperature
      unit_of_measurement: "°C"

    - name: "Bad Oben Feuchtigkeit"
      state_topic: "stat/Elv/Bad Oben/Feuchtigkeit"
      icon: mdi:cloud-percent
      state_class: measurement
      device_class: humidity
      unit_of_measurement: "%"

    # Küche
    - name: "Küche Temperatur"
      state_topic: "stat/Elv/Küche/Temperatur"
      icon: mdi:thermometer
      state_class: measurement
      device_class: temperature
      unit_of_measurement: "°C"

    - name: "Küche Feuchtigkeit"
      state_topic: "stat/Elv/Küche/Feuchtigkeit"
      icon: mdi:cloud-percent
      state_class: measurement
      device_class: humidity
      unit_of_measurement: "%"

    # Esszimmer
    - name: "Esszimmer Temperatur"
      state_topic: "stat/Elv/Esszimmer/Temperatur"
      icon: mdi:thermometer
      state_class: measurement
      device_class: temperature
      unit_of_measurement: "°C"

    - name: "Esszimmer Feuchtigkeit"
      state_topic: "stat/Elv/Esszimmer/Feuchtigkeit"
      icon: mdi:cloud-percent
      state_class: measurement
      device_class: humidity
      unit_of_measurement: "%"

    # Wohnzimmer
    - name: "Wohnzimmer Temperatur"
      state_topic: "stat/Elv/Wohnzimmer/Temperatur"
      icon: mdi:thermometer
      state_class: measurement
      device_class: temperature
      unit_of_measurement: "°C"

    - name: "Wohnzimmer Feuchtigkeit"
      state_topic: "stat/Elv/Wohnzimmer/Feuchtigkeit"
      icon: mdi:cloud-percent
      state_class: measurement
      device_class: humidity
      unit_of_measurement: "%"

    # Bad unten
    - name: "Bad Unten Temperatur"
      state_topic: "stat/Elv/Bad Unten/Temperatur"
      icon: mdi:thermometer
      state_class: measurement
      device_class: temperature
      unit_of_measurement: "°C"

    - name: "Bad Unten Feuchtigkeit"
      state_topic: "stat/Elv/Bad Unten/Feuchtigkeit"
      icon: mdi:cloud-percent
      state_class: measurement
      device_class: humidity
      unit_of_measurement: "%"

    # Außen-Kombisensor
    - name: "Außen (ELV)"
      state_topic: "stat/Elv/Kombisensor/Temperatur"
      icon: mdi:thermometer
      state_class: measurement
      device_class: temperature
      unit_of_measurement: "°C"

    - name: "Außen Feuchtigkeit"
      state_topic: "stat/Elv/Kombisensor/Feuchtigkeit"
      icon: mdi:cloud-percent
      state_class: measurement
      device_class: humidity
      unit_of_measurement: "%"

    - name: "Windgeschwindigkeit"
      state_topic: "stat/Elv/Kombisensor/Windgeschwindigkeit"
      icon: mdi:weather-windy
      state_class: measurement
      device_class: wind_speed
      unit_of_measurement: "km/h"

    - name: "Wippenschläge"
      state_topic: "stat/Elv/Kombisensor/Wippenschläge"
      icon: mdi:weather-rainy
      state_class: measurement

    - name: "Regen"
      state_topic: "stat/Elv/Kombisensor/Regen"
      icon: mdi:weather-rainy
      state_class: measurement

    - name: "Regenmenge"
      state_topic: "stat/Elv/Kombisensor/Regenmenge"
      icon: mdi:weather-rainy
      state_class: measurement

    - name: "Tagesregenmenge"
      state_topic: "stat/Elv/Kombisensor/Tagesregenmenge"
      icon: mdi:weather-rainy
      state_class: measurement

    - name: "Heartbeat (ELV)"
      unique_id: "Heartbeat ELV"
      state_topic: "stat/Elv/Heartbeat"
      icon: mdi:heart-pulse

ELVWde1.py (which is actually runs on a windows pc)

#!/usr/bin/python3
# -*-coding:Utf-8 -*

# Programm, dass die Wetterdaten vom ELV WDE1 ausliest
# Programm erstellt von René Janaczeck
# Version: 06.01.2024 20:46

# USB-Wetterdatenempfänger USB-WDE1

# Der USB-WDE1 kann die Daten aller folgend aufgeführten Wettersensoren empfangen.
# - Funk-Kombi-Sensor KS 300
# - Funk-Kombi-Sensor KS 200
# - Funk-Temperatursensor S 300 IA, Sensor abgesetzt
# - Funk-Temperatur- und Luftfeuchtesensor S 300 TH, Sensoren intern
# - Funk-Temperatur- und Luftfeuchtesensor ASH 2200, Sensoren intern
# - Funk-Pool-Sensor PS 50

# Die Wettersensoren senden ihre Daten unregelmäßig alle 2,5 bis 3 Minuten.
# Kleinste mögliche Temperatur ist -29,9 [°C] (je nach Sensor kann diese aber nicht erreicht werden – der PS50 geht z. B. nur bis 0°C).
# Größte mögliche Temperatur ist +79,9 [°C] (je nach Sensor kann diese aber nicht erreicht werden – der PS50 geht z. B. nur bis +69,9°C).
# Der Wertebereich für die Luftfeuchtigkeit geht von 0 bis 99 [%] (ohne Nachkommastelle)
# Der Wertebereich für die Windgeschwindigkeit geht von 0,0 bis 199,9 [km/h].
# Signalisiert die Sofort-Erkennung des Kombisensors „Regen“, ist der entsprechende Wert gleich 1, ansonsten ist dieser 0.

# Der WDE1 liefert Daten entweder im OpenFormat (*) oder im Textformat
# LogView Format
# '$1;1;;22,8;22,8;22,7;22,9;20,6;20,6;21,4;20,0;42;35;43;41;42;41;41;41;1,0;88;2,5;503;0;0\r\n'
#  - $1;1;Zeitstempel (ohne);                   # Startsequenz
#  - Temp Sensor1; ...; Temp Sensor8;           # Zuerst 8 Temperaturen
#  - Feuchte Sensor1; ...; Feuchte Sensor8;     # dann 8 Feuchtigkeiten
#  - Temp. Kombisensor; Feuchte Kombisensor; Windgeschwindigkeit; Niederschlag (Wippenschläge); Regen (Ja=1, Nein=0); Stopzeichen 0<cr><lf>
#  - 0\r\n                                      # abschließende Zeichenfolge

# Textformat
# Sensor 1: 21,2 C; 37 %
# ...
# Sensor 8: 22,4 C
# Kombi-S.: 16,0 C; 42 %; 8,0 km/h; 455 Takte; Regen: Ja

# Dieses Script setzt das LogView / OpenFormat voraus.

import serial
from datetime import datetime
import time 
import logging
import paho.mqtt.client as mqtt

# Comport Parameter
COMPORT='COM6'
#BAUDRATE=9600 # Default Einstellung
BAUDRATE=38400 # Muss auf dem WDE1 eingestellt werden, damit die Baudrate genutzt werden kann
TimeOut = 30
Parity='N'     
DataBits=8
StopBits=1

# MQTT Broker Parameter
Mqtt_Broker_Address = '192.168.1.2'
Mqtt_Port = 1883
Mqtt_User = "mqttuser"
Mqtt_Password = "mqttpassword"

LogFileName = 'C:\\Messwerte\\ELV USB-WDE1\\ELV.log'

# In welchem Intervall sollen die Daten gelesen werden?
# Min 150 - 180 Sekunden, siehe Text oben
Intervall = 300

# List für die MQTT Topics
# wird später ergänzt mit den Werten
ElvMqttTopics = [
    'stat/Elv/Schlafzimmer/Temperatur', 
    'stat/Elv/HomeOffice/Temperatur',
    'stat/Elv/Kinderzimmer/Temperatur',
    'stat/Elv/Bad Oben/Temperatur',
    'stat/Elv/Küche/Temperatur',
    'stat/Elv/Esszimmer/Temperatur',
    'stat/Elv/Bad Unten/Temperatur',
    'stat/Elv/Wohnzimmer/Temperatur',
    'stat/Elv/Schlafzimmer/Feuchtigkeit',
    'stat/Elv/HomeOffice/Feuchtigkeit',
    'stat/Elv/Kinderzimmer/Feuchtigkeit',
    'stat/Elv/Bad Oben/Feuchtigkeit',
    'stat/Elv/Küche/Feuchtigkeit',
    'stat/Elv/Esszimmer/Feuchtigkeit',
    'stat/Elv/Bad Unten/Feuchtigkeit',
    'stat/Elv/Wohnzimmer/Feuchtigkeit',
    'stat/Elv/Kombisensor/Temperatur',
    'stat/Elv/Kombisensor/Feuchtigkeit',
    'stat/Elv/Kombisensor/Windgeschwindigkeit',
    'stat/Elv/Kombisensor/Wippenschläge',
    'stat/Elv/Kombisensor/Regen',
    'stat/Elv/Kombisensor/Regenmenge',
    'stat/Elv/Kombisensor/Tagesregenmenge'
]

# Dieses Topic dient als Heartbeat, wann das letzte Mal die Daten gesendet wurden
HeartBeat_Topic = 'stat/Elv/Heartbeat' 

# Dient zur Ausgabe in einer Log-Datei
def log(what):
    logging.info(what)

# Send Data to MQTT Broker
def Publish_MQTT_Data(Topic, Data):
    # Verbindung zum MQTT Broker herstellen
    Mqtt_Client = mqtt.Client()
    Mqtt_Client.username_pw_set(Mqtt_User, Mqtt_Password)
    
    try:
        Mqtt_Client.connect(Mqtt_Broker_Address, Mqtt_Port)
    except Exception as e:
        log('Connection Error while trying to connect to MQTT Broker, ' + str(e))
        pass

    # Send Data to MQTT Broker
    try:
        Mqtt_Client.publish(topic=Topic, payload=Data, qos=1, retain=True)
        # Im Protokoll ausgeben
        log('Published ' + str(Topic) + ': ' + str(Data))
    except Exception as e:
        log("Can't publish MQTT Message to " + str(Topic) + ' with Data ' + str(Data) + ', ' + str(e))

    # Close Connection to MQTT Broker
    Mqtt_Client.disconnect()

class ElvWde1():
    # Klasse für den Elv WDE1 Wetterdatenempfänger 
    # Die Befehle müssen als Text (ASCII-Zeichen) zum USB-WDE1 gesendet werden. 
    # Verbindungsparameter siehe oben

    def __init__(self): 
        #log('Initialize Class ElvWde1')
        pass
    
    def OpenPort(self):
        #log('Try to open Comport ' + str(COMPORT))
        try:
            SerialPort = serial.Serial(port=COMPORT, baudrate=BAUDRATE, bytesize=DataBits, timeout=TimeOut, parity=Parity, stopbits=StopBits) 
        except serial.SerialException as var :
            log("Can't open Comport " + str(COMPORT))
            return False
        else:
            log('Serial Port ' + COMPORT + ' opened ...')
            return SerialPort

    def ClosePort(self, SerialPort):
        if (SerialPort.is_open == True):
            SerialPort.close()
            log('Serial Port ' + str(COMPORT) + ' closed.')

    def ReadDataAndPublish(self):
        serPort = self.OpenPort()
        if (serPort != False and serPort.is_open == True):
            # Max 30 sek versuchen, Daten zu lesen, damit nicht ewig die Schleife läuft
            # insbesondere wenn die Daten nicht mit '$1;1;;' starten
            maxTime = time.time() + 30

            # Daten von der seriellen Schnittstelle lesen
            #log('Try to read data from Comport ' + str(COMPORT))
            data = serPort.readline()

            # Daten wurden gelesen, starten diese auch mit '$1;1;;' ?
            # Es kommt gelegentlich vor, dass diese nicht mit '$1;1;;' starten,
            # dann erneut versuchen, wenn nicht maxTime abgelaufen ist ...
            while time.time() < maxTime and not (data.startswith(b'$1;1;;')):
                log(str(data) + " data starts not with b'$1;1;;', try again ...")
                data = serPort.readline()

            #log('Data from Serial Port ' + str(COMPORT) + ', ' + str(data))
            
            # seriellen Port wieder schließen
            self.ClosePort(serPort)
            
            # Daten in Ascii umwandeln
            ElvData = data.decode('Ascii')
            
            # $1;1;'' entfernen 
            ElvData = ElvData.replace('$1;1;;','')

            # '0\r\n' entfernen 
            ElvData = ElvData.replace('0\r\n','')

            # Kommatas in Punkte umwandeln, damit Home Assistant damit arbeiten kann
            ElvData = ElvData.replace(',','.')

            # Daten in ein Array umwandeln, damit sie sich leichter verarbeiten lassen
            ElvData = ElvData.split(';')

            # ElvMqttTopics und Daten zusammen bringen (zip) und in ein Dictonary (dict) umwandeln
            # => Key (MQTT Topic) = value
            ElvDict = dict(zip(ElvMqttTopics, ElvData))
            #log(str(ElvDict))
            
            # In einer Schleife key (MQTT Topic) und Value ermitten und dann per MQTT publishen
            for key, value in ElvDict.items():
                Publish_MQTT_Data(Topic=key, Data=value)

            # Heartbeat publishen, damit man weiß, wann zuletzt gepublisht wurde
            # Dient zur Kontrolle, ob das Script noch läuft/Daten liefert    
            Publish_MQTT_Data(Topic=HeartBeat_Topic, Data=datetime.now().strftime("%d.%m.%Y %H:%M"))    


# Starte das Programm

# Logging konfigurieren
logging.basicConfig(format='%(asctime)s %(message)s', datefmt='%d.%m.%Y %H:%M:%S ', level=logging.INFO, filename=LogFileName)

# Abbruchbedingung für die While Schleife
Ende = False

# Class ElvWde1 instanzieren
a = ElvWde1()

# regelmäßig Daten vom serial port lesen
try:
    while not Ende:
        # Daten vom serial Port lesen und publishen
        a.ReadDataAndPublish()

        # Pause, für Intervall Sekunden, siehe oben
        time.sleep(Intervall) 
except (KeyboardInterrupt,SystemExit):
    # Wenn Strg-C oder ein Exit auftritt, Schleife beenden
    ende=True
    pass

log ('Programm beendet')