I was dismayed to find out that my new Ambient WS-2902A didn't have the capability of sending data directly to APRS. There were other posts describing methods for getting that data out of Ambient's REST API and into APRS, but all of them involved doing something like custom router firmware or running a script on a computer (single point of failure)...
I thought this was an excellent use for AWS Lambda. This lets you run your code in a high-availability environment,
for free. You don't even have to pay for the electricity this runs on. Jeff Bezos has it covered. Steps to do it:
- Get an amateur radio license

- Set up your station to report to AmbientWeather.net through the awnet phone app.
- Create an Application Key and API Key at https://dashboard.ambientweather.net/account.
- Create yourself an account at Amazon and log in to Amazon Web Services.
- Go to the AWS Lambda service. https://console.aws.amazon.com/lambda/home?region=us-east-1#/create/function
- Click "Create function", and choose the "Author from scratch" method. I named my function ambientToAPRS. Make sure to choose the Python 3.8 runtime.
- Replace the sample code with this (and replace the values of callAndSSID, aprsPasscode, latitude, longitude, apiKey, mac, and applicationKey - no, those are not really mine). The APRS specification designates SKY as a destination address for SKYWARN stations. In my case, that is what I used. Looks like everyone else just uses APRS as the destination.
import urllib, json, time, http.client
from urllib.request import urlopen
def lambda_handler(event, context):
# REPLACE THESE VALUES ########
mac = 'DC:4F:22:59:13:12'
apiKey = '438955956ece44d9a2984ae15e46fb162303311747ef4390b930dee08d6accea'
applicationKey = 'd984259457f445d6a6c2bda2896ca23e0430163bb8be4a49b424b86d8072779f'
# Your call and substation ID go here, e.g. KN4DVB-1
callAndSSID = 'KN4DVB-1'
# Your APRS passcode - https://apps.magicbug.co.uk/passcode/
aprsPasscode = str('124324')
# latitude must be exactly 7 chars - include leading zeros
latitude = '3612.70N'
# longitude must be exactly 9 chars - include leading zero(s)
longitude = '08141.69W'
destination = 'APRS'
#uncomment (remove the # from) the following line if you are a real, trained NWS SKYWARN watcher
#destination = 'SKY'
#########################
url = 'https://api.ambientweather.net/v1/devices/' + mac + '?apiKey=' + apiKey + '&applicationKey=' + '&endDate=&limit=1'
json_url = urlopen(url)
data = json.loads(json_url.read())
diff = time.time() - (data[0]["dateutc"]/1000)
responsebody = ""
if (diff < 800):
postbody = 'user ' + callAndSSID +' pass ' + aprsPasscode +'\n'
dhmtime = str(data[0]["date"][8:10]) + str(data[0]["date"][11:13]) + str(data[0]["date"][14:16]) + "z"
postbody += callAndSSID + '>' + destination + ',WIDE1-1:@' + dhmtime + latitude + '/' + longitude + '_'
# wind direction
postbody += 'c'
postbody += (str('000') + str(data[0]["winddir"]))[-3:]
# wind speed
postbody += 's'
postbody += (str('000') + str(round(float(data[0]["windspeedmph"]))))[-3:]
# wind gust
postbody += 'g'
postbody += (str('000') + str(round(float(data[0]["windgustmph"]))))[-3:]
# temp F
postbody += 't'
tempf = int(data[0]["tempf"])
if (tempf < 0):
postbody += "-" + (str("00") + str(tempf))[-2:]
else:
postbody += (str("000")+str(tempf))[-3:]
# rain in last hour
postbody += 'r'
postbody += (str('000') + str(int(data[0]["hourlyrainin"]*100)))[-3:]
# rain in last day
postbody += 'P'
postbody += (str('000') + str(int(data[0]["dailyrainin"]*100)))[-3:]
#humidity
postbody += "h"
postbody += (str('00') + str(data[0]["humidity"]))[-2:]
#pressure
postbody += "b"
postbody += (str('00000') + str(int(10*33.864*data[0]["baromrelin"])))[-5:]
posturl = "http://rotate.aprs2.net:8080/"
conn = http.client.HTTPConnection('rotate.aprs2.net',8080)
headers = {'Content-Type':'application/octet-stream','Accept-Type':'text/plain'}
conn.request('POST', '/', postbody, headers)
responsebody = str(conn.getresponse().read())
return {
'statusCode': 200,
'body': responsebody
}
- Save your function and test it... you should see your beautiful WX icon show up at https://aprs.fi .
- Go to CloudWatch Rules: https://console.aws.amazon.com/cloudwatch/home?region=us-east-1#rules:
- Create a rule. I called mine ambientToAPRS-runner.
- In the Event Source, set it to Schedule, with fixed rate of 1 minute.
- Click "Add Target", and choose your ambientToAPRS lambda function.
- Click "Configure Details".
- Put in the name of your rule, and set state to Enabled.
- Click "Create Rule".
- Go back to aprs.fi in 10 minutes, and pull up some beautiful weather graphs for your station.
The AWS Lambda free usage tier includes 1M free requests per month and 400,000 GB-seconds of compute time per month. You will run 44,640 requests in a 31-day month (44,641 if they ever add a leap second in a 31-day month).
If you want the data copied to CWOP, my understanding is that you have to register your callsign with CWOP (I'll get on this today).
My next project: Decode the 915 MHz signal from the sensor array and push it to a local iGate on RF APRS on a solar-powered Arduino with an SDR receiver and a UV-5R transmitter.