Thursday, September 16, 2010

Where's Mah Intertubes

By popular demand (of a single cheesecakemonger)

Have you ever signed up with a shoddy Internet provider, where you get sudden and frequent connection failures that prevent your router from, well, routing? Are you getting tired of trying to stare down your modem in wait for the Internet connection to get up again after one of these failures? Maybe you'd prefer there to be some kind of script that you could use to be instantly notified when the connection gets back on, so you can both instantly resume obsessively browsing ICHC and get out of the living room into the kitchen and maybe do some dishes as your provider persists that crappy service is just what you need?

Well, this is the script you are looking for. You can run it after your connection went down and it'll play some sound when the connection is back again. That'd be if you run it (using some awesome sound effects from Battle for Wesnoth) as:
./wheresmahintertubes.py \
--yay=/usr/share/games/wesnoth/1.8/data/core/music/defeat.ogg \
--boo=/usr/share/games/wesnoth/1.8/data/core/music/victory.ogg &

Hell, you can even put it into your startup applications in Gnome or something (I figure making it into an init.d start-up script would be a bit overkill). Simplest way I can think of to do that in Ubuntu would be to go to System -> Preferences -> Starup Applications, there press Add and put the same command as above into the field aptly named Command.

The script has zero innovative mechanics: it tries to connect to Google by IP every 30 seconds and then plays an appropriate bit of accidental music through PyGame's mixer.

I figured pinging Google like that is the simplest way to determine whether the connection is up or down. Sure, it's not perfect, but it'll work well enough with the benefit of working within 5 minutes of me starting to write the script. Anyway, the timeout, the address, and the delay between checks can all be configured using the friendly commandline interface (i.e. command switches: --timeout, --uri, --delay).

Using PyGame is also a bit suspect in this application (overkill, mostly) and I did initially plan it to use Gstreamer but PyGame is 4 times less code written on my end, but neither requires suspiciously GTK-related libraries, nor does it mysteriously hang up or disobey orders... Indeed, it just works, and chances are you already have it installed on a lot of systems anyway, because you wanted to play Slingshot st some point...

So, here's the code. Enjoy.
 
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3 #
4 # Where's Mah Intertubes?!
5 #
6 # A simple script that lets you know when your connection goes down or comes
7 # back up with sounds.
8 #
9 # Depends:
10 # espeak (if you don't want to use sound files)
11 # pygame (if you do)
12 #
13 # Options:
14 # -h, --help show this help message and exit
15 # --speak recite a message when connection goes on or off
16 # (default)
17 # --off=AUDIO_OFF, --boo=AUDIO_OFF
18 # set a sound played when connection is lost
19 # --on=AUDIO_ON, --yay=AUDIO_ON
20 # set a sound played when connection is back on
21 # -t TIMEOUT, --timeout=TIMEOUT
22 # set timeout for checking if connection works (default:
23 # 10s)
24 # -d DELAY, --delay=DELAY
25 # set delay between connection checks (default: 30s)
26 # -u URI, --uri=URI, --url=URI
27 # ping this URI to see if connection works (default:
28 # http://74.125.77.147)
29 # -v, --verbose display information about things done by the program
30 #
31 # Examples:
32 # Let's say you want to check if you can connect and you're fine with the
33 # espeak dude to moan about it instead fo using a cool sound you can use one
34 # of the following (let it run in the background):
35 #
36 # ./wheresmahintertubes.py &
37 # ./wheresmahintertubes.py --speak &
38 #
39 # If you want some proper fun sounds then all you need is point them out:
40 #
41 # ./wheresmahintertubes.py --yay=file/for_on.ogg --boo=file/for_off.mp3 &
42 #
43 # License:
44 # Copyright (C) 2010 Konrad Siek <konrad.siek@gmail.com>
45 #
46 # This program is free software: you can redistribute it and/or modify it
47 # under the terms of the GNU General Public License version 3, as published
48 # by the Free Software Foundation.
49 #
50 # This program is distributed in the hope that it will be useful, but
51 # WITHOUT ANY WARRANTY; without even the implied warranties of
52 # MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
53 # PURPOSE. See the GNU General Public License for more details.
54 #
55 # You should have received a copy of the GNU General Public License along
56 # with this program. If not, see <http://www.gnu.org/licenses/>.
57 #
58
59 import time
60 import sys
61 import pygame
62
63 quiet = False
64 uri = "http://74.125.77.147"
65
66 def printerr(*args):
67 if quiet:
68 return
69 from sys import argv, stderr
70 from os.path import basename
71 stderr.write("%s:" % basename(argv[0]))
72 for arg in args:
73 stderr.write(" %s" % arg)
74 stderr.write("\n")
75
76 def printout(*args):
77 if quiet:
78 return
79 from sys import argv, stdout
80 from os.path import basename
81 stdout.write("%s:" % basename(argv[0]))
82 for arg in args:
83 stdout.write(" %s" % arg)
84 stdout.write("\n")
85
86 def can_has_connection(timeout):
87 import urllib2
88 printout("Checking ping to", uri)
89 try:
90 urllib2.urlopen(uri, timeout=timeout) # Google
91 except urllib2.URLError as error:
92 return False
93 return True
94
95 class Speaker:
96 def __init__(self):
97 self.library = {}
98
99 def add_to_library(self, key, path):
100 self.library[key] = path
101
102 def play_from_library(self, key):
103 if not key in self.library:
104 return False
105 self.play(self.library[key])
106 return True
107
108 def play(self, message):
109 from os import system
110 system('espeak %s' % message)
111
112 class PyGamePlayer:
113 def __init__(self):
114 pygame.init()
115 self.library = {}
116
117 def add_to_library(self, key, path):
118 self.library[key] = path
119
120 def play_from_library(self, key):
121 if not key in self.library:
122 return False
123 self.play(self.library[key])
124 return True
125
126 def play(self, path):
127 pygame.mixer.Sound(path).play()
128
129 class Checker:
130 def __init__(self, delay, timeout):
131 self.delay = delay
132 self.timeout = timeout
133
134 def run(self):
135 connected = can_has_connection(self.timeout)
136 printout('Initially the connection is', 'on' if connected else 'off')
137 while True:
138 time.sleep(self.delay)
139 current = can_has_connection(self.timeout)
140 if connected != current:
141 connected = current
142 self.react(connected)
143
144 def react(self, connected):
145 from threading import Thread
146 printout('The connection just went', 'on' if connected else 'off')
147 def run():
148 self.player.play_from_library(connected)
149 thread = Thread()
150 thread.run = run
151 thread.start()
152
153 if __name__ == '__main__':
154 from optparse import OptionParser
155 from os.path import basename
156 from sys import argv
157
158 usage = '\n%s [OPTIONS] ' % basename(argv[0]) + \
159 '--on=[SOUND FILE] --off=[SOUND FILE]\n' + \
160 '\tplay a sound when network connection goes up or down' + \
161 '\n%s [OPTIONS] ' % basename(argv[0]) + '--speak\n' + \
162 '\trecite a message when network connection goes up or down (boring...)'
163
164 description = 'Wait around and periodically check if the connection ' + \
165 'went up or down, and if that happens play an appropriate sound to ' + \
166 'indicate it to the user. Network connectivity is check by the ' + \
167 'the simple method of connecting a specific host address, and ' + \
168 'assuming that the entwork is down if it takes too much time for ' + \
169 'that host to respond.'
170
171 parser = OptionParser(usage=usage, description=description)
172
173 parser.add_option('--speak', action='store_true', dest='speak', \
174 help='recite a message when connection goes on or off (default)')
175 parser.add_option('--off', '--boo', action='store', dest='audio_off', \
176 help='set a sound played when connection is lost')
177 parser.add_option('--on', '--yay', action='store', dest='audio_on', \
178 help='set a sound played when connection is back on')
179 parser.add_option('-t', '--timeout', action='store', dest='timeout', \
180 help='set timeout for checking if connection works (default: 10s)', \
181 default=10, type='int')
182 parser.add_option('-d', '--delay', action='store', dest='delay', \
183 help='set delay between connection checks (default: 30s)', \
184 default=30, type='int')
185 parser.add_option('-u', '--uri', '--url', action='store', dest='uri', \
186 help='ping this URI to see if connection works (default: %s)' % uri, \
187 default=uri)
188 parser.add_option('-v', '--verbose', action='store_true', dest='verbose', \
189 help='display information about things done by the program')
190
191 opts, args = parser.parse_args()
192
193 quiet = not opts.verbose
194 uri = opts.uri
195
196 player = None
197
198 if opts.speak or not opts.audio_on or not opts.audio_off:
199 player = Speaker()
200 player.add_to_library(True, "Connection just went up")
201 player.add_to_library(False, "Connection just went down")
202 else:
203 player = PyGamePlayer()
204 player.add_to_library(True, opts.audio_on)
205 player.add_to_library(False, opts.audio_off)
206
207 checker = Checker(delay=opts.delay, timeout=opts.timeout)
208 checker.player = player
209
210 try:
211 checker.run()
212 except (KeyboardInterrupt, SystemExit):
213 printout('Exiting...')
214 running = False
The code is also available on GitHub at python/wheresmahintertubes.py.

2 comments:

Dariusz Dwornikowski said...

Please write a sound ping tools for network testing :)

Few years ago I read about such a hack to discover network topology with one laptop in a building of a size of our CW.

Kondziu said...

Just your average run-of-the-mill ICMP ping tool? It's just one library and two lines of actual code (apart from optparse and that).

And if you mean something more complex, then it sounds like a world of pain.