After much trial and error, here’s my method for uploading to CWOP without any special equipment and for free. All you need is a computer that’s always on. You can use a Raspberry Pi, remote server, etc. The script pulls data from the Ambient API, processes it into an APRS packet, and uploads the packet—this avoids mucking around with intercepting traffic on the local network, and has the added benefit of making it so the computer doesn’t have to be anywhere near the station itself.
This assumes you’re already registered through CWOP and have a callsign.
The script uses the ambient_api library by avryhof (
https://github.com/avryhof/ambient_api). He also makes a library specifically for uploading to APRS (
https://github.com/avryhof/ambient_aprs), but at the time I was trying to figure this out, I noticed it had a bug that cause coordinates to be reported in the wrong format. He has since corrected it, but by then I already had my own simplified script, which streamlines some stuff and has the added benefit of reporting zeroes when the values are actually zero, instead of omitting the data entirely. That’s a style preference, so please check out his work if you’d rather.
STEP ONE: install Python on whatever computer you’re going to be using. I suggest lots of Googling if you need instructions on how, that’s the only way I figure anything coding-related out. My version is 2.7.10; can’t guarantee any other versions.
STEP TWO: install ambient_api per the instructions on his GitHub page, or whatever other method you prefer if you’re more tech savvy.
STEP THREE: obtain Ambient API keys. Do this by emailing support@ambientweather.com and providing your Ambient device’s MAC address. You’ll get back both an application key and an API key.
STEP FOUR: copy and paste the following code into a file with a “.py” extension. You can name it whatever you want and put it anywhere you want. Edit the required values, marked by ????, in the code to customize it to your station. There should be 6 values to customize (API key, application key, callsign, latitude, longitude, and device name), so make sure you get them all.
import os
from socket import *
from datetime import datetime, time
import math
os.environ["AMBIENT_ENDPOINT"] = 'https://api.ambientweather.net/v1'
os.environ["AMBIENT_API_KEY"] = '????'
os.environ["AMBIENT_APPLICATION_KEY"] = '????'
from ambient_api.ambientapi import AmbientAPI
callsign = '????'
latitude = ????
longitude = ????
devicename = '????' #This identifies your equipment/software. You can put anything you want. I use 'WS2902A', which is the model of weather station I have
#IMPORTANT: lat/long must be listed in DECIMAL DEGREES (DD.DDDD). Number of digits doesn't really matter. Use positive values for N/E, negative for S/W. The program then converts to degrees decimal minutes (DD MM.MMMM), which is the format APRS requires.
api = AmbientAPI()
devices = api.get_devices()
home = devices[0] #this assumes you have only one station. Increase number accordingly if you want to get data from others
weather= home.last_data
#convert coordinates to degrees decimal minutes
if latitude < 0:
latitude = abs(latitude)
latitude = str(int(latitude)).zfill(2) + str(round(60*(latitude - int(latitude)),2)).zfill(2) + 'S'
else:
latitude = str(int(latitude)).zfill(2) + str(round(60*(latitude - int(latitude)),2)).zfill(2) + 'N'
if longitude < 0:
longitude = abs(longitude)
longitude = str(int(longitude)).zfill(3) + str(round(60*(longitude - int(longitude)),2)).zfill(2) + 'W'
else:
longitude = str(int(longitude)).zfill(3) + str(round(60*(longitude - int(longitude)),2)).zfill(2) + 'E'
winddir = str(weather.get('winddir')).zfill(3)
windspeed = str(int(math.ceil(weather.get('windspeedmph')))).zfill(3)
windgust = str(int(math.ceil(weather.get('windgustmph')))).zfill(3)
if weather.get('tempf') < 0:
temp = '-' + str(int(round(weather.get('tempf')))).zfill(2)
else:
temp = str(int(round(weather.get('tempf')))).zfill(3)
rainhour = str(int(weather.get('hourlyrainin')*100)).zfill(3) #technically this is RATE of rain per hour, not AMOUNT per hour, but seems to be tolerated?
past24hoursrain = str(int(weather.get('dailyrainin')*100)).zfill(3) #at the moment, the Ambient API does not provide "rain in last hour", so no way to calculate "rain in last 24 hours." The API can only report "rain since local midnight." Therefore this only gets reported after 23:45 local time, so rain since midnight is reasonably close to rain in last 24 hours
dailyrain = str(int(weather.get('dailyrainin')*100)).zfill(3) #this value IS supposed to be "rain since local midnight," so it is always reported
pressure = str(int(weather.get('baromrelin')/0.0029529983071445)).zfill(5) #pressure is supposed to be reported to APRS in "altimiter" (QNH) format, that is, relative. The system itself corrects the pressure to sea level based on your station's listed elevation, so make sure that's accurate
humidity = str(int(weather.get('humidity')%100)).zfill(2) #uses modulus operator % so that 100% is given as '00'
# If luminosity is above 999 W/m^2, APRS wants a lowercase L
if weather.get('solarradiation') >= 1000:
luminosity = 'l' + str(int(round(weather.get('solarradiation'))) % 1000).zfill(3)
else:
luminosity = 'L' + str(int(round(weather.get('solarradiation')))).zfill(3)
# Time reported in Zulu (UTC). 24-hour rain workaround still has to be local time, though
packet = callsign + '>APRS,TCPIP*:@' + datetime.utcnow().strftime("%d%H%M") + 'z' + latitude + '/' + longitude + '_' + winddir + '/' + windspeed + 'g' + windgust + 't' + temp + 'r' + rainhour + 'p' + (past24hoursrain if datetime.now().time() >= time(23,45) else '...') + 'P' + dailyrain + 'h' + humidity + 'b' + pressure + luminosity + devicename
print(packet) #prints the assembled packet for debugging purposes
#send the packet
s = socket(AF_INET, SOCK_STREAM)
s.connect(('cwop.aprs.net', 14580))
s.send('user ' + callsign + ' pass -1 vers Python\n')
s.send(packet+'\n')
s.shutdown(0)
s.close()
STEP FIVE: run the script once by executing ‘python [your script]’ in a terminal window. It should spit out a correctly formatted APRS packet and upload it to CWOP successfully.
STEP SIX: set a CRON job to run that script automatically at an interval of your choosing (no more than 5 minutes to be polite to the APRS servers, every 10 minutes seems to be the norm). Google how to do this on your specific device. I upload every 5 minutes so the CRON task I use is as follows:
*/5 * * * * python [path to your script]
And that should do it. Check findu to make sure your packets are coming through, and make sure your computer never goes to sleep.
Good luck!! Let me know if any of that isn’t working and I’ll do my best to help out, but for almost anything device-specific, Google is going to be smarter and faster than me.