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).