Handling data from Ambient Weather WS-2902C API to MQTT
As I mentioned in my initial Ambient Weather WS-2902C post, there is a new feature that allows sending data to a custom server. I coded up a python script to take the sent data, and publish it to MQTT. This allows for super easy data ingestion with Home Assistant and other similar solutions. I should probably publish on GitHub but I’ll post here first.
Installation
This script is reliant on Paho-MQTT. EDIT: during the creation of the service to run this at boot, I discovered version 1.5.1 will throw errors. Use version 1.5.0. Install it with pip:
sudo pip install paho-mqtt==1.5.0
Create a python file and name it main.py. Paste in the following:
# Python script to decode Ambient Weather data (from WS-2902C and similar) # and publish to MQTT. # original author: Austin of austinsnerdythings.com # publish date: 2021-03-20 # some resources I used include #https://askubuntu.com/questions/29152/how-do-i-use-python-with-apache2 #https://www.toptal.com/python/pythons-wsgi-server-application-interface #https://www.emqx.io/blog/how-to-use-mqtt-in-python from urllib.parse import urlparse, parse_qs import paho.mqtt.client as mqtt import time, os # set MQTT vars MQTT_BROKER_HOST = os.getenv('MQTT_BROKER_HOST',"mqtt") MQTT_BROKER_PORT = int(os.getenv('MQTT_BROKER_PORT',1883)) MQTT_CLIENT_ID = os.getenv('MQTT_CLIENT_ID',"ambient_weather_decode") MQTT_USERNAME = os.getenv('MQTT_USERNAME',"") MQTT_PASSWORD = os.getenv('MQTT_PASSWORD',"") # looking to get resultant topic like weather/ws-2902c/[item] MQTT_TOPIC_PREFIX = os.getenv('MQTT_TOPIC_PREFIX',"weather") MQTT_TOPIC = MQTT_TOPIC_PREFIX + "/ws-2902c" # mostly copied + pasted from https://www.emqx.io/blog/how-to-use-mqtt-in-python and some of my own MQTT scripts def on_connect(client, userdata, flags, rc): if rc == 0: print(f"connected to MQTT broker at {MQTT_BROKER_HOST}") else: print("Failed to connect, return code %d\n", rc) def on_disconnect(client, userdata, flags, rc): print("disconnected from MQTT broker") # set up mqtt client client = mqtt.Client(client_id=MQTT_CLIENT_ID) if MQTT_USERNAME and MQTT_PASSWORD: client.username_pw_set(MQTT_USERNAME,MQTT_PASSWORD) print("Username and password set.") client.will_set(MQTT_TOPIC_PREFIX+"/status", payload="Offline", qos=1, retain=True) # set LWT client.on_connect = on_connect # on connect callback client.on_disconnect = on_disconnect # on disconnect callback # connect to broker client.connect(MQTT_BROKER_HOST, port=MQTT_BROKER_PORT) client.loop_start() def publish(client, topic, msg): result = client.publish(topic, msg) # result: [0, 1] status = result[0] # uncomment for debug. don't need all the success messages. if status == 0: #print(f"Sent {msg} to topic {topic}") pass else: print(f"Failed to send message to topic {topic}") def application(environ, start_response): # construct a full URL from the request. HTTP_HOST is FQDN, PATH_INFO is everything after # the FQDN (i.e. /data/stationtype=AMBWeatherV4.2.9&&tempinf=71.1&humidityin=35) url = "http://" + environ["HTTP_HOST"] + environ["PATH_INFO"] # unsure why I need to parse twice. probably just need to do it once with variable url. parsed = urlparse(url) result = parse_qs(parsed.geturl()) # send to our other function to deal with the results. # result is a dict handle_results(result) # we need to return a response. HTTP code 200 means everything is OK. other HTTP codes include 404 not found and such. start_response('200 OK', [('Content-Type', 'text/plain')]) # the response doesn't actually need to contain anything response_body = '' # return the encoded bytes of the response_body. # for python 2 (don't use python 2), the results don't need to be encoded return [response_body.encode()] def handle_results(result): """ result is a dict. full list of variables include: stationtype: ['AMBWeatherV4.2.9'], PASSKEY: ['<station_mac_address>'], dateutc: ['2021-03-20 17:12:27'], tempinf: ['71.1'], humidityin: ['36'], baromrelin: ['29.693'], baromabsin: ['24.549'], tempf: ['58.8'], battout: ['1'], humidity: ['32'], winddir: ['215'],windspeedmph: ['0.0'], windgustmph: ['0.0'], maxdailygust: ['3.4'], hourlyrainin: ['0.000'], eventrainin: ['0.000'], dailyrainin: ['0.000'], weeklyrainin: ['0.000'], monthlyrainin: ['0.000'], totalrainin: ['0.000'], solarradiation: ['121.36'], uv: ['1'],batt_co2: ['1'] """ # we're just going to publish everything. less coding. for key in result: # skip first item, which is basically a URL and MQTT doesn't like it. probably resulting from my bad url parsing. if 'http://' in key: continue #print(f"{key}: {result[key]}") # resultant topic is weather/ws-2902c/solarradiation specific_topic = MQTT_TOPIC + f"/{key}" # replace [' and '] with nothing. these come from the url parse msg = str(result[key]).replace("""['""", '').replace("""']""", '') #print(f"attempting to publish to {specific_topic} with message {msg}") publish(client, specific_topic, msg) # this little guy runs a web server if this python file is called directly. if it isn't called directly, it won't run. # Apache/Python WSGI will run the function 'application()' directly # in theory, you don't need apache or any webserver. just run it right out of python. would need # to improve error handling to ensure it run without interruption. if __name__ == '__main__': from wsgiref.simple_server import make_server # probably shouldn't run on port 80 but that's what I specified in the ambient weather console httpd = make_server('', 80, application) print("Serving on http://localhost:80") httpd.serve_forever()
Execute with python3:
python3 main.py
Watch the results populate in the console window:
PS C:\Users\Austin\source\repos\ambient-weather-decode> python3 c:\Users\Austin\source\repos\ambient-weather-decode\main.py connected to MQTT broker at mqtt Serving on http://localhost:80 Sent E0:98:06:A3:42:65 to topic weather/ws-2902c/PASSKEY Sent 2021-03-20 17:57:31 to topic weather/ws-2902c/dateutc Sent 70.7 to topic weather/ws-2902c/tempinf Sent 36 to topic weather/ws-2902c/humidityin Sent 29.675 to topic weather/ws-2902c/baromrelin Sent 24.531 to topic weather/ws-2902c/baromabsin Sent 66.2 to topic weather/ws-2902c/tempf Sent 1 to topic weather/ws-2902c/battout Sent 26 to topic weather/ws-2902c/humidity Sent 207 to topic weather/ws-2902c/winddir Sent 0.2 to topic weather/ws-2902c/windspeedmph Sent 1.1 to topic weather/ws-2902c/windgustmph Sent 3.4 to topic weather/ws-2902c/maxdailygust Sent 0.000 to topic weather/ws-2902c/hourlyrainin Sent 0.000 to topic weather/ws-2902c/eventrainin Sent 0.000 to topic weather/ws-2902c/dailyrainin Sent 0.000 to topic weather/ws-2902c/weeklyrainin Sent 0.000 to topic weather/ws-2902c/monthlyrainin Sent 0.000 to topic weather/ws-2902c/totalrainin Sent 697.92 to topic weather/ws-2902c/solarradiation Sent 6 to topic weather/ws-2902c/uv Sent 1 to topic weather/ws-2902c/batt_co2
Verification
Verify in MQTT by subscribing to topic ‘weather/#’. The # is a wildcard and will include all subtopics:
Conclusion
I am stoked the Ambient Weather WS-2902C makes it so easy to work the the data.
My next post will show how to turn this Python script into a persistent Linux service – Python service to consume Ambient Weather API data.
A further post will demonstrate incorporating these MQTT messages into Home Assistant sensors.
5 replies on “Handling data from Ambient Weather WS-2902C API to MQTT”
Awesome post! Thank you for taking the time to share this. You saved me a ton of time trying to figure this out on my own.
A suggestion to give back a little…
In your main.py, you had a comment about not liking the way you parsed the URL. In the weather station setup, if you set the Path to: /data? the resulting environ in your python program will split the query from the path, so you don’t have to rebuild the URL and parse it twice. “result = parse_qs(environ[‘QUERY_STRING’]) will give you what you want.
Please keep posting articles like this – love what you’re doing!
This worked great! Thanks!
I just posted a comment about a github repo and docker;
Looks like Dan is already working on a hass.io (Home Assistant) addon.
Love it.
https://github.com/dancwilliams/hassio-addons/tree/0f222aa1c72587d0be06a357d928bbe909026aef/awnet
Looks great; I dont see a github repo; would make taking suggestions from open source community easier;
also i am interested in seeing if this could be put into a very small docker container; Just bought mine today; might look into this over winter break!
I have this running on a Ubuntu LXC and I can see the data from my Weather Station. I can see that it connects to my local EMQX. It however is not publishing any messages. The only thing I am able to see on MQTT explorer is weather/offline (it never goes online, despite being connected to my broker).