Initial commit to git
This commit is contained in:
commit
ad0f28b1bb
|
|
@ -0,0 +1 @@
|
||||||
|
/etc/noaacap.conf
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
Package: noaacap
|
||||||
|
Version: 0.4
|
||||||
|
Section: hamradio
|
||||||
|
Priority: extra
|
||||||
|
Maintainer: Dan Srebnick <k2dls at k2dls.net>
|
||||||
|
Architecture: all
|
||||||
|
Depends: aprx (>=2.9), db-util, python-lxml, python-pip, python-bs4, python-tz
|
||||||
|
Description: APRS Weather Alerts beacon exec for aprx
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
Copyright: 2017 Daniel L. Srebnick <k2dls@k2dls.net>
|
||||||
|
License: BSD-2-clause
|
||||||
|
Files: *
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
[noaacap]
|
||||||
|
myTZ = America/New_York
|
||||||
|
myZone = NJZ014
|
||||||
|
|
@ -0,0 +1,212 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
## Author: Dan Srebnick, K2DLS
|
||||||
|
##
|
||||||
|
## Version: 0.4
|
||||||
|
## Release: August 24, 2017
|
||||||
|
## Switched to compressed zcs to improve capacity
|
||||||
|
## Improved messsage + \n length check
|
||||||
|
##
|
||||||
|
## Version: 0.3
|
||||||
|
## Release: August 22, 2017
|
||||||
|
## Changes: Implement NWS-CANCL messages, implement DB id consisting of
|
||||||
|
## Office + Phenomena + Significance + ETN
|
||||||
|
##
|
||||||
|
## Version: 0.2
|
||||||
|
## Release: August 19, 2017
|
||||||
|
## Changes: Make sure message + \n does not exceed 67 chars
|
||||||
|
##
|
||||||
|
## Version: 0.1
|
||||||
|
## Release: August 7, 2017
|
||||||
|
##
|
||||||
|
## License: BSD-2-Clause (/usr/local/share/noaacap/license.txt)
|
||||||
|
##
|
||||||
|
## This program is called from aprx.
|
||||||
|
##
|
||||||
|
|
||||||
|
import pytz, datetime
|
||||||
|
import string
|
||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import bsddb
|
||||||
|
import os
|
||||||
|
import ConfigParser
|
||||||
|
import re
|
||||||
|
|
||||||
|
config = ConfigParser.ConfigParser()
|
||||||
|
|
||||||
|
try:
|
||||||
|
os.path.isfile('/etc/noaacap.conf')
|
||||||
|
except:
|
||||||
|
exit("\nError: /etc/noaacap.conf not found\n")
|
||||||
|
else:
|
||||||
|
config.read('/etc/noaacap.conf')
|
||||||
|
|
||||||
|
myTZ = config.get('noaacap', 'myTZ')
|
||||||
|
myZone = config.get('noaacap', 'myZone')
|
||||||
|
|
||||||
|
def aprstime(timestr,TZ):
|
||||||
|
local = pytz.timezone (TZ)
|
||||||
|
naive = datetime.datetime.strptime(timestr, "%Y-%m-%dT%H:%M:%S")
|
||||||
|
local_dt = local.localize(naive, is_dst=None)
|
||||||
|
utc_dt = local_dt.astimezone (pytz.utc)
|
||||||
|
return utc_dt.strftime ("%d%H%M")
|
||||||
|
|
||||||
|
def vtecparse(value):
|
||||||
|
line = value.split("\n")[0]
|
||||||
|
ProductClass, Action, Office, Phenomena, Significance, ETN, DTGroup = \
|
||||||
|
line.split('.')
|
||||||
|
EventBegin, EventEnd = DTGroup.split('-')
|
||||||
|
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
|
||||||
|
|
||||||
|
sg = {"W":"WARN ","A":"WATCH","Y":"ADVIS","S":"STMNT","F":"4CAST",
|
||||||
|
"O":"OUTLK","N":"SYNOP"}
|
||||||
|
|
||||||
|
url = 'https://alerts.weather.gov/cap/wwaatmget.php?x=' + myZone + '&y=0'
|
||||||
|
try:
|
||||||
|
r = requests.get(url)
|
||||||
|
except:
|
||||||
|
exit('\n')
|
||||||
|
|
||||||
|
soup = BeautifulSoup(r.text, 'xml')
|
||||||
|
entries = soup.find_all('entry')
|
||||||
|
count = len(entries)
|
||||||
|
|
||||||
|
dbfile = '/dev/shm/noaacap.db'
|
||||||
|
ppmap = '/usr/local/share/noaacap/ppmap.db'
|
||||||
|
|
||||||
|
hit = 0
|
||||||
|
for i in range(0, count):
|
||||||
|
|
||||||
|
if ("no active" in entries[i].title.string):
|
||||||
|
if os.path.isfile(dbfile):
|
||||||
|
os.remove(dbfile)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
if i == 0:
|
||||||
|
alerts = bsddb.hashopen(dbfile,'c')
|
||||||
|
pp = bsddb.hashopen(ppmap,'r')
|
||||||
|
|
||||||
|
updated = str(entries[i].updated.string)
|
||||||
|
|
||||||
|
if (entries[i].status.string == "Actual"):
|
||||||
|
|
||||||
|
# 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)
|
||||||
|
if (ProductClass <> "/O"): #Loop if not operational
|
||||||
|
continue
|
||||||
|
except:
|
||||||
|
continue #Loop if error parsing P-VTEC
|
||||||
|
|
||||||
|
id = str(Office + Phenomena + Significance + ETN)
|
||||||
|
|
||||||
|
if alerts.has_key(id):
|
||||||
|
if alerts[id] == updated:
|
||||||
|
continue
|
||||||
|
|
||||||
|
alerts[id] = updated
|
||||||
|
|
||||||
|
effutc = aprstime(entries[i].effective.string[0:-6],myTZ)
|
||||||
|
exputc = aprstime(entries[i].expires.string[0:-6],myTZ)
|
||||||
|
|
||||||
|
# Compress effective time to 3 byte
|
||||||
|
|
||||||
|
dd = int(effutc[0:2])
|
||||||
|
hh = int(effutc[2:4])
|
||||||
|
mm = int(effutc[4:6])
|
||||||
|
|
||||||
|
t = ''
|
||||||
|
for s in (dd, hh, mm):
|
||||||
|
if 0 <= s <= 9:
|
||||||
|
t = t + chr(s+48)
|
||||||
|
elif 10 <= s <= 35:
|
||||||
|
t = t + chr(s+55)
|
||||||
|
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)
|
||||||
|
except:
|
||||||
|
exit('\n')
|
||||||
|
|
||||||
|
soup2 = BeautifulSoup(rs.text, 'xml')
|
||||||
|
parms = soup2.find_all('parameter')
|
||||||
|
j = len(parms)
|
||||||
|
for j in range(0, len(parms)):
|
||||||
|
if parms[j].valueName.string == 'UGC':
|
||||||
|
zcs = parms[j].value.string
|
||||||
|
|
||||||
|
type = pp[str(Phenomena)]
|
||||||
|
event = sg[Significance]
|
||||||
|
|
||||||
|
if Action == "CAN":
|
||||||
|
event = "CANCL"
|
||||||
|
|
||||||
|
hit = 1
|
||||||
|
suffix = exputc + "z," + type + "," + zcs + "{" + t + "00"
|
||||||
|
|
||||||
|
# Make sure message does not exceed 67 chars (66 + newline)
|
||||||
|
# If it does, trim it. Also be certain that myZone
|
||||||
|
# is included in the trimmed zcs.
|
||||||
|
while len(suffix) > 66:
|
||||||
|
if not myZone in parsezcs(zcs):
|
||||||
|
zcs = myZone + "-" + zcs
|
||||||
|
suffix = exputc + "z," + type + "," + zcs + "{" + t + "00"
|
||||||
|
n = zcs.rfind('-')
|
||||||
|
zcs = zcs[0:n]
|
||||||
|
suffix = exputc + "z," + type + "," + zcs + "{" + t + "00"
|
||||||
|
|
||||||
|
print ":NWS_" + event + ":" + suffix
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
if (hit == 0):
|
||||||
|
print
|
||||||
|
|
@ -0,0 +1,47 @@
|
||||||
|
This software is written in python 2.7 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
|
||||||
|
release may be found at http://thelifeofkenneth.com/aprx/debs/.
|
||||||
|
|
||||||
|
Once you are running aprx >= 2.9, install noaacap:
|
||||||
|
|
||||||
|
Install via sudo dpkg -i noaacap-0.1.1.deb. Then, to satisfy the dependencies,
|
||||||
|
run sudo apt-get -f install.
|
||||||
|
|
||||||
|
CONFIGURATION
|
||||||
|
|
||||||
|
Add a beacon section similar to the following to /etc/aprx.conf:
|
||||||
|
|
||||||
|
<beacon>
|
||||||
|
|
||||||
|
beaconmode both
|
||||||
|
cycle-size 2m
|
||||||
|
|
||||||
|
beacon via WIDE2-1 \
|
||||||
|
srccall N0CALL-13 \
|
||||||
|
exec /usr/local/bin/noaacap.py
|
||||||
|
|
||||||
|
</beacon>
|
||||||
|
|
||||||
|
The program will check for a new alert or update every 2 minutes, but only
|
||||||
|
send if there is a change.
|
||||||
|
|
||||||
|
Edit /etc/noaacap.conf:
|
||||||
|
|
||||||
|
Do not remove the [noaacap] section header.
|
||||||
|
|
||||||
|
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
|
||||||
|
the following command: timedatectl list-timezones | grep America.
|
||||||
|
|
||||||
|
myZone should be set to the zone number. A list of zones may be found at
|
||||||
|
https://alerts.weather.gov/. Only list one zone. You could use a county code
|
||||||
|
rather than a zone code if you wish.
|
||||||
|
|
||||||
|
USE
|
||||||
|
|
||||||
|
It would be good to hear from users of this software. Please email
|
||||||
|
k2dls@k2dls.net with the callsign-SSID of your APRS station that is sending
|
||||||
|
weather alerts via this program.
|
||||||
|
|
||||||
|
Dan Srebnick, K2DLS
|
||||||
|
08/19/2017
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
Copyright 2017 Daniel L. Srebnick (K2DLS)
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
Binary file not shown.
Loading…
Reference in New Issue