Python coding

Ecowitt to MQTT: Get the data from the weather station

last updated: 2022-01-21

Short description

My old weather station died. So I got a new one, this time with WiFi (instead of USB).
It's named WH3000 SE PRO, from froggit.de, but can also be found under other brands.

WH3000 SE PRO out     WH3000 SE PRO in

The station can upload the weather data to my own weather server using the ecowitt protocol. As I want to use the data in OpenHAB, I wrote a little Python script to parse the data and send it to my MQTT server.

Installation

All the infos needed can be found in the manual of the station. After powering up the station generates an Access Point. With the help of an App we can connect to our WiFi router and configure the server IP address and the port where the data should be send. I choose one minute as time interval.

One of my running Raspberry Pi servers get's the data on a free port (8000).

The python script is started on boot in etc/rc.local as background process:

    python3 /home/pi/weather_ecowitt_2_mqtt.py &

Another solution would be to start a cron job at reboot.

The python script

With the help of a little Python program I could sniff the data on the port. Here a screenshot how the data looks like:

ecowitt data
click for a better view

Now it's easy to parse the data and doing a bit of calculation to get it right (e.g. Fahrenheit! to °C). The data is saved in a python dictionary which makes it easy to send a Json string over MQTT.

    #!/usr/bin/env python3
    # -*- coding: utf-8 -*-
    """
    ###############################################################################
    #
    #  Name:         weather_ecowitt_2_mqtt.py
    #  Purpose:      Get the ecowitt weather data on port 8000 and send data to
    #                the mqtt server
    #  Author:       weigu.lu
    #  Date:         2021-03-15
    #  Version       1.0
    #
    #  Copyright 2020 weigu <weigu@weigu.lu>
    #
    #  This program is free software; you can redistribute it and/or modify
    #  it under the terms of the GNU General Public License as published by
    #  the Free Software Foundation; either version 2 of the License, or
    #  (at your option) any later version.
    #
    #  This program is distributed in the hope that it will be useful,
    #  but WITHOUT ANY WARRANTY; without even the implied warranty of
    #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    #  GNU General Public License for more details.
    #
    #  You should have received a copy of the GNU General Public License
    #  along with this program; if not, write to the Free Software
    #  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
    #  MA 02110-1301, USA.
    #
    ###############################################################################
    """
    # pylint: disable-msg=C0103
    # pylint: disable-msg=R0912
    # pylint: disable-msg=R0914
    # pylint: disable-msg=R0915
    # pylint: disable-msg=W0105

    import socket
    import sys
    from time import strftime, localtime
    from datetime import datetime
    import paho.mqtt.client as mqtt


    HOST = ''   # empty: all interfaces
    PORT = 8000

    # MQTT things
    MQTT_CLIENT_ID = "weatherstation_" + strftime("%s", localtime()) # must be unique!# must be unique!
    MQTT_SERVER_IP = "192.168.1.60" # 127.0.0.1 if mosquitto is running on same server as python script
    MQTT_SERVER_PORT = 1883
    MQTT_TOPIC = "weigu/garden/garden/weatherstation"

    #---- Functions ------------------------------------------------------------
    def on_connect(_client, _userdata, _flags, result_code):
        """Callback that is executed when the client receives a CONNACK response
           from the server. Subscribe to the topic (topic name, QoS)."""
        print("Connected with result code "+str(result_code))
        mqtt_client.subscribe(MQTT_TOPIC, 0)

    def on_disconnect(_client, _userdata, _message):
        """Callback that is executed when we disconnect from the broker."""
        print("Disconnected from the broker.")

    def on_subscribe(_client, _userdata, _mid, _granted_qos):
        """Callback that is executed when subscribing to a topic."""
        print('Subscribed on topic.')

    def on_unsubscribe(_client, _userdata, _mid, _granted_qos):
        """Callback that is executed when unsubscribing to a topic."""
        print('Unsubscribed on topic.')

    def on_message(_client, _userdata, message):
        """Callback that is executed when a message is received."""
        print('Message received.')

    def parse_ecowitt_data(data):
        """Parse the ecowitt data and add it to a dictionnary."""
        data_dict = {}          # create empty dictionary
        data_list = data.decode().split('&')
        #print(data_list)
        for item in data_list:
            if item.find("dateutc") != -1:
                ws_datetime = item[item.find('=')+1:].replace('+', 'T')
                data_dict['datetime'] = ws_datetime
            if item.find("tempinf") != -1:
                temp_in = round((float(item[item.find('=')+1:])-32.0) * 5/9, 2) #from F to C
                data_dict['Temperature_in_[C]'] = temp_in
            if item.find("humidityin") != -1:
                hum_in = int(item[item.find('=')+1:])
                data_dict['Humidity_in_[%]'] = hum_in
            if item.find("baromrelin") != -1:
                barometer_rel_in = round((float(item[item.find('=')+1:])*33.86389), 2) #inch_mercury-hpa
                data_dict['Barometer_rel_in_[hpa]'] = barometer_rel_in
            if item.find("baromabsin") != -1:
                barometer_abs_in = round((float(item[item.find('=')+1:])*33.86389), 2) #inch_mercury-hpa
                data_dict['Barometer_abs_in_[hpa]'] = barometer_abs_in
            if item.find("tempf") != -1:
                temp_out = round((float(item[item.find('=')+1:])-32.0) * 5/9, 2)
                data_dict['Temperature_out_[C]'] = temp_out
            if item.find("humidity") != -1:
                hum_out = int(item[item.find('=')+1:])
                data_dict['Humidity_out_[%]'] = hum_out
            if item.find("winddir") != -1:
                wind_dir = int(item[item.find('=')+1:])
                data_dict['Wind_direction_[degree]'] = wind_dir
            if item.find("windspeedmph") != -1:
                wind_speed = round(float(item[item.find('=')+1:])*1.609344, 2)
                data_dict['Wind_speed_[km/h]'] = wind_speed
            if item.find("windgustmph") != -1:
                wind_gust_speed = round(float(item[item.find('=')+1:])*1.609344, 2)
                data_dict['Wind_gust_speed_[km/h]'] = wind_gust_speed
            if item.find("rainratein") != -1:
                rain_rate_in = float(item[item.find('=')+1:])
                data_dict['Rain_rate_[]'] = rain_rate_in
            if item.find("eventrainin") != -1:
                rain_event_in = float(item[item.find('=')+1:])
                data_dict['Rain_event_[]'] = rain_event_in
            if item.find("hourlyrainin") != -1:
                rain_hourly_in = float(item[item.find('=')+1:])
                data_dict['Rain_hourly_[]'] = rain_hourly_in
            if item.find("dailyrainin") != -1:
                rain_daily_in = float(item[item.find('=')+1:])
                data_dict['Rain_daily_[]'] = rain_daily_in
            if item.find("weeklyrainin") != -1:
                rain_weekly_in = float(item[item.find('=')+1:])
                data_dict['Rain_weekly'] = rain_weekly_in
            if item.find("monthlyrainin") != -1:
                wind_gust_speed = float(item[item.find('=')+1:])
                data_dict['Rain_monthly_[]'] = wind_gust_speed
            if item.find("totalrainin") != -1:
                wind_gust_speed = float(item[item.find('=')+1:])
                data_dict['Rain_total_[]'] = wind_gust_speed
            if item.find("solarradiation") != -1:
                solar_radiation = float(item[item.find('=')+1:])
                data_dict['Solar_radiation_[W/m^2]'] = solar_radiation
            if item.find("uv") != -1:
                uv_radiation = float(item[item.find('=')+1:])
                data_dict['UV_[km/h]'] = uv_radiation
        # Calculate own rel baro from height
        hasl = 240 # Height above sea level in m
        aap = barometer_abs_in
        atc = temp_out
        baro_rel_in_calc = round(aap + ((aap * 9.80665 * hasl)/(287 * (273 + atc + (hasl/400)))), 2)
        data_dict['Barometer_rel_in_calc_[hpa]'] = baro_rel_in_calc
        return data_dict

    #---- MAIN -----------------------------------------------------------------

    mqtt_client = mqtt.Client(client_id=MQTT_CLIENT_ID, clean_session=True)

    def main():
        """main"""
        print("Program started at ", datetime.now().isoformat('T', 'seconds'))
        # define the callback functions
        mqtt_client.on_connect = on_connect
        mqtt_client.on_disconnect = on_disconnect
        mqtt_client.on_subscribe = on_subscribe
        mqtt_client.on_unsubscribe = on_unsubscribe
        mqtt_client.on_message = on_message
        # connect to the broker
        mqtt_client.connect(MQTT_SERVER_IP, MQTT_SERVER_PORT, keepalive=60,
                            bind_address="")
        # start loop to process callbacks! (new thread!)
        mqtt_client.loop_start()

        my_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        print('Socket created')
        try:
            my_socket.bind((HOST, PORT))
        except socket.error:
            print('Binding failed')
            sys.exit()
        print('Binding socket completed')
        my_socket.listen(3) # listening on socket (incoming connections we're willing to queue)
        print('Listening on socket')

        try:
            while True:
                now = datetime.now()
                now_time = now.time()
                print(now_time)
                conn, addr = my_socket.accept() # wait for connection (blocking call)
                with conn:
                    print('Connected with ' + addr[0] + ':' + str(addr[1]))
                    while True:
                        data = conn.recv(1024)
                        if not data:
                            break
                        #print(data)                    
                        message = parse_ecowitt_data(data)
                        message = str(message).replace("'", '\"')
                        print(message)
                        mqtt_client.publish(MQTT_TOPIC, message, qos=0, retain=False)
            my_socket.close()
        except KeyboardInterrupt:
            print("Keyboard interrupt by user")
            mqtt_client.loop_stop() # clean up
            mqtt_client.disconnect()
            my_socket.close()

    main()

Downloads