/ Raspberry_Pi_On_Air_Sign / onair.py
onair.py
1 # SPDX-FileCopyrightText: 2018 Phillip Burgess for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 #!/usr/bin/python 6 7 # "ON AIR" sign controller for Raspberry Pi. Polls Ustream and Google+ 8 # Hangouts for online status, activates PowerSwitch Tail II as needed, 9 # turns on lamp. Requires RPi.GPIO library. 10 # 11 # Written by Phil Burgess / Paint Your Dragon for Adafruit Industries. 12 # 13 # Adafruit invests time and resources providing this open source code, 14 # please support Adafruit and open-source hardware by purchasing products 15 # from Adafruit! 16 # 17 # Resources: 18 # http://www.adafruit.com/products/268 19 # http://www.markertek.com/Studio-Gear/Studio-Warning-Lights-Signs.xhtml 20 21 import bisect, calendar, json, time, urllib 22 import RPi.GPIO as GPIO 23 24 pin = 24 # PowerSwitch Tail connects to this GPIO pin 25 26 # Ustream settings ----------------------------------------------------------- 27 # 'uKey' is your Developer API key (request one at developer.ustream.tv). 28 # 'uChannel' is Ustream channel to monitor. 29 uKey = 'PUT_USTREAM_DEVELOPER_API_KEY_KERE' 30 uChannel = 'adafruit-industries' 31 uUrl = ('http://api.ustream.tv/json/channel/' + uChannel + 32 '/getValueOf/status?key=' + uKey ) 33 34 # Google+ settings ----------------------------------------------------------- 35 # 'gKey' is your API key from the Google APIs API Access page (need to switch 36 # on G+ on API Services page first). 'gId' is the account ID to monitor (can 37 # find this in the URL of your profile page, mild nuisance but ID is used 38 # because user name is not guaranteed unique. 39 gKey = 'PUT_GOOGLE_API_KEY_HERE' 40 gId = '112526208786662512291' # Adafruit account ID 41 gUrl = ('https://www.googleapis.com/plus/v1/people/' + gId + 42 '/activities/public?maxResults=4&' + 43 'fields=items(title,published),nextPageToken&key=' + gKey) 44 gOn = 'is hanging out' # This phrase in title indicates an active hangout 45 46 # List of starting times (HH:MM) and polling frequency (seconds) ------------- 47 # This is to provide a more responsive 'on air' switchover time without 48 # blowing through search bandwidth limits. Use more frequent checks during 49 # known 'likely to switch' periods, infrequent during 'out of office' times. 50 # Currently follows same pattern each day; doesn't have a weekly schedule. 51 times = [ 52 ("06:00", 60), # 6am, office hours starting, poll once per minute 53 ("21:25", 10), # 9:25pm, Show & Tell starting soon, poll 6X/minute 54 ("21:35", 30), # S&T underway, reduce polling to 2X per minute 55 ("21:55", 10), # 9:55pm, AAE starting soon, poll 6X/minute again 56 ("22:05", 30), # AAE underway, slow polling to 2X per minute 57 ("23:10", 60), # AAE over (plus extra), return to once per minute 58 ("00:00", 900) ] # After midnight, gone home, poll every 15 minutes 59 60 def req(url): # Open connection, read and deserialize JSON document ---------- 61 connection = urllib.urlopen(url) 62 try: data = json.load(connection) 63 finally: connection.close() 64 return data 65 66 def paginate(pageToken): # --------------------------------------------------- 67 global gOnline, latestPost, timeThreshold 68 69 # Output from Google+ API is paginated, limited to 20 items max. 70 # We can't necessarily read everything in one pass, may need to 71 # make repeated calls passing a "page token" from one to the next. 72 response = req( 73 (gUrl + '&pageToken=' + pageToken) if pageToken else gUrl) 74 for item in response['items']: 75 utcTime = time.strptime( 76 item['published'].split('.', 1)[0], 77 '%Y-%m-%dT%H:%M:%S') 78 seconds = calendar.timegm(utcTime) # UTC -> epoch 79 if seconds > latestPost: 80 # Keep track of time of most recent post. 81 # If the time threshold is more than 24 hours 82 # before this, it can be moved up -- otherwise 83 # searches will eventually reach all the way 84 # back to the last hangout which may be a full 85 # week prior. 86 latestPost = seconds 87 t = latestPost - 60 * 60 * 24 88 if(timeThreshold < t): timeThreshold = t 89 if seconds < timeThreshold: 90 # Time threshold reached, no on-air message 91 # found in any posts newer than the threshold, 92 # no hangout occurring. Stop search. Save 93 # time of latest post as new threshold; 94 # no need to search earlier than that for 95 # hangouts, they won't appear retroactively. 96 # (Items are in reverse chronological order.) 97 gOnline = False 98 timeThreshold = latestPost 99 return None # Stop search; no further pages 100 if gOn in item['title']: 101 # On-air message found! Set global gOnline 102 # flag, set time threshold to hangout time; 103 # need to keep testing back to this time to 104 # confirm hangout is still running. (If it 105 # ends, title changes and will no longer 106 # contain the on-air string. There is no 107 # separate event to indicate end of hangout.) 108 gOnline = True 109 timeThreshold = seconds 110 return None 111 return response['nextPageToken'] # Continue search on next page 112 113 # Startup -------------------------------------------------------------------- 114 115 GPIO.setwarnings(False) # Don't bug me about existing pin state 116 GPIO.setmode(GPIO.BCM) # Use Broadcom pin numbers 117 GPIO.setup(pin, GPIO.OUT) # Enable output 118 GPIO.output(pin, GPIO.LOW) # Pin off by default 119 120 # Convert times[] to a new list with units in integer minutes 121 mins = [] 122 for t in times: 123 x = t[0].split(":") 124 mins.append([(int(x[0]) % 24) * 60 + int(x[1]), t[1]]) 125 126 mins.sort() # Sort in-place in increasing order 127 128 # If first time is not midnight, insert an item there, duplicating the 129 # polling frequency of the last item in the list. 130 if mins[0][0] > 0: mins.insert(0, [0, mins[len(mins)-1][1]]) 131 132 timeThreshold = latestPost = 0 133 134 while 1: # Main loop --------------------------------------------------------- 135 136 uOnline = gOnline = False 137 startTime = time.time() 138 139 # Ustream broadcast query 140 try: uOnline = req(uUrl)['results'] == 'live' 141 except Exception as e: print ("Error: {0} : {1}").format(type(e), e.args) 142 143 # G+ hangout query 144 try: 145 pageToken = None 146 while 1: 147 pageToken = paginate(pageToken) 148 if pageToken is None: break 149 except Exception as e: print ("Error: {0} : {1}").format(type(e), e.args) 150 151 print ('G+ hangout: ') + ('online' if gOnline else 'offline') 152 print ('Ustream : ') + ('online' if uOnline else 'offline') 153 GPIO.output(pin, GPIO.HIGH if uOnline or gOnline else GPIO.LOW) 154 155 # Delay before next query 156 try: 157 n = time.time() # NAO 158 t = time.localtime(n) # Local time struct 159 m = t.tm_hour * 60 + t.tm_min # Convert to minutes 160 i = bisect.bisect(mins, [m, 60]) - 1 # mins[] list index 161 d = mins[i][1] - (n - startTime) # Time to next poll 162 if d > 0: 163 print ('Waiting ') + str(d) + ' seconds' 164 time.sleep(d) 165 except: 166 time.sleep(60)