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 pytz
import datetime
import dateutil.parser
import string
import requests
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("A weather alert beacon exec for aprx >= 2.9 and Direwolf >= 1.3")
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)
# We need this function early in execution
@ -95,7 +96,7 @@ except:
log.error("or 30 (for 1h if beacon cycle-size 2m)")
ErrExit()
url = 'https://alerts.weather.gov/cap/wwaatmget.php?x=' + myZone + '&y=0'
url = 'https://api.weather.gov/alerts/active.atom?zone=' + myZone
try:
r = requests.get(url, timeout=2)
@ -135,53 +136,10 @@ def vtecparse(value):
return ProductClass, Action, Office, Phenomena, Significance, ETN, \
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
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 os.path.isfile(dbfile):
os.remove(dbfile)
@ -200,26 +158,51 @@ for i in range(0, count):
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
VTEC = capchildren(entries[i].parameter.children)
try:
ProductClass, Action, Office, Phenomena, Significance, ETN, \
EventBegin, EventEnd = vtecparse(VTEC['VTEC'].string)
EventBegin, EventEnd = vtecparse(VTEC)
except:
log.debug("VTEC parse failed")
continue #Loop if error parsing P-VTEC
if (ProductClass != "/O"): #Loop if not operational
continue
# Is alert expired?
now = datetime.datetime.utcnow()
now = datetime.datetime.now(datetime.timezone.utc)
# Fix exit on exp = '000000T0000Z'
exp = dateutil.parser.parse(EventEnd)
try:
exp = datetime.datetime.strptime(EventEnd,'%y%m%dT%H%MZ')
exp = dateutil.parser.parse(EventEnd)
except:
exp = now
log.debug('Now: ' + datetime.datetime.strftime(now,"%y-%m-%d %H:%M") + \
' Exp: ' + datetime.datetime.strftime(exp,"%y-%m-%d %H:%M"))
if now > exp:
log.debug("Alert Expired")
continue
@ -270,32 +253,22 @@ for i in range(0, count):
elif 36 <= 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 = ''
for j in range(0, len(parms)):
if parms[j].valueName.string == 'UGC':
zcs = parms[j].value.string
log.debug('Parameters follow\n' + str(parms))
zcs_discrete = ''
for j in soup.select('area'):
for k in j.select('geocode'):
if (k.find('valueName').text == 'UGC'):
addzcs = k.find('value').text
zcs = zcs + addzcs + '-'
zcs_discrete = zcs_discrete + addzcs + "\n"
# This handles messages found to contain empty UGC list
if zcs == '':
log.debug('No UGC list in message')
continue
zcs = zcs[:-1]
log.debug("ZCS Value: " + zcs)
type = pp[bytes(Phenomena, 'utf-8')].decode('utf-8')
if Action == "CAN":
@ -307,15 +280,6 @@ for i in range(0, count):
message = exputc + "z," + type + "," + zcs
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
# to any necessary truncation.
@ -339,13 +303,13 @@ for i in range(0, count):
n = zcs.rfind('-')
zcs = zcs[0:n]
message = exputc + "z," + type + "," + zcs
if not myZone in parsezcs(zcs):
if not myZone in zcs:
zcs = myZone + "-" + zcs
message = exputc + "z," + type + "," + zcs
if adjZone1True and not adjZone1 in parsezcs(zcs):
if adjZone1True and not adjZone1 in zcs:
zcs = adjZone1 + "-" + zcs
message = exputc + "z," + type + "," + zcs
if adjZone2True and not adjZone2 in parsezcs(zcs):
if adjZone2True and not adjZone2 in zcs:
zcs = adjZone2 + "-" + zcs
message = exputc + "z," + type + "," + zcs
@ -353,7 +317,7 @@ for i in range(0, count):
log.info("Truncated Msg: " + message)
line = "{" + t + "00"
print(":NWS_" + event + ":" + message + line)
print(":NWS-" + event + ":" + message + line)
if myResend > 0:
resend[id] = bytes(str(myResend), 'utf-8')

View File

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

View File

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