Merge branch 'master' of https://github.com/K2IE/noaacap
This commit is contained in:
commit
f473855e29
|
|
@ -1,8 +1,8 @@
|
||||||
Package: noaacap
|
Package: noaacap
|
||||||
Version: 1.0.0
|
Version: 1.4.0
|
||||||
Section: hamradio
|
Section: hamradio
|
||||||
Priority: extra
|
Priority: extra
|
||||||
Maintainer: Dan Srebnick <k2ie at k2ie.net>
|
Maintainer: Dan Srebnick <k2ie at k2ie.net>
|
||||||
Architecture: all
|
Architecture: all
|
||||||
Depends: aprx (>=2.9) | direwolf (>=1.3), db-util, python3-lxml, python3-pip, python3-bs4, python3-tz, python3-bsddb3, python3-systemd
|
Depends: aprx (>=2.9) | direwolf (>=1.3), db-util, python3-lxml, python3-pip, python3-bs4, python3-tz, python3-bsddb3, python3-systemd,python3-dateutil
|
||||||
Description: APRS Weather Alerts beacon exec for aprx & Dire Wolf
|
Description: APRS Weather Alerts beacon exec for aprx & Dire Wolf
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
[noaacap]
|
[noaacap]
|
||||||
myTZ = America/New_York
|
myTZ = America/New_York
|
||||||
myZone = NJC025
|
myZone = NJZ012
|
||||||
myResend = 30
|
myResend = 30
|
||||||
adjZone1 = NJZ012
|
adjZone1 = NJZ013
|
||||||
adjZone2 = NJZ020
|
adjZone2 = NJZ014
|
||||||
Logging = 2
|
Logging = 2
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@
|
||||||
##
|
##
|
||||||
## See /usr/local/share/noaacap/CHANGELOG for change history
|
## See /usr/local/share/noaacap/CHANGELOG for change history
|
||||||
##
|
##
|
||||||
version = "1.0"
|
version = "1.4.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)
|
||||||
|
|
@ -110,9 +111,15 @@ if r.status_code != 200:
|
||||||
soup = BeautifulSoup(r.text, 'xml')
|
soup = BeautifulSoup(r.text, 'xml')
|
||||||
entries = soup.find_all('entry')
|
entries = soup.find_all('entry')
|
||||||
count = len(entries)
|
count = len(entries)
|
||||||
log.debug("Entry count: " + str(count))
|
|
||||||
|
|
||||||
dbfile = '/dev/shm/noaaconf.db'
|
dbfile = '/dev/shm/noaaconf.db'
|
||||||
|
if count == 0:
|
||||||
|
log.info("Exiting - no events found")
|
||||||
|
if os.path.isfile(dbfile):
|
||||||
|
os.remove(dbfile)
|
||||||
|
print()
|
||||||
|
exit(0)
|
||||||
|
|
||||||
ppmap = '/usr/local/share/noaacap/ppmap.db'
|
ppmap = '/usr/local/share/noaacap/ppmap.db'
|
||||||
|
|
||||||
sg = {"W":"WARN ","A":"WATCH","Y":"ADVIS","S":"STMNT","F":"4CAST",
|
sg = {"W":"WARN ","A":"WATCH","Y":"ADVIS","S":"STMNT","F":"4CAST",
|
||||||
|
|
@ -135,91 +142,76 @@ def vtecparse(value):
|
||||||
return ProductClass, Action, Office, Phenomena, Significance, ETN, \
|
return ProductClass, Action, Office, Phenomena, Significance, ETN, \
|
||||||
EventBegin, EventEnd
|
EventBegin, EventEnd
|
||||||
|
|
||||||
def capchildren(children):
|
def move_entry(mylist,entry_val,entry_pos):
|
||||||
k = None
|
z_index = mylist.index(entry_val)
|
||||||
list = {}
|
z_value = mylist.pop(z_index)
|
||||||
for tag in children:
|
mylist.insert(entry_pos,z_value)
|
||||||
if tag.name == 'valueName':
|
return mylist
|
||||||
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.info("Processing entry: " + str(i + 1) + " of " + str(count))
|
||||||
if ("no active" in entries[i].title.string):
|
if i == 0:
|
||||||
if os.path.isfile(dbfile):
|
alerts = db.DB()
|
||||||
os.remove(dbfile)
|
alerts.open(dbfile, "Alerts", db.DB_HASH, db.DB_CREATE)
|
||||||
break
|
if myResend > 0:
|
||||||
else:
|
resend = db.DB()
|
||||||
if i == 0:
|
resend.open(dbfile, "Resend", db.DB_HASH, db.DB_CREATE)
|
||||||
alerts = db.DB()
|
pp = db.DB()
|
||||||
alerts.open(dbfile, "Alerts", db.DB_HASH, db.DB_CREATE)
|
pp.open(ppmap, None, db.DB_HASH, db.DB_RDONLY)
|
||||||
if myResend > 0:
|
|
||||||
resend = db.DB()
|
|
||||||
resend.open(dbfile, "Resend", db.DB_HASH, db.DB_CREATE)
|
|
||||||
pp = db.DB()
|
|
||||||
pp.open(ppmap, None, db.DB_HASH, db.DB_RDONLY)
|
|
||||||
|
|
||||||
updated = entries[i].updated.string
|
updated = entries[i].updated.string
|
||||||
|
|
||||||
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')
|
||||||
|
|
||||||
|
VTEC = ''
|
||||||
|
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
|
||||||
|
|
||||||
|
EventEnd = "20" + EventEnd
|
||||||
|
|
||||||
# Is alert expired?
|
# Is alert expired?
|
||||||
now = datetime.datetime.utcnow()
|
now = datetime.datetime.now(datetime.timezone.utc).replace(microsecond=0)
|
||||||
# Fix exit on exp = '000000T0000Z'
|
# Fix exit on exp = '000000T0000Z'
|
||||||
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") + \
|
|
||||||
' Exp: ' + datetime.datetime.strftime(exp,"%y-%m-%d %H:%M"))
|
# log.debug('Time Now: ' + datetime.datetime.strftime(now,"%y-%m-%d %H:%M"))
|
||||||
|
# log.debug('Expiration: ' + 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
|
||||||
|
|
@ -228,11 +220,14 @@ for i in range(0, count):
|
||||||
|
|
||||||
id = bytes(str(Office + Phenomena + Significance + ETN), 'utf-8')
|
id = bytes(str(Office + Phenomena + Significance + ETN), 'utf-8')
|
||||||
|
|
||||||
|
# log.debug("ID: " + id.decode('utf-8'))
|
||||||
|
|
||||||
# Do we have this alert?
|
# Do we have this alert?
|
||||||
if alerts.has_key(id):
|
if id in alerts:
|
||||||
# Is the updated time unchanged?
|
# Is the updated time unchanged?
|
||||||
|
# log.debug('ID found in alerts. Now compare last updated time.')
|
||||||
if alerts[id].decode('utf-8') == updated:
|
if alerts[id].decode('utf-8') == updated:
|
||||||
log.debug(id.decode('utf-8') + " " + updated + " found")
|
log.debug(id.decode('utf-8') + " " + updated + " has already been sent")
|
||||||
# Is resend behavoir desired?
|
# Is resend behavoir desired?
|
||||||
if myResend > 0:
|
if myResend > 0:
|
||||||
|
|
||||||
|
|
@ -250,7 +245,7 @@ for i in range(0, count):
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
alerts[id] = updated
|
alerts.put(id, updated.encode('utf-8'))
|
||||||
|
|
||||||
effutc = aprstime(entries[i].effective.string[0:-6],myTZ)
|
effutc = aprstime(entries[i].effective.string[0:-6],myTZ)
|
||||||
exputc = aprstime(entries[i].expires.string[0:-6],myTZ)
|
exputc = aprstime(entries[i].expires.string[0:-6],myTZ)
|
||||||
|
|
@ -270,32 +265,88 @@ 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
|
zcs_discrete = []
|
||||||
try:
|
for j in soup.select('area'):
|
||||||
rs = requests.get(entries[i].id.string, timeout=2)
|
for k in j.select('geocode'):
|
||||||
except requests.exceptions.Timeout:
|
if (k.find('valueName').text == 'UGC'):
|
||||||
log.error("Timeout exception requesting " + url)
|
addzcs = k.find('value').text
|
||||||
ErrExit()
|
zcs_discrete.append(addzcs)
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
||||||
# This handles messages found to contain empty UGC list
|
# This handles messages found to contain empty UGC list
|
||||||
if zcs == '':
|
if zcs_discrete == []:
|
||||||
log.debug('No UGC list in message')
|
log.debug('No UGC list in message')
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
zcs = ''
|
||||||
|
sorted_zcs = (sorted(zcs_discrete))
|
||||||
|
|
||||||
|
# Move myZone to first position in case of truncation
|
||||||
|
move_entry(sorted_zcs,myZone,0)
|
||||||
|
|
||||||
|
# Move adjacent zones, if present, after myZone
|
||||||
|
adjZone2Index = 1
|
||||||
|
if adjZone1 != '' and adjZone1 in sorted_zcs:
|
||||||
|
adjZone2Index = 2
|
||||||
|
move_entry(sorted_zcs,adjZone1,1)
|
||||||
|
log.debug("adjZone1 found in sorted zcs and moved to index 1")
|
||||||
|
|
||||||
|
if adjZone2 != '' and adjZone2 in sorted_zcs:
|
||||||
|
move_entry(sorted_zcs,adjZone2,adjZone2Index)
|
||||||
|
log.debug("adjZone2 found in sorted zcs and moved to index " + str(adjZone2Index))
|
||||||
|
|
||||||
|
prev_item = ''
|
||||||
|
prev_prefix = ''
|
||||||
|
prev_suffix = ''
|
||||||
|
next_item = ''
|
||||||
|
# next_prefix = ''
|
||||||
|
next_suffix = ''
|
||||||
|
|
||||||
|
k = 0
|
||||||
|
for j in sorted_zcs:
|
||||||
|
k += 1
|
||||||
|
curr_item = j
|
||||||
|
|
||||||
|
try:
|
||||||
|
next_item = sorted_zcs[k]
|
||||||
|
except:
|
||||||
|
next_item = ''
|
||||||
|
if next_item != '':
|
||||||
|
# next_prefix = next_item[0:3]
|
||||||
|
next_suffix = next_item[3:6]
|
||||||
|
i_next_suffix = int(next_suffix)
|
||||||
|
|
||||||
|
curr_prefix = curr_item[0:3]
|
||||||
|
curr_suffix = curr_item[3:6]
|
||||||
|
i_curr_suffix = int(curr_suffix)
|
||||||
|
i_curr_plus1 = i_curr_suffix + 1
|
||||||
|
|
||||||
|
# Always print entire first item
|
||||||
|
if k == 1:
|
||||||
|
zcs = curr_item
|
||||||
|
# Did prefix change?
|
||||||
|
elif prev_prefix != curr_prefix:
|
||||||
|
zcs = zcs + "-" + curr_item
|
||||||
|
# Current suffix is 1 more than previous
|
||||||
|
elif i_prev_plus1 == i_curr_suffix:
|
||||||
|
if i_next_suffix != i_curr_plus1:
|
||||||
|
zcs = zcs + ">" + curr_suffix
|
||||||
|
# Current suffix is not 1 more than previous
|
||||||
|
elif i_prev_plus1 != i_curr_suffix:
|
||||||
|
zcs = zcs + "-" + curr_suffix
|
||||||
|
else:
|
||||||
|
log.error("Unexpected condition during zcs compression")
|
||||||
|
ErrExit()
|
||||||
|
|
||||||
|
# For debugging only
|
||||||
|
# print (zcs)
|
||||||
|
|
||||||
|
prev_item = curr_item
|
||||||
|
prev_prefix = curr_prefix
|
||||||
|
prev_suffix = curr_suffix
|
||||||
|
i_prev_suffix = int(prev_suffix)
|
||||||
|
i_prev_plus1 = i_prev_suffix + 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,47 +358,13 @@ 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
|
# Make sure message does not exceed 67 chars. If it does, trim it.
|
||||||
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.
|
|
||||||
|
|
||||||
adjZone1True = 0
|
|
||||||
adjZone2True = 0
|
|
||||||
|
|
||||||
if adjZone1 != '' and adjZone1 in zcs_discrete:
|
|
||||||
adjZone1True = 1
|
|
||||||
log.debug("adjZone1 found in alert data")
|
|
||||||
|
|
||||||
if adjZone2 != '' and adjZone2 in zcs_discrete:
|
|
||||||
adjZone2True = 1
|
|
||||||
log.debug("adjZone2 found in alert data")
|
|
||||||
|
|
||||||
# Make sure message does not exceed 67 chars. If it
|
|
||||||
# does, trim it. Also be certain that myZone is included
|
|
||||||
# included in the trimmed zcs.
|
|
||||||
|
|
||||||
n = 0
|
n = 0
|
||||||
while len(message) > 67:
|
while len(message) > 67:
|
||||||
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):
|
|
||||||
zcs = myZone + "-" + zcs
|
|
||||||
message = exputc + "z," + type + "," + zcs
|
|
||||||
if adjZone1True and not adjZone1 in parsezcs(zcs):
|
|
||||||
zcs = adjZone1 + "-" + zcs
|
|
||||||
message = exputc + "z," + type + "," + zcs
|
|
||||||
if adjZone2True and not adjZone2 in parsezcs(zcs):
|
|
||||||
zcs = adjZone2 + "-" + zcs
|
|
||||||
message = exputc + "z," + type + "," + zcs
|
|
||||||
|
|
||||||
if n > 0:
|
if n > 0:
|
||||||
log.info("Truncated Msg: " + message)
|
log.info("Truncated Msg: " + message)
|
||||||
|
|
@ -359,7 +376,7 @@ for i in range(0, count):
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
if (hit == 0):
|
if hit == 0:
|
||||||
print()
|
print()
|
||||||
|
|
||||||
log.info("Exiting")
|
log.info("Exiting")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,21 @@
|
||||||
noaacap changelog
|
noaacap changelog
|
||||||
##
|
##
|
||||||
|
Version: 1.3.1
|
||||||
|
Release: April 2, 2024
|
||||||
|
Fix expiration date parsing issue
|
||||||
|
##
|
||||||
|
Version: 1.3
|
||||||
|
Release: TBD
|
||||||
|
Fixed truncation bug in compressed zone list
|
||||||
|
##
|
||||||
|
Version: 1.2
|
||||||
|
Release: March 27, 2024
|
||||||
|
Reintroduced compressed zones and other improvements
|
||||||
|
##
|
||||||
|
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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,6 @@
|
||||||
|
The current release of noaacap is 1.4.0. If you are running older than this
|
||||||
|
version, please update before asking for support.
|
||||||
|
|
||||||
This software is written in python3 and has been tested with aprx 2.9.x.
|
This software is written in python3 and has been tested with aprx 2.9.x.
|
||||||
If you are running a version < 2.9.0 you should first update aprx. The current
|
If you are running a version < 2.9.0 you should first update aprx. The current
|
||||||
release of aprx may be found at http://thelifeofkenneth.com/aprx/debs/.
|
release of aprx may be found at http://thelifeofkenneth.com/aprx/debs/.
|
||||||
|
|
@ -14,7 +17,7 @@ https://github.com/K2IE/noaacap/releases
|
||||||
|
|
||||||
Once you are running aprx >= 2.9 or direwolf >= 1.3, install noaacap:
|
Once you are running aprx >= 2.9 or direwolf >= 1.3, install noaacap:
|
||||||
|
|
||||||
Install via sudo dpkg -i noaacap-1.0.deb. Then, to satisfy the dependencies,
|
Install via sudo dpkg -i noaacap-1.4.deb. Then, to satisfy the dependencies,
|
||||||
run sudo apt-get -f install.
|
run sudo apt-get -f install.
|
||||||
|
|
||||||
==> APRX CONFIGURATION <==
|
==> APRX CONFIGURATION <==
|
||||||
|
|
@ -57,10 +60,14 @@ myTZ should be set to the Linux TZ value for the issuing office of your
|
||||||
preferred weather zone. For a complete list of timezones, you may use
|
preferred weather zone. For a complete list of timezones, you may use
|
||||||
the following command: timedatectl list-timezones | grep America.
|
the following command: timedatectl list-timezones | grep America.
|
||||||
|
|
||||||
myZone should be set to your county code. A list of county codes may be found
|
myZone should be set to your zone code. A list of zone codes may be found
|
||||||
at https://alerts.weather.gov/. Only list one county. The program will then
|
at https://www.weather.gov/pimar/PubZone. Only list one zone. The program
|
||||||
send the alert for all affected zones. You could instead use a zone code if
|
will send the alert for all affected zones that will fit in the allocated 67
|
||||||
you want to be more specific.
|
characters. You could instead use a county code if you wish. However the
|
||||||
|
associated zone may also be sent, wasting characters.
|
||||||
|
|
||||||
|
As an example, if you're located in Lancaster, PA, the map shows that you
|
||||||
|
are in zone 066. Enter PAZ066 for myZone.
|
||||||
|
|
||||||
myResend should be set to 0 for no message resends or to an interval of your
|
myResend should be set to 0 for no message resends or to an interval of your
|
||||||
choosing. If noaacap beacons every 2 minutes, a value of 30 would provide
|
choosing. If noaacap beacons every 2 minutes, a value of 30 would provide
|
||||||
|
|
@ -82,3 +89,4 @@ weather alerts via this program.
|
||||||
|
|
||||||
Dan Srebnick, K2IE
|
Dan Srebnick, K2IE
|
||||||
02/06/2020
|
02/06/2020
|
||||||
|
Updated 04/03/2024
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue