Update noaacap for CAP 1.2 data source

This commit is contained in:
K2IE 2024-03-25 11:32:32 -04:00
parent af645f510f
commit df64969adf
3 changed files with 51 additions and 83 deletions

View File

@ -11,6 +11,7 @@ version = "1.0"
import sys import sys
import pytz import pytz
import datetime import datetime
import dateutil.parser
import string import string
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
@ -29,7 +30,7 @@ if len(sys.argv) == 2 and sys.argv[1] == '-v':
print("noaacap.py by K2IE, version " + version + "\n") print("noaacap.py by K2IE, version " + version + "\n")
print("A weather alert beacon exec for aprx >= 2.9 and Direwolf >= 1.3") print("A weather alert beacon exec for aprx >= 2.9 and Direwolf >= 1.3")
print("Licensed under the BSD 2 Clause license") print("Licensed under the BSD 2 Clause license")
print("Copyright 2017-2020 by Daniel L. Srebnick\n") print("Copyright 2017-2024 by Daniel L. Srebnick\n")
sys.exit(0) sys.exit(0)
# We need this function early in execution # We need this function early in execution
@ -95,7 +96,7 @@ except:
log.error("or 30 (for 1h if beacon cycle-size 2m)") log.error("or 30 (for 1h if beacon cycle-size 2m)")
ErrExit() ErrExit()
url = 'https://alerts.weather.gov/cap/wwaatmget.php?x=' + myZone + '&y=0' url = 'https://api.weather.gov/alerts/active.atom?zone=' + myZone
try: try:
r = requests.get(url, timeout=2) r = requests.get(url, timeout=2)
@ -135,53 +136,10 @@ def vtecparse(value):
return ProductClass, Action, Office, Phenomena, Significance, ETN, \ return ProductClass, Action, Office, Phenomena, Significance, ETN, \
EventBegin, EventEnd EventBegin, EventEnd
def capchildren(children):
k = None
list = {}
for tag in children:
if tag.name == 'valueName':
k = tag.string
elif tag.name == 'value':
list[k] = tag.string
return list
def parsezcs(zcs):
zonelist = set()
inprogress = 0
newprefix = 1
for z in range(0, len(zcs)):
if not inprogress:
zone = ''
inprogress = 1
if re.match('[A-Z]',zcs[z]):
if newprefix == 1:
prefix = ''
newprefix = 0
prefix = prefix + zcs[z]
if re.match('[0-9]',zcs[z]):
zone = zone + zcs[z]
if zcs[z] == '>':
zonelist.add(prefix + zone)
inprogress = 0
endzone = zcs[z+1:z+4]
z = z + 3
startzone = int(zone) + 1
endzone = int(endzone)
for y in range(startzone, endzone):
zonelist.add(prefix + str(y).zfill(3))
elif zcs[z] == '-':
zonelist.add(prefix + zone)
inprogress = 0
if re.match('[A-Z]',zcs[z+1]):
newprefix = 1
if inprogress:
zonelist.add(prefix + zone)
return zonelist
hit = 0 hit = 0
for i in range(0, count): for i in range(0, count):
log.debug("Processing entry: " + str(count)) log.debug("Processing entry: " + str(i + 1))
if ("no active" in entries[i].title.string): if ("no active" in entries[i].title.string):
if os.path.isfile(dbfile): if os.path.isfile(dbfile):
os.remove(dbfile) os.remove(dbfile)
@ -200,26 +158,51 @@ for i in range(0, count):
if (entries[i].status.string == "Actual"): if (entries[i].status.string == "Actual"):
# Retrieve link for entry i
url = entries[i].id.string + ".cap"
try:
r = requests.get(url, timeout=2)
except requests.exceptions.Timeout:
log.error("Timeout exception requesting " + url)
ErrExit()
if r.status_code != 200:
log.error(str(r1.status_code) + " " + url)
ErrExit()
soup = BeautifulSoup(r.text, 'xml')
for j in soup.select('parameter'):
if (j.find('valueName').text == 'VTEC'):
VTEC = j.find('value').text
log.debug("VTEC String: " + VTEC)
# Parse P-VTEC string and make sure this is an operational ProductClass # Parse P-VTEC string and make sure this is an operational ProductClass
VTEC = capchildren(entries[i].parameter.children)
try: try:
ProductClass, Action, Office, Phenomena, Significance, ETN, \ ProductClass, Action, Office, Phenomena, Significance, ETN, \
EventBegin, EventEnd = vtecparse(VTEC['VTEC'].string) EventBegin, EventEnd = vtecparse(VTEC)
except: except:
log.debug("VTEC parse failed")
continue #Loop if error parsing P-VTEC continue #Loop if error parsing P-VTEC
if (ProductClass != "/O"): #Loop if not operational if (ProductClass != "/O"): #Loop if not operational
continue continue
# Is alert expired? # Is alert expired?
now = datetime.datetime.utcnow() now = datetime.datetime.now(datetime.timezone.utc)
# Fix exit on exp = '000000T0000Z' # Fix exit on exp = '000000T0000Z'
exp = dateutil.parser.parse(EventEnd)
try: try:
exp = datetime.datetime.strptime(EventEnd,'%y%m%dT%H%MZ') exp = dateutil.parser.parse(EventEnd)
except: except:
exp = now exp = now
log.debug('Now: ' + datetime.datetime.strftime(now,"%y-%m-%d %H:%M") + \ log.debug('Now: ' + datetime.datetime.strftime(now,"%y-%m-%d %H:%M") + \
' Exp: ' + datetime.datetime.strftime(exp,"%y-%m-%d %H:%M")) ' Exp: ' + datetime.datetime.strftime(exp,"%y-%m-%d %H:%M"))
if now > exp: if now > exp:
log.debug("Alert Expired") log.debug("Alert Expired")
continue continue
@ -270,32 +253,22 @@ for i in range(0, count):
elif 36 <= s <= 61: elif 36 <= s <= 61:
t = t + chr(s+61) t = t + chr(s+61)
# Pull specific alert to get compressed UGC zones
try:
rs = requests.get(entries[i].id.string, timeout=2)
except requests.exceptions.Timeout:
log.error("Timeout exception requesting " + url)
ErrExit()
if rs.status_code != 200:
log.error(str(rs.status_code) + " " + rs)
ErrExit()
soup2 = BeautifulSoup(rs.text, 'xml')
parms = soup2.find_all('parameter')
j = len(parms)
zcs = '' zcs = ''
for j in range(0, len(parms)): zcs_discrete = ''
if parms[j].valueName.string == 'UGC': for j in soup.select('area'):
zcs = parms[j].value.string for k in j.select('geocode'):
if (k.find('valueName').text == 'UGC'):
log.debug('Parameters follow\n' + str(parms)) addzcs = k.find('value').text
zcs = zcs + addzcs + '-'
zcs_discrete = zcs_discrete + addzcs + "\n"
# This handles messages found to contain empty UGC list # This handles messages found to contain empty UGC list
if zcs == '': if zcs == '':
log.debug('No UGC list in message') log.debug('No UGC list in message')
continue continue
zcs = zcs[:-1]
log.debug("ZCS Value: " + zcs)
type = pp[bytes(Phenomena, 'utf-8')].decode('utf-8') type = pp[bytes(Phenomena, 'utf-8')].decode('utf-8')
if Action == "CAN": if Action == "CAN":
@ -307,15 +280,6 @@ for i in range(0, count):
message = exputc + "z," + type + "," + zcs message = exputc + "z," + type + "," + zcs
log.info("Msg: " + message) log.info("Msg: " + message)
# Pull discrete zone UGC values
soup3 = BeautifulSoup(rs.text, 'xml')
parms = soup3.find_all('geocode')
j = len(parms)
zcs_discrete = ''
for j in range(0, len(parms)):
if parms[j].valueName.string == 'UGC':
zcs_discrete = zcs_discrete + parms[j].value.string + "\n"
# Determine if adjacent zones are in original message prior # Determine if adjacent zones are in original message prior
# to any necessary truncation. # to any necessary truncation.
@ -339,13 +303,13 @@ for i in range(0, count):
n = zcs.rfind('-') n = zcs.rfind('-')
zcs = zcs[0:n] zcs = zcs[0:n]
message = exputc + "z," + type + "," + zcs message = exputc + "z," + type + "," + zcs
if not myZone in parsezcs(zcs): if not myZone in zcs:
zcs = myZone + "-" + zcs zcs = myZone + "-" + zcs
message = exputc + "z," + type + "," + zcs message = exputc + "z," + type + "," + zcs
if adjZone1True and not adjZone1 in parsezcs(zcs): if adjZone1True and not adjZone1 in zcs:
zcs = adjZone1 + "-" + zcs zcs = adjZone1 + "-" + zcs
message = exputc + "z," + type + "," + zcs message = exputc + "z," + type + "," + zcs
if adjZone2True and not adjZone2 in parsezcs(zcs): if adjZone2True and not adjZone2 in zcs:
zcs = adjZone2 + "-" + zcs zcs = adjZone2 + "-" + zcs
message = exputc + "z," + type + "," + zcs message = exputc + "z," + type + "," + zcs
@ -353,7 +317,7 @@ for i in range(0, count):
log.info("Truncated Msg: " + message) log.info("Truncated Msg: " + message)
line = "{" + t + "00" line = "{" + t + "00"
print(":NWS_" + event + ":" + message + line) print(":NWS-" + event + ":" + message + line)
if myResend > 0: if myResend > 0:
resend[id] = bytes(str(myResend), 'utf-8') resend[id] = bytes(str(myResend), 'utf-8')

View File

@ -1,5 +1,9 @@
noaacap changelog noaacap changelog
## ##
Version: 1.1
Release: March 25, 2024
Updated to use CAP 1.2 data source
##
Version: 1.0 Version: 1.0
Release: February 6, 2020 Release: February 6, 2020
Added ability to specify up to 2 adjacent zones Added ability to specify up to 2 adjacent zones

View File

@ -1,6 +1,6 @@
BSD 2-Clause License BSD 2-Clause License
Copyright (c) 2017-2020, Daniel L. Srebnick Copyright (c) 2017-2024, Daniel L. Srebnick
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without Redistribution and use in source and binary forms, with or without