Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| #!/usr/bin/python | |
| # | |
| # Watch p2000 alarms and notify for new alarms | |
| # | |
| # This program displays p2000 pager messages. p2000 is the pager system for the | |
| # dutch emergency services. Messages are downloaded from p2000-online.net every | |
| # so often (default: 60 seconds). This website also has a delay of about 90 | |
| # seconds, making the total delay approximately 2.5 minutes. | |
| # | |
| # Requirements: | |
| # - A linux desktop | |
| # - pynotify and gtk to display notifications | |
| # - beautifulsoup for parsing html | |
| # - libcanberra to play a sound when new alarms arrive (optional) | |
| # | |
| # (c)2011 Dennis Kaarsemaker | |
| # License: gpl 3+ | |
| from BeautifulSoup import BeautifulSoup as soup | |
| import ctypes | |
| import os | |
| import glib | |
| import gtk | |
| import pynotify | |
| import sys | |
| import time | |
| import urllib | |
| # These are the region names as used by p2000-online.net | |
| regions = ( | |
| "Amsterdam-Amstelland", | |
| "Brabant Noord", | |
| "Brabant ZuidOost", | |
| "Drenthe", | |
| "Flevoland", | |
| "Friesland", | |
| "Gelderland-Midden", | |
| "Gelderland-Zuid", | |
| "Gooi en Vechtstreek", | |
| "Groningen", | |
| "Haaglanden", | |
| "Hollands Midden", | |
| "IJsselland", | |
| "Kennemerland", | |
| "Limburg Noord", | |
| "Limburg Zuid", | |
| "Midden en West Brabant", | |
| "Noord- en Oost Gelderland", | |
| "Noord-Holland Noord", | |
| "Rotterdam Rijnmond", | |
| "Twente", | |
| "Utrecht", | |
| "Zaanstreek-Waterland", | |
| "Zeeland", | |
| "Zuid Holland Zuid", | |
| ) | |
| def _escape(data): | |
| return data.replace('&','&').replace('<','<').replace('>','>') | |
| class I(dict): | |
| def __getitem__(self, item): | |
| return 'http://alarmeringen.nl/static/images/icon_%s.png' % dict.__getitem__(self, item) | |
| class Alert(object): | |
| """Representation of a single message""" | |
| icons = I({ | |
| 'brandweer': 'fire', | |
| 'politie': 'police', | |
| 'ambulance': 'ambu', | |
| 'lifeliner': 'heli', | |
| 'knrm': 'boat', | |
| }) | |
| def __init__(self, date, time, who, region, text, recipients = None): | |
| self.time = '%s %s' % (date, time) | |
| self.who = who | |
| self.region = region | |
| self.text = text | |
| self.recipients = recipients or [] | |
| if self.is_lifeliner(): | |
| self.who = 'LifeLiner' | |
| def is_lifeliner(self): | |
| return 'lifeliner' in (''.join(self.recipients)).lower() | |
| def show(self, timeout): | |
| """Display a notification for this alarm""" | |
| title = "%s %s" % (self.who, self.region) | |
| text = "%s %s" % (self.time, self.text) | |
| mod = self.__module__ | |
| if mod == '__main__': | |
| path = sys.argv[0] | |
| else: | |
| path = sys.modules[mod].__file__ | |
| path = os.path.abspath(os.path.dirname(path)) | |
| imgl = self.icons[self.who.lower()] | |
| imgc = os.path.join(os.path.expanduser('~'), '.cache', os.path.basename(imgl)) | |
| if not os.path.exists(imgc): | |
| with open(imgc, 'w') as fd: | |
| req = urllib.urlopen(img) | |
| if req.code == 404: | |
| raise IOError | |
| fd.write(req.read()) | |
| notification = pynotify.Notification(title, _escape(text), 'file://' + imgc) | |
| notification.set_urgency(pynotify.URGENCY_CRITICAL) | |
| caps = pynotify.get_server_caps() | |
| if self.recipients and 'actions' in caps: | |
| notification.add_action("details", "Details", self.details) | |
| # If the notification is deleted, the callback will not be called | |
| _alerts.append(notification) | |
| notification.set_timeout(timeout * 1000) | |
| notification.show() | |
| def prnt(self): | |
| """Output this alarm on stdout""" | |
| print "%s %s %s %s" % (time.ctime(), self.time, self.who, self.region) | |
| print "%s %s %s" % (time.ctime(), ' ' * len(self.time), self.text) | |
| for r in self.recipients: | |
| print "%s %s %s" % (time.ctime(), ' ' * len(self.time), r) | |
| def details(self, notification, action): | |
| message = '<b>%s</b>\n%s\n<span foreground="gray">%s</span>' % (self.text, self.time, '\n'.join(self.recipients)) | |
| dialog = gtk.MessageDialog(type=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE) | |
| dialog.set_markup(message) | |
| dialog.set_title('%s %s' % (self.who, self.region)) | |
| dialog.connect("response", lambda *args: dialog.destroy()) | |
| dialog.show() | |
| dialog.set_keep_above(True) | |
| _alerts.remove(notification) | |
| _alerts = [] | |
| class P2000Interface(object): | |
| def __init__(self, opts): | |
| self.regions = opts.regions | |
| self.verbose = opts.verbose | |
| self.last_alert = None | |
| self.quiet = opts.quiet | |
| if not self.quiet: | |
| try: | |
| self.canberra = ctypes.CDLL('libcanberra.so.0') | |
| self.ctx = ctypes.c_void_p() | |
| self.canberra.ca_context_create(ctypes.byref(self.ctx)) | |
| except OSError: | |
| # Can't find libcanberra | |
| self.quiet = True | |
| pynotify.init("p2000 meldingen") | |
| def get_alerts(self): | |
| """Fetch all alerts for these regions""" | |
| url = 'http://www.p2000-online.net/p2000.php?%s&nofilterform=1' | |
| url = url % '&'.join(['%s=1' % x for x in self.regions]) | |
| if self.verbose: | |
| print time.ctime(), url | |
| try: | |
| data = urllib.urlopen(url).read() | |
| except IOError: | |
| if self.verbose: | |
| import traceback | |
| traceback.print_exc() | |
| return [] | |
| doc = soup(data) | |
| alerts = [] | |
| table = doc.body('table', {'style': 'align:center'})[0] | |
| for tr in table('tr'): | |
| if tr.td.get('class', None) == 'DT': | |
| alerts.append(Alert(*[x.text for x in tr('td')])) | |
| else: | |
| recipient = tr('td')[-1].text | |
| if recipient != ' ': | |
| alerts[-1].recipients.append(recipient) | |
| return alerts | |
| def alert_iteration(self, first_iteration = False): | |
| global _alerts | |
| _alerts = [] | |
| if self.verbose: | |
| print time.ctime(), "Fetching alerts..." | |
| alerts = self.get_alerts() | |
| if self.verbose: | |
| print time.ctime(), "Received %d alerts" % len(alerts) | |
| play_sound = True | |
| alerts = [x for x in alerts[:5] if x.time > self.last_alert] | |
| alerts.reverse() | |
| timeout = min(10,3*len(alerts)) | |
| if self.verbose: | |
| print "Timeout: %d" % timeout | |
| for alert in alerts: | |
| if play_sound: | |
| self.canberra.ca_context_play(self.ctx, 1, "event.id", "message-new-instant", None) | |
| play_sound = False | |
| if self.verbose: | |
| alert.prnt() | |
| alert.show(timeout) | |
| self.last_alert = alert.time | |
| return not first_iteration | |
| def main(): | |
| import optparse | |
| usage = """%prog [options] | |
| This tool autimatically downloads and displays p2000 alerts for the regions you | |
| specify (Default: Noord-Holland Noord and Flevoland). It can display images and | |
| play sounds too.""" | |
| p = optparse.OptionParser(usage = usage) | |
| default_regions = ("Flevoland","Noord-HollandNoord") | |
| p.add_option('-a', '--all', dest='all_regions', action='store_true', default=False, | |
| help="Show alerts for all regions") | |
| for r in regions: | |
| opt_r = '--%s' % r.lower().replace('-','').replace(' ','') | |
| p.add_option(opt_r, dest='regions', action='append_const', const=r.replace(' ',''), | |
| help="Show alerts for region %s" % r) | |
| p.add_option('-d', '--delay', dest='delay', type='int', default=60, metavar='SECS', | |
| help="Delay between updates (default: 60)") | |
| p.add_option('-v', '--verbose', dest='verbose', action='store_true', default=False, | |
| help="Verbose output") | |
| p.add_option('-q', '--quiet', dest='quiet', action='store_true', default=False, | |
| help="Don't play sounds when new alarms arrive") | |
| opts, args = p.parse_args() | |
| if not opts.regions: | |
| opts.regions = default_regions | |
| if opts.all_regions: | |
| opts.regions = [x.replace(' ','') for x in regions] | |
| ui = P2000Interface(opts) | |
| glib.timeout_add_seconds(0, ui.alert_iteration, True) | |
| glib.timeout_add_seconds(opts.delay, ui.alert_iteration) | |
| gtk.main() | |
| if __name__ == '__main__': | |
| main() |