Created
January 6, 2018 10:48
-
-
Save darksidelemm/e1e2670a265ef18cbcf847d11e66864e to your computer and use it in GitHub Desktop.
Bureau of Meteorology Single-Site JSON to APRS Uploader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# BOM JSON Observation to APRS Uploader | |
# | |
# Mark Jessop <[email protected]> | |
# 2018-01-06 | |
# | |
# This is intended to be run as a regular (5-10 minute) cron job. | |
# Update the JSON feed, APRS_CALL, APRS_PASSCODE, and WX_CALL Fields below before using. | |
# | |
# A link to the single-station observation JSON feed can be found at the bottom of an observation page, | |
# I.e. http://www.bom.gov.au/products/IDS60801/IDS60801.94648.shtml | |
# | |
import requests | |
import json | |
import sys | |
import traceback | |
import logging | |
from socket import * | |
# SETTINGS | |
# URL to the JSON feed. | |
BOM_JSON_URL = "http://www.bom.gov.au/fwo/IDS60801/IDS60801.94648.json" # Adelaide (West Terrace) | |
# Login details for APRS-IS | |
APRS_CALL = 'N0CALL' | |
APRS_PASSCODE = 00000 | |
# The object name of the weather station on APRS. | |
WX_CALL = 'IDS60801' | |
# Write data to a temporary file for checking. | |
# This file will be writen if it does not exist. | |
TEMP_FILE = './bom_obs.json' | |
def upload_to_aprs(aprsUser, aprsPass, wx_call, data, serverHost = 'rotate.aprs2.net', serverPort = 14580): | |
''' Upload an APRS packet to APRS-IS ''' | |
sSock = socket(AF_INET, SOCK_STREAM) | |
sSock.connect((serverHost, serverPort)) | |
# logon | |
sSock.send('user %s pass %s vers VK5QI-Python 0.1\n' % (aprsUser, aprsPass) ) | |
# send packet | |
sSock.send('%s>APRS:%s\n' % (wx_call, data) ) | |
# close socket | |
sSock.shutdown(0) | |
sSock.close() | |
def str_or_dots(number, length): | |
""" | |
If parameter is None, fill space with dots. Else, zero-pad. | |
""" | |
if number is None: | |
return '.'*length | |
else: | |
format_type = { | |
'int': 'd', | |
'float': '.0f', | |
}[type(number).__name__] | |
return ''.join(('%0',str(length),format_type)) % number | |
def make_aprs_wx(lat_str, lon_str, comment="BOMWX", wind_dir=None, wind_speed=None, wind_gust=None, temperature=None, rain_since_midnight=None, humidity=None, pressure=None): | |
""" | |
Assembles the payload of the APRS weather packet. | |
""" | |
return '!%s/%s_%s/%sg%st%sP%sh%sb%s%s' % ( | |
lat_str, | |
lon_str, | |
str_or_dots(wind_dir, 3), | |
str_or_dots(wind_speed, 3), | |
str_or_dots(wind_gust, 3), | |
str_or_dots(temperature, 3), | |
str_or_dots(rain_since_midnight, 3), | |
str_or_dots(humidity, 2), | |
str_or_dots(pressure, 5), | |
comment | |
) | |
# Lookup table to convert cardinal directions to degrees. | |
cardinal_lookup = { | |
'N': 0, | |
'NNE': 22, | |
'NE': 45, | |
'ENE': 67, | |
'E': 90, | |
'ESE': 112, | |
'SE': 135, | |
'SSE': 157, | |
'S': 180, | |
'SSW': 202, | |
'SW': 225, | |
'WSW': 247, | |
'W': 270, | |
'WNW': 292, | |
'NW': 315, | |
'NNW': 337, | |
'CALM': 0 | |
} | |
# Example BOM JSON blob | |
#{u'swell_period': None, u'wind_dir': u'SSW', u'lat': -34.4, u'cloud_oktas': None, u'gust_kt': 24, u'history_product': u'IDS60801', u'local_date_time_full': u'20180106200000', u'cloud': u'-', u'press_msl': 1008.0, u'cloud_type': u'-', u'wind_spd_kmh': 28, u'lon': 140.6, u'swell_height': None, u'wmo': 94682, u'press_qnh': 1008.1, u'weather': u'-', u'wind_spd_kt': 15, u'rain_trace': u'0.0', u'aifstime_utc': u'20180106093000', u'delta_t': 15.0, u'press_tend': u'-', u'rel_hum': 17, u'local_date_time': u'06/08:00pm', u'press': 1008.0, u'vis_km': u'-', u'sea_state': u'-', u'air_temp': 33.5, u'name': u'Loxton', u'cloud_base_m': None, u'cloud_type_id': None, u'gust_kmh': 44, u'dewpt': 5.6, u'swell_dir_worded': u'-', u'sort_order': 0, u'apparent_t': 27.2} | |
def bom_json_to_aprs(obs): | |
''' Convert a BOM JSON observation into an APRS weather report packet ''' | |
# Convert float latitude to APRS format (DDMM.MM) | |
lat = float(obs["lat"]) | |
lat_degree = abs(int(lat)) | |
lat_minute = abs(lat - int(lat)) * 60.0 | |
lat_min_str = ("%02.2f" % lat_minute).zfill(5) | |
lat_dir = "S" | |
if lat>0.0: | |
lat_dir = "N" | |
lat_str = "%02d%s" % (lat_degree,lat_min_str) + lat_dir | |
# Convert float longitude to APRS format (DDDMM.MM) | |
lon = float(obs["lon"]) | |
lon_degree = abs(int(lon)) | |
lon_minute = abs(lon - int(lon)) * 60.0 | |
lon_min_str = ("%02.2f" % lon_minute).zfill(5) | |
lon_dir = "E" | |
if lon<0.0: | |
lon_dir = "W" | |
lon_str = "%03d%s" % (lon_degree,lon_min_str) + lon_dir | |
# Convert temperature to Farenheit (ugh) | |
temp_f = float(obs['air_temp']) * (9.0/5.0) + 32 | |
# Convert pressure to hPa*10 | |
press_hpa = int(obs['press']*10) | |
# Convert rain in mm to hundredths of an inch | |
rain_in = float(obs['rain_trace']) * 3.93700787 | |
# Convert wind speeds to miles per hour | |
wind_speed = obs['wind_spd_kt'] * 1.15077945 | |
wind_gust = obs['gust_kt'] * 1.15077945 | |
# Convert cardinal direction to degrees using lookup table | |
if obs['wind_dir'] in cardinal_lookup: | |
wind_dir = cardinal_lookup[obs['wind_dir']] | |
else: | |
wind_dir = None | |
# Produce the APRS weather report string, using the above function. | |
aprs_str = make_aprs_wx(lat_str, | |
lon_str, | |
temperature = temp_f, | |
pressure = press_hpa, | |
rain_since_midnight = rain_in, | |
humidity = float(obs['rel_hum']), | |
wind_speed = wind_speed, | |
wind_gust = wind_gust, | |
wind_dir = wind_dir) | |
return aprs_str | |
def get_bom_json(url): | |
''' Grab JSON data from the BOM website, and return the latest observation ''' | |
try: | |
# Wrap all this in a try/catch to avoid errors. | |
# Grab the data from the BOM website. | |
bom_request = requests.get(url) | |
json_data = bom_request.text | |
# Parse the JSON data into a Python Dictionary | |
parsed_data = json.loads(json_data) | |
# Extract the most recent observation | |
latest_obs = parsed_data['observations']['data'][0] | |
return latest_obs | |
except Exception as e: | |
print("ERROR: Grabbing data from BOM Failed: " + traceback.format_exc()) | |
return None | |
if __name__ == '__main__': | |
# Grab latest observation. | |
_obs = get_bom_json(BOM_JSON_URL) | |
# Attempt to read the temp observation file. | |
try: | |
_temp_file = open(TEMP_FILE, 'r') | |
_temp_data = _temp_file.read() | |
data = json.loads(_temp_data) | |
_temp_file.close() | |
# Is the timestamp in the observation different? | |
if data['aifstime_utc'] == _obs['aifstime_utc']: | |
new_obs = False | |
else: | |
new_obs = True | |
except Exception as e: | |
print("Error reading stored observation, assuming observation is new: %s" % str(e)) | |
new_obs = True | |
if new_obs: | |
# Write current observation to disk. | |
_temp_file = open(TEMP_FILE, 'w') | |
_temp_file.write(json.dumps(_obs)) | |
_temp_file.close() | |
# Produce APRS string. | |
aprs_str = bom_json_to_aprs(_obs) | |
print("APRS String: %s" % aprs_str) | |
print("Uploading to APRS...") | |
upload_to_aprs(APRS_CALL, APRS_PASSCODE, WX_CALL, aprs_str) | |
else: | |
print("Not a new observation, Exiting.") | |
sys.exit(0) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment