Skip to content

Instantly share code, notes, and snippets.

@darksidelemm
Created January 6, 2018 10:48
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save darksidelemm/e1e2670a265ef18cbcf847d11e66864e to your computer and use it in GitHub Desktop.
Save darksidelemm/e1e2670a265ef18cbcf847d11e66864e to your computer and use it in GitHub Desktop.
Bureau of Meteorology Single-Site JSON to APRS Uploader
#
# BOM JSON Observation to APRS Uploader
#
# Mark Jessop <vk5qi@rfhead.net>
# 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