| 1 | #!/usr/bin/env python |
|---|
| 2 | |
|---|
| 3 | """ |
|---|
| 4 | StormSiren |
|---|
| 5 | Copyright (C) 2008 Chris Freeze <cfreeze/cfreeze_com\> |
|---|
| 6 | |
|---|
| 7 | Redistribution and use in source and binary forms, with or without |
|---|
| 8 | modification, are permitted provided that the following conditions |
|---|
| 9 | are met: |
|---|
| 10 | |
|---|
| 11 | 1. Redistributions of source code must retain the above copyright |
|---|
| 12 | notice, this list of conditions and the following disclaimer. |
|---|
| 13 | 2. Redistributions in binary form must reproduce the above copyright |
|---|
| 14 | notice, this list of conditions and the following disclaimer in the |
|---|
| 15 | documentation and/or other materials provided with the distribution. |
|---|
| 16 | |
|---|
| 17 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
|---|
| 18 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
|---|
| 19 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
|---|
| 20 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
|---|
| 21 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
|---|
| 22 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
|---|
| 23 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
|---|
| 24 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
|---|
| 25 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
|---|
| 26 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
|---|
| 27 | """ |
|---|
| 28 | |
|---|
| 29 | """ |
|---|
| 30 | This is StormSiren a program inspired by the StormSiren application |
|---|
| 31 | written by Roy McManus <slorf/users_sourceforge_net>. Much like the |
|---|
| 32 | original StormSiren written by Roy McManus <slorf/users_sourceforge_net>, |
|---|
| 33 | this program is a simple script capable of scanning and providing |
|---|
| 34 | notifications of weather bulletins issued by the National |
|---|
| 35 | Weather Service. StormSiren supports a wide range of paging devices |
|---|
| 36 | and filtering of alert types per alert device. While inspired by |
|---|
| 37 | StormSiren, StormSiren is a complete rewrite that is capable of using |
|---|
| 38 | the new CAP/XML feeds provided at http://www.weather.gov/alerts/. |
|---|
| 39 | |
|---|
| 40 | For more information there is see the README.TXT file located in the root |
|---|
| 41 | of this directory. |
|---|
| 42 | """ |
|---|
| 43 | |
|---|
| 44 | import re |
|---|
| 45 | import sys |
|---|
| 46 | import datetime |
|---|
| 47 | import logging |
|---|
| 48 | |
|---|
| 49 | from WeatherTypes import WeatherTypes |
|---|
| 50 | from CapTimezone import CapTimezone |
|---|
| 51 | from CapAlert import CapAlert |
|---|
| 52 | |
|---|
| 53 | class CapAtom(object): |
|---|
| 54 | def __init__(self,xml,state): |
|---|
| 55 | self.log = logging.getLogger("CapAtom") |
|---|
| 56 | |
|---|
| 57 | self.__tag_url = 'id' |
|---|
| 58 | self.__url_e = re.compile('(.*)x=(.*)$') |
|---|
| 59 | self.__url = '' |
|---|
| 60 | self.__id = '' |
|---|
| 61 | |
|---|
| 62 | self.__tag_area = 'cap:areaDesc' |
|---|
| 63 | self.__areas = [] |
|---|
| 64 | self.__state = state |
|---|
| 65 | |
|---|
| 66 | self.__tag_issued = 'cap:effective' |
|---|
| 67 | self.__tag_expires = 'cap:expires' |
|---|
| 68 | self.__tag_updated = 'updated' |
|---|
| 69 | self.__datetime_e = re.compile('(\d+)-(\d+)-(\d+)T(\d+):(\d+):(\d+)(\-|\+)(\d+):(\d+)$') |
|---|
| 70 | self.__issued = '' |
|---|
| 71 | self.__expires = '' |
|---|
| 72 | self.__updated = '' |
|---|
| 73 | |
|---|
| 74 | self.__tag_urgency = 'cap:urgency' |
|---|
| 75 | self.__urgency = '' |
|---|
| 76 | self.__tag_severity = 'cap:severity' |
|---|
| 77 | self.__severity = '' |
|---|
| 78 | self.__tag_certainty = 'cap:certainty' |
|---|
| 79 | self.__certainty = '' |
|---|
| 80 | |
|---|
| 81 | self.__tag_title = 'title' |
|---|
| 82 | self.__title_e = re.compile('^(.+) Issued At:.*$') |
|---|
| 83 | self.__title = '' |
|---|
| 84 | self.__matter = '' |
|---|
| 85 | self.__event_e = re.compile('(.*) (\w+)$') |
|---|
| 86 | self.__type = WeatherTypes() |
|---|
| 87 | |
|---|
| 88 | self.__expanded = False |
|---|
| 89 | self.__cap_alert = None |
|---|
| 90 | |
|---|
| 91 | self.__parse(xml) |
|---|
| 92 | |
|---|
| 93 | def __trim(self,str): |
|---|
| 94 | if(str): |
|---|
| 95 | str = str.strip() |
|---|
| 96 | str = str.replace("\n","") |
|---|
| 97 | |
|---|
| 98 | def __parse(self,xml): |
|---|
| 99 | self.__url = self.__getfield(xml,self.__tag_url) |
|---|
| 100 | |
|---|
| 101 | if(self.__url): |
|---|
| 102 | self.__url = self.__url.replace("\n", ' ').strip(' ') |
|---|
| 103 | parse_res = self.__url_e.search(self.__url) |
|---|
| 104 | if(parse_res != None): |
|---|
| 105 | self.__id = parse_res.group(2) |
|---|
| 106 | |
|---|
| 107 | self.__issued = self.getDateTime(self.__getfield(xml,self.__tag_issued)) |
|---|
| 108 | self.__expires = self.getDateTime(self.__getfield(xml,self.__tag_expires)) |
|---|
| 109 | self.__updated = self.getDateTime(self.__getfield(xml,self.__tag_updated)) |
|---|
| 110 | |
|---|
| 111 | self.__urgency = self.__trim(self.__getfield(xml,self.__tag_urgency)) |
|---|
| 112 | self.__severity = self.__trim(self.__getfield(xml,self.__tag_severity)) |
|---|
| 113 | self.__certainty = self.__trim(self.__getfield(xml,self.__tag_certainty)) |
|---|
| 114 | |
|---|
| 115 | areas = self.__getfield(xml,self.__tag_area) |
|---|
| 116 | if(areas): |
|---|
| 117 | areas = areas.replace("\n",' ') |
|---|
| 118 | areas = areas.replace("/", ';') |
|---|
| 119 | for area in areas.split(';'): |
|---|
| 120 | self.__areas.append(area.strip()) |
|---|
| 121 | |
|---|
| 122 | self.__title = self.__getfield(xml,self.__tag_title) |
|---|
| 123 | if(self.__title): |
|---|
| 124 | parse_res = self.__title_e.search(self.__title.replace("\n", ' ')) |
|---|
| 125 | if(parse_res): |
|---|
| 126 | parse_res = self.__event_e.search(parse_res.group(1)) |
|---|
| 127 | if(parse_res): |
|---|
| 128 | self.__matter = parse_res.group(1) |
|---|
| 129 | self.__type = WeatherTypes.fromString(parse_res.group(2)) |
|---|
| 130 | |
|---|
| 131 | def getDateTime(self,text): |
|---|
| 132 | timestamp = None |
|---|
| 133 | parse_res = self.__datetime_e.search(text) |
|---|
| 134 | if(parse_res): |
|---|
| 135 | timezone = CapTimezone() |
|---|
| 136 | timezone.setOffset(int(parse_res.group(8))) |
|---|
| 137 | timezone.setSign(parse_res.group(7)) |
|---|
| 138 | timestamp = datetime.datetime(int(parse_res.group(1)), int(parse_res.group(2)), int(parse_res.group(3)), int(parse_res.group(4)), int(parse_res.group(5)), int(parse_res.group(6)),0,timezone) |
|---|
| 139 | |
|---|
| 140 | return timestamp |
|---|
| 141 | |
|---|
| 142 | |
|---|
| 143 | def __getfield(self,xml,tag): |
|---|
| 144 | val = xml.getElementsByTagName(tag) |
|---|
| 145 | |
|---|
| 146 | if(val): |
|---|
| 147 | return val[0].firstChild.data |
|---|
| 148 | else: |
|---|
| 149 | return "" |
|---|
| 150 | |
|---|
| 151 | def display(self): |
|---|
| 152 | print self.__str__() |
|---|
| 153 | |
|---|
| 154 | def displayFull(self): |
|---|
| 155 | self.display() |
|---|
| 156 | print "\tDesc: " + self.description |
|---|
| 157 | |
|---|
| 158 | def getState(self): |
|---|
| 159 | return self.__state |
|---|
| 160 | |
|---|
| 161 | def getIssued(self): |
|---|
| 162 | if(self.__issued): |
|---|
| 163 | return self.__issued.strftime("%a %b %d %Y - %I:%M:%S %p") |
|---|
| 164 | else: |
|---|
| 165 | return None |
|---|
| 166 | |
|---|
| 167 | def getExpires(self): |
|---|
| 168 | if(self.__expires): |
|---|
| 169 | return self.__expires.strftime("%a %b %d %Y - %I:%M:%S %p") |
|---|
| 170 | else: |
|---|
| 171 | return None |
|---|
| 172 | |
|---|
| 173 | def getUpdated(self): |
|---|
| 174 | if(self.__updated): |
|---|
| 175 | return self.__updated.strftime("%a %b %d %Y - %I:%M:%S %p") |
|---|
| 176 | else: |
|---|
| 177 | return None |
|---|
| 178 | |
|---|
| 179 | def getType(self): |
|---|
| 180 | return self.__type |
|---|
| 181 | |
|---|
| 182 | def getId(self): |
|---|
| 183 | return self.__id |
|---|
| 184 | |
|---|
| 185 | def getAreas(self): |
|---|
| 186 | return self.__areas |
|---|
| 187 | |
|---|
| 188 | def getMatter(self): |
|---|
| 189 | return self.__matter |
|---|
| 190 | |
|---|
| 191 | def expanded(self): |
|---|
| 192 | return self.__expanded |
|---|
| 193 | |
|---|
| 194 | def expand(self,simfetch,proxy): |
|---|
| 195 | if(not self.__expanded): |
|---|
| 196 | self.__expanded = True |
|---|
| 197 | self.log.info("Expanding CAP: %s" % self.__id) |
|---|
| 198 | self.__cap_alert = CapAlert(self.__url,simfetch,proxy) |
|---|
| 199 | |
|---|
| 200 | def getDescription(self): |
|---|
| 201 | if(self.__cap_alert): |
|---|
| 202 | return self.__cap_alert.description |
|---|
| 203 | else: |
|---|
| 204 | return None |
|---|
| 205 | |
|---|
| 206 | def getHeadline(self): |
|---|
| 207 | if(self.__cap_alert): |
|---|
| 208 | return self.__cap_alert.headline |
|---|
| 209 | else: |
|---|
| 210 | return None |
|---|
| 211 | |
|---|
| 212 | def getUrl(self): |
|---|
| 213 | return self.__url |
|---|
| 214 | |
|---|
| 215 | def getUrgency(self): |
|---|
| 216 | return self.__urgency |
|---|
| 217 | |
|---|
| 218 | def getSeverity(self): |
|---|
| 219 | return self.__severity |
|---|
| 220 | |
|---|
| 221 | def getCertainty(self): |
|---|
| 222 | return self.__certainty |
|---|
| 223 | |
|---|
| 224 | id = property(getId,None,None) |
|---|
| 225 | areas = property(getAreas,None,None) |
|---|
| 226 | type = property(getType,None,None) |
|---|
| 227 | state = property(getState,None,None) |
|---|
| 228 | matter = property(getMatter,None,None) |
|---|
| 229 | headline = property(getHeadline,None,None) |
|---|
| 230 | url = property(getUrl,None,None) |
|---|
| 231 | issued = property(getIssued,None,None) |
|---|
| 232 | expires = property(getExpires,None,None) |
|---|
| 233 | updated = property(getUpdated,None,None) |
|---|
| 234 | urgency = property(getUrgency,None,None) |
|---|
| 235 | severity = property(getSeverity,None,None) |
|---|
| 236 | certainty = property(getCertainty,None,None) |
|---|
| 237 | headline = property(getHeadline,None,None) |
|---|
| 238 | description = property(getDescription,None,None) |
|---|
| 239 | |
|---|
| 240 | def __str__(self): |
|---|
| 241 | str = "\tId: " + self.id + "\n" + \ |
|---|
| 242 | "\tState: " + self.state + "\n" |
|---|
| 243 | str += "\tAreas: %s\n" % self.getAreas() |
|---|
| 244 | if(self.type != None): |
|---|
| 245 | str += "\tType: " + WeatherTypes.toString(self.__type) + "\n" |
|---|
| 246 | if(self.headline != None): |
|---|
| 247 | str += "\tHeadline: " + self.headline + "\n" |
|---|
| 248 | if(self.matter != None): |
|---|
| 249 | str += "\tMatter: " + self.matter + "\n" |
|---|
| 250 | if(self.url != None): |
|---|
| 251 | str += "\tURL: " + self.__url + "\n" |
|---|
| 252 | if(self.issued != None): |
|---|
| 253 | str += "\tIssued: " + self.issued + "\n" |
|---|
| 254 | if(self.expires != None): |
|---|
| 255 | str += "\tExpires: " + self.expires + "\n" |
|---|
| 256 | if(self.updated != None): |
|---|
| 257 | str += "\tUpdated: " + self.updated + "\n" |
|---|
| 258 | if(self.urgency != None): |
|---|
| 259 | str += "\tUrgency: " + self.__urgency + "\n" |
|---|
| 260 | if(self.severity != None): |
|---|
| 261 | str += "\tSeverity: " + self.__severity + "\n" |
|---|
| 262 | if(self.certainty != None): |
|---|
| 263 | str += "\tCertainty: " + self.__certainty + "\n" |
|---|
| 264 | return str |
|---|