├── arduino ├── schematic.pdf ├── iss_picture.pdf ├── fritzing │ ├── lamp.fzz │ └── RGB Common Cathode LED.fzpz └── ISS_Lamp.pde ├── pixmaps ├── iss_blank.png ├── iss_pass.png ├── ISS_notify_icon.png ├── iss_pass.svg ├── iss_blank.svg └── iss_logo.svg ├── uninstall.sh ├── install.sh ├── GNOME_ISS_notify.server ├── README.markdown └── ISS-notify-applet.py /arduino/schematic.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natronics/ISS-Notify/HEAD/arduino/schematic.pdf -------------------------------------------------------------------------------- /pixmaps/iss_blank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natronics/ISS-Notify/HEAD/pixmaps/iss_blank.png -------------------------------------------------------------------------------- /pixmaps/iss_pass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natronics/ISS-Notify/HEAD/pixmaps/iss_pass.png -------------------------------------------------------------------------------- /arduino/iss_picture.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natronics/ISS-Notify/HEAD/arduino/iss_picture.pdf -------------------------------------------------------------------------------- /arduino/fritzing/lamp.fzz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natronics/ISS-Notify/HEAD/arduino/fritzing/lamp.fzz -------------------------------------------------------------------------------- /pixmaps/ISS_notify_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natronics/ISS-Notify/HEAD/pixmaps/ISS_notify_icon.png -------------------------------------------------------------------------------- /arduino/fritzing/RGB Common Cathode LED.fzpz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/natronics/ISS-Notify/HEAD/arduino/fritzing/RGB Common Cathode LED.fzpz -------------------------------------------------------------------------------- /uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | rm -f /usr/local/bin/ISS-notify-applet.py 4 | rm -f /usr/share/pixmaps/iss_blank.png 5 | rm -f /usr/share/pixmaps/iss_pass.png 6 | rm -f /usr/lib/bonobo/servers/GNOME_ISS_notify.server 7 | 8 | killall gnome-panel 9 | -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | chmod +x ISS-notify-applet.py 6 | 7 | cp ISS-notify-applet.py /usr/local/bin/ 8 | cp pixmaps/*.png /usr/share/pixmaps/ 9 | cp GNOME_ISS_notify.server /usr/lib/bonobo/servers/ 10 | 11 | 12 | killall gnome-panel 13 | -------------------------------------------------------------------------------- /GNOME_ISS_notify.server: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /arduino/ISS_Lamp.pde: -------------------------------------------------------------------------------- 1 | /** 2 | * Simple indicator lamp. There are RGB LED's on the pins. This will listen on 3 | * the serial port and pulse colors of light when it hears a "1". It will stop 4 | * pulsing on a "0" 5 | * 6 | * @author Nathan Bergey 7 | * @version Wednesday, June 08 2011 8 | * 9 | * @section LICENSE 10 | * 11 | * This program is free software; you can redistribute it and/or 12 | * modify it under the terms of the GNU General Public License as 13 | * published by the Free Software Foundation; either version 2 of 14 | * the License, or (at your option) any later version. 15 | * 16 | * This program is distributed in the hope that it will be useful, but 17 | * WITHOUT ANY WARRANTY; without even the implied warranty of 18 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 19 | * General Public License for more details at 20 | * http://www.gnu.org/copyleft/gpl.html 21 | */ 22 | 23 | static int redPin = 4; 24 | static int grePin = 9; 25 | static int bluPin = 5; 26 | 27 | int dly = 300; 28 | 29 | int redState = LOW; 30 | int greState = LOW; 31 | int command = 0; 32 | 33 | boolean light = false; 34 | 35 | void setup() 36 | { 37 | RESET_PINS(); 38 | } 39 | 40 | void loop() 41 | { 42 | // Serial Commands 43 | if (Serial.available()) 44 | { 45 | command = Serial.read(); // will not be -1 46 | if (command == 49) { // "1" 47 | light = true; 48 | } 49 | if (command == 48) { // "0" 50 | light = false; 51 | } 52 | } 53 | command = 0; 54 | 55 | if (light) 56 | pulse(); 57 | } 58 | 59 | void pulse() 60 | { 61 | // Pulse Up 62 | for (int i = 0; i < 256; i++) 63 | { 64 | analogWrite(grePin, i); 65 | 66 | if (i > 85) 67 | analogWrite(bluPin, i - 85); 68 | if (i > 170) 69 | analogWrite(redPin, i - 170); 70 | delay(10); 71 | } 72 | for (int i = 171; i < 256; i++) 73 | { 74 | analogWrite(bluPin, i); 75 | analogWrite(redPin, i - 85); 76 | delay(10); 77 | } 78 | for (int i = 171; i < 256; i++) 79 | { 80 | analogWrite(redPin, i); 81 | delay(10); 82 | } 83 | 84 | delay(1000); 85 | 86 | // Pulse Down 87 | for (int i = 255; i >= 0; i--) 88 | { 89 | analogWrite(grePin, i); 90 | delay(10); 91 | } 92 | 93 | for (int i = 255; i >= 0; i--) 94 | { 95 | analogWrite(redPin, i); 96 | delay(10); 97 | } 98 | 99 | 100 | for (int i = 255; i >= 0; i--) 101 | { 102 | analogWrite(bluPin, i); 103 | delay(10); 104 | } 105 | 106 | } 107 | 108 | void RESET_PINS() 109 | { 110 | pinMode(redPin, OUTPUT); 111 | pinMode(grePin, OUTPUT); 112 | pinMode(bluPin, OUTPUT); 113 | 114 | digitalWrite(redPin, LOW); 115 | digitalWrite(grePin, LOW); 116 | digitalWrite(bluPin, LOW); 117 | } 118 | 119 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | This is an old software project that puts an icon on certain linux desktops. 2 | 3 | **Looking for the light-up hardware project? See:** 4 | 5 | # 6 | 7 | 8 | 9 | ### Python Gnome-Panel Applet for International Space Station Passes 10 | 11 | This is a quick and dirty python app that runs in the Gnome panel and acts as an alarm for when the [Internations Space Station](http://en.wikipedia.org/wiki/International_Space_Station) is overhead. 12 | 13 | When loaded it downloads a list of upcoming passes from [heavens-above.com](http://heavens-above.com). It then goes to sleep and the next time the ISS is overhead it turns red and, optionally, lights LED's on an ardruino. 14 | 15 | 16 | ### To install: 17 | 18 | Make sure you have the right packages installed 19 | 20 | $ sudo apt-get install python python-gnomeapplet python-serial 21 | 22 | Then install it 23 | 24 | $ sudo ./install.sh 25 | 26 | After the gnome pannel restarts right click and choose "add to panel". ISS-Notify should appear in the list. 27 | 28 | ### To update: 29 | 30 | Get the latest code (`git pull`) then remove the applet from your panel if it's running and try 31 | 32 | $ sudo ./uninstall.sh 33 | $ sudo ./install.sh 34 | 35 | ### To uninstall: 36 | 37 | $ sudo ./uninstall.sh 38 | 39 | ### To test: 40 | 41 | $ ./ISS-notify-applet.py -debug 42 | 43 | This will run the applet in it's own window and you can see print statements in the terminal you called if from. Great for debuging. 44 | 45 | ### Setting your location: 46 | 47 | Find this line in ISS-notify-applet.py 48 | 49 | class PyApplet(): 50 | ha = HeavensAbove(45.47361, -122.64931, 100, "PST") 51 | 52 | and change the latitude and longitude to your location. 53 | 54 | ha = HeavensAbove(latitude, longitude, altitude, timezone) 55 | 56 | ### Connection to notification lamp 57 | 58 | See the circuit diagram and firmware in the arduino folder. You might have to set some udev rules to get a predictable name for the python app. 59 | 60 | Find this line in ISS-notify-applet.py 61 | 62 | class lamp(): 63 | device = '/dev/ttyACM0' 64 | 65 | Make `/dev/ttyACM0` the right thing (`dmesg | tail` is often useful). Example of the udev rule that worked for me 66 | 67 | SUBSYSTEMS=="usb", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789]?", MODE:="0666" 68 | KERNEL=="ttyACM*", ATTRS{idVendor}=="16c0", ATTRS{idProduct}=="04[789]?", SYMLINK+="ttyUSB00%n", MODE:="0666", ENV{ID_MM_DEVICE_IGNORE}="1" 69 | 70 | ### License 71 | 72 | © 2011 [Nathan Bergey](http://twitter.com/natronics) 73 | 74 | This program is free software; you can redistribute it and/ormodify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. 75 | 76 | This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details at 77 | 78 | -------------------------------------------------------------------------------- /ISS-notify-applet.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import pygtk 3 | pygtk.require('2.0') 4 | import sys 5 | import gtk, gtk.gdk, gobject 6 | import gnomeapplet 7 | import datetime 8 | import urllib2 9 | import signal 10 | import serial 11 | 12 | class lamp(): 13 | """Allows the use of a serial connected lamp as the alarm""" 14 | 15 | # Set this to the right name...some testing might be required 16 | device = '/dev/ttyACM0' 17 | 18 | def write_to_device(self, c): 19 | """Writes a characer out via serial""" 20 | try: 21 | ser = serial.Serial(self.device, 9600, timeout=0.5) 22 | ser.write(c) 23 | ser.close() 24 | except: 25 | print "ISS-Notify: Failed to find lamp, continuing happily" 26 | pass 27 | 28 | def lights_on(self): 29 | """Send the signal to turn on the lights""" 30 | self.write_to_device('1') 31 | 32 | def lights_off(self): 33 | """Send the signal to turn off the lights""" 34 | self.write_to_device('0') 35 | 36 | class HeavensAbove: 37 | """Scrapes data about the ISS from http://heavens-above.com""" 38 | 39 | lat = 0 40 | lon = 0 41 | alt = 0 42 | tz = "GMT" 43 | 44 | # These defaults are useful for debuging if you're impatient 45 | next_pass = datetime.datetime.today() + datetime.timedelta(0,5) 46 | seconds_to_next_pass = 5 47 | pass_length = 7 48 | 49 | def __init__(self, lat, lon, alt, tz): 50 | """Set the postion and altitude information to values""" 51 | self.lat = lat 52 | self.lon = lon 53 | self.alt = alt 54 | self.tz = tz 55 | 56 | def get_passes(self): 57 | """This gets a web page with predictable output from www.heavens-above.com and parses it for all upcoming ISS passes""" 58 | 59 | def remove_chars(s, chars): 60 | """Useful utility, send it a drity string and a string of characters to strip from the dirty string""" 61 | for c in chars: 62 | s = s.replace(c,"") 63 | return s 64 | 65 | today = datetime.datetime.today() 66 | year = today.year 67 | passes_dict = [] 68 | 69 | # Get the html page from www.heavens-above.com 70 | url = "http://www.heavens-above.com/AllPass1Sat.asp?satid=25544&lat=%f&lng=%f&alt=%0.0f&tz=%s" % (self.lat, self.lon, self.alt, self.tz) 71 | req = urllib2.Request(url) 72 | response = urllib2.urlopen(req) 73 | data = response.read() 74 | 75 | # Strip out tabs, new lines, and other white space 76 | data = remove_chars(data, '\t\n\r') 77 | 78 | # Get just the with the data in it from the html 79 | table = data.split(r'
')[1] 80 | table = table.split(r'
')[0] 81 | 82 | # Break out each row in the table, skip the first two (just contains metadata) 83 | passes = table.split('')[3:] 84 | 85 | # Go through each row 86 | for i, apass in enumerate(passes): 87 | # split the row into cells 88 | details = apass.split('') 89 | 90 | # parse the data out into variables 91 | date = details[1][-15:-9].strip() 92 | begin_time = details[2][0:8].strip() 93 | begin_alt = details[3][0:2].strip() 94 | begin_az = details[4][0:3].strip() 95 | max_time = details[5][0:8].strip() 96 | max_alt = details[6][0:2].strip() 97 | max_az = details[7][0:3].strip() 98 | end_time = details[8][0:8].strip() 99 | end_alt = details[9][0:2].strip() 100 | end_az = details[10][0:3].strip() 101 | 102 | # further parse the date 103 | day = date[0:2] 104 | month = date[3:] 105 | 106 | #debug 107 | #print i, date, month, day, begin_time, begin_alt, begin_az, max_time, max_alt, max_az, end_time, end_alt, end_az 108 | 109 | # Find the begining and ending dates and turn them into datetime objects 110 | begin_datetime = datetime.datetime.strptime("%d-%s-%s %s" % (year, month, day, begin_time), "%Y-%b-%d %H:%M:%S") 111 | end_datetime = datetime.datetime.strptime("%d-%s-%s %s" % (year, month, day, end_time), "%Y-%b-%d %H:%M:%S") 112 | 113 | #debug 114 | #print i, begin_datetime, end_datetime 115 | 116 | # Store the data in a list 117 | passes_dict.append({"begin_time": begin_datetime, "end_time": end_datetime}) 118 | 119 | # Return all the data 120 | return passes_dict 121 | 122 | def get_next_pass(self): 123 | """This will try and get all the upcoming passes from www.heavens-above.com and store the data for upcoming one""" 124 | 125 | now = datetime.datetime.today() 126 | 127 | try: 128 | # Get all passes 129 | passes = self.get_passes() 130 | 131 | # Loop through the passes and find the first upcoming one 132 | for apass in passes: 133 | next_pass = apass["begin_time"] 134 | timedelta = next_pass - now 135 | past = timedelta.days 136 | if past >= 0: 137 | alarm_sleep_time = timedelta.seconds 138 | break 139 | 140 | # How long will this pass last? 141 | duration = apass["end_time"] - next_pass 142 | 143 | self.next_pass = next_pass 144 | self.seconds_to_next_pass = alarm_sleep_time 145 | self.pass_length = duration.seconds 146 | except: 147 | # I don't know what to do here 148 | print "Time lookup failed!!" 149 | 150 | class PyApplet(): 151 | """A gnome-panel applet that alerts a user if the International Space Station is overhead""" 152 | 153 | alarm = {} 154 | ha = HeavensAbove(45.47361, -122.64931, 100, "PST") 155 | lamp = lamp() 156 | iss_blank = gtk.image_new_from_file("/usr/share/pixmaps/iss_blank.png") 157 | iss_pass = gtk.image_new_from_file("/usr/share/pixmaps/iss_pass.png") 158 | 159 | def __init__(self, applet): 160 | self.applet=applet 161 | 162 | # Button 163 | self.button = gtk.Button() 164 | self.button.set_image(self.iss_blank) 165 | self.button.connect('button-press-event', self.showMenu, self.applet) 166 | self.button.set_relief(gtk.RELIEF_NONE) 167 | self.applet.add(self.button) 168 | 169 | # Background 170 | self.applet.set_background_widget(self.applet) 171 | 172 | # We've now packed the UI, so show it 173 | self.applet.show_all() 174 | 175 | # Get the next pass 176 | self.ha.get_next_pass() 177 | 178 | # Next event 179 | self.alarm = gobject.timeout_add_seconds(self.ha.seconds_to_next_pass, self.pass_begin_alarm) 180 | 181 | def pass_begin_alarm(self): 182 | """Triggered when the begin pass alarm goes off""" 183 | # Turn on lights 184 | print "begin pass" 185 | self.begin_pass() 186 | 187 | # Next Event 188 | gobject.source_remove(self.alarm) 189 | self.alarm = gobject.timeout_add_seconds(self.ha.pass_length, self.pass_end_alarm) 190 | return True 191 | 192 | def pass_end_alarm(self): 193 | """Triggered when the end pass alarm goes off""" 194 | # Turn lights off 195 | print "end pass" 196 | self.end_pass() 197 | 198 | # Get the next pass 199 | self.ha.get_next_pass() 200 | 201 | gobject.source_remove(self.alarm) 202 | self.alarm = gobject.timeout_add_seconds(self.ha.seconds_to_next_pass, self.pass_begin_alarm) 203 | return True 204 | 205 | def begin_pass(self): 206 | self.button.set_image(self.iss_pass) 207 | self.lamp.lights_on() 208 | 209 | def end_pass(self): 210 | self.button.set_image(self.iss_blank) 211 | self.lamp.lights_off() 212 | 213 | def showMenu(self, button, event, applet): 214 | if event.type == gtk.gdk.BUTTON_PRESS and event.button == 3: 215 | button.emit_stop_by_name("button_press_event") 216 | self.create_menu(applet) 217 | 218 | def create_menu(self, applet): 219 | next_pass_string = self.ha.next_pass.strftime("%a %H:%M:%S") 220 | time_to_pass = self.ha.next_pass - datetime.datetime.today() 221 | time_to_pass_days = time_to_pass.days 222 | time_to_pass_minutes = int(time_to_pass.seconds / 60.0) 223 | 224 | if time_to_pass_days < 0: 225 | next_pass_string = "Not sure" 226 | else: 227 | next_pass_string = next_pass_string + " - %d days, %d minutes" % (time_to_pass_days, time_to_pass_minutes) 228 | 229 | propxml=""" 230 | 231 | 232 | 233 | 234 | 235 | """ % (next_pass_string) 236 | 237 | verbs = [("next_pass", self.showAboutDialog), ("update", self.update_pass_data), ("test_lamp", self.test_lamp), ("about", self.showAboutDialog)] 238 | 239 | applet.setup_menu(propxml, verbs, None) 240 | 241 | def test_lamp(self, widget, menuname): 242 | self.lamp.lights_on() 243 | # short delay 244 | for i in range(1000): 245 | k = i + 1 246 | self.lamp.lights_off() 247 | 248 | def update_pass_data(self, widget, menuname): 249 | self.ha.get_next_pass() 250 | signal.alarm(0) 251 | signal.signal(signal.SIGALRM, self.pass_begin_alarm) 252 | signal.alarm(self.ha.seconds_to_next_pass) 253 | 254 | def showAboutDialog(self, widget, menuname): 255 | #print menuname 256 | pass 257 | 258 | def class_factory(applet, iid): 259 | PyApplet(applet) 260 | return True 261 | 262 | if len(sys.argv) == 2 and sys.argv[1] == "-debug": 263 | print "running in window" 264 | main_window = gtk.Window(gtk.WINDOW_TOPLEVEL) 265 | main_window.set_title("Python Applet") 266 | main_window.connect("destroy", gtk.main_quit) 267 | app = gnomeapplet.Applet() 268 | class_factory(app, None) 269 | app.reparent(main_window) 270 | main_window.show_all() 271 | gtk.main() 272 | sys.exit() 273 | 274 | gnomeapplet.bonobo_factory("OAFIID:ISS_Gnome_Panel_Factory", 275 | gnomeapplet.Applet.__gtype__, 276 | "hello", "0", class_factory) 277 | -------------------------------------------------------------------------------- /pixmaps/iss_pass.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 64 | 70 | 76 | 82 | 88 | 93 | 98 | 103 | 109 | 115 | 121 | 127 | 133 | 139 | 145 | 150 | 156 | 162 | 168 | 173 | 179 | 185 | 191 | 197 | 202 | 207 | 213 | 219 | 225 | 231 | 237 | 243 | 249 | 255 | 256 | 257 | 258 | -------------------------------------------------------------------------------- /pixmaps/iss_blank.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 46 | 48 | 49 | 51 | image/svg+xml 52 | 54 | 55 | 56 | 57 | 58 | 74 | 80 | 82 | 88 | 94 | 100 | 105 | 110 | 115 | 121 | 127 | 133 | 139 | 145 | 151 | 157 | 162 | 168 | 174 | 180 | 185 | 191 | 197 | 203 | 209 | 214 | 219 | 225 | 231 | 237 | 243 | 249 | 255 | 261 | 267 | 268 | 269 | 270 | -------------------------------------------------------------------------------- /pixmaps/iss_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 24 | 26 | 30 | 34 | 35 | 44 | 45 | 67 | 69 | 70 | 72 | image/svg+xml 73 | 75 | 76 | 77 | 78 | 79 | 94 | 100 | 107 | 110 | 117 | 123 | 129 | 135 | 140 | 146 | 152 | 159 | 165 | 172 | 178 | 184 | 190 | 196 | 202 | 209 | 215 | 221 | 227 | 234 | 241 | 248 | 254 | 261 | 267 | 273 | 279 | 285 | 291 | 297 | 303 | 304 | 305 | 306 | --------------------------------------------------------------------------------