|
| 1 | +# ON AIR sign for YouTube livestreaming |
| 2 | +# Runs on Airlift Metro M4 with 64x32 RGB Matrix display & shield |
| 3 | + |
| 4 | +import time |
| 5 | +import board |
| 6 | +import displayio |
| 7 | +import adafruit_display_text.label |
| 8 | +from adafruit_display_shapes.rect import Rect |
| 9 | +from adafruit_display_shapes.polygon import Polygon |
| 10 | +from adafruit_bitmap_font import bitmap_font |
| 11 | +from adafruit_matrixportal.network import Network |
| 12 | +from adafruit_matrixportal.matrix import Matrix |
| 13 | + |
| 14 | +# Get wifi details and more from a secrets.py file |
| 15 | +try: |
| 16 | + from secrets import secrets |
| 17 | +except ImportError: |
| 18 | + print("WiFi secrets are kept in secrets.py, please add them there!") |
| 19 | + raise |
| 20 | + |
| 21 | +# Set up where we'll be fetching data from |
| 22 | +# Adafruit YouTube channel: |
| 23 | +CHANNEL_ID = ( |
| 24 | + "UCpOlOeQjj7EsVnDh3zuCgsA" # this isn't a secret but you have to look it up |
| 25 | +) |
| 26 | + |
| 27 | +DATA_SOURCE = ( |
| 28 | + "https://www.googleapis.com/youtube/v3/search?part=snippet&channelId=" |
| 29 | + + CHANNEL_ID |
| 30 | + + "&type=video&eventType=live&key=" |
| 31 | + + secrets["youtube_token"] |
| 32 | +) |
| 33 | +DATA_LOCATION1 = ["items"] |
| 34 | + |
| 35 | +# Number of seconds between checking, if this is too quick the query quota will run out |
| 36 | +UPDATE_DELAY = 300 |
| 37 | + |
| 38 | +# Times are in 24-hour format for simplification |
| 39 | +OPERATING_TIME_START = "12:00" # what hour to start checking |
| 40 | +OPERATING_TIME_END = "19:00" # what hour to stop checking |
| 41 | + |
| 42 | +# --- Display setup --- |
| 43 | +matrix = Matrix() |
| 44 | +display = matrix.display |
| 45 | +network = Network(status_neopixel=board.NEOPIXEL, debug=False) |
| 46 | + |
| 47 | +# --- Drawing setup --- |
| 48 | +# Create a Group |
| 49 | +group = displayio.Group(max_size=22) |
| 50 | +# Create a bitmap object |
| 51 | +bitmap = displayio.Bitmap(64, 32, 2) # width, height, bit depth |
| 52 | +# Create a color palette |
| 53 | +color = displayio.Palette(4) |
| 54 | +color[0] = 0x000000 # black |
| 55 | +color[1] = 0xFF0000 # red |
| 56 | +color[2] = 0x444444 # dim white |
| 57 | +color[3] = 0xDD8000 # gold |
| 58 | +# Create a TileGrid using the Bitmap and Palette |
| 59 | +tile_grid = displayio.TileGrid(bitmap, pixel_shader=color) |
| 60 | +# Add the TileGrid to the Group |
| 61 | +group.append(tile_grid) |
| 62 | + |
| 63 | +# draw the frame for startup |
| 64 | +rect1 = Rect(0, 0, 2, 32, fill=color[2]) |
| 65 | +rect2 = Rect(62, 0, 2, 32, fill=color[2]) |
| 66 | +rect3 = Rect(2, 0, 9, 2, fill=color[0]) |
| 67 | +rect4 = Rect(53, 0, 9, 2, fill=color[0]) |
| 68 | +rect5 = Rect(2, 30, 12, 2, fill=color[0]) |
| 69 | +rect6 = Rect(50, 30, 12, 2, fill=color[0]) |
| 70 | + |
| 71 | +group.append(rect1) |
| 72 | +group.append(rect2) |
| 73 | +group.append(rect3) |
| 74 | +group.append(rect4) |
| 75 | +group.append(rect5) |
| 76 | +group.append(rect6) |
| 77 | + |
| 78 | + |
| 79 | +def redraw_frame(): # to adjust spacing at bottom later |
| 80 | + rect3.fill = color[2] |
| 81 | + rect4.fill = color[2] |
| 82 | + rect5.fill = color[2] |
| 83 | + rect6.fill = color[2] |
| 84 | + |
| 85 | + |
| 86 | +# draw the wings w polygon shapes |
| 87 | +wing_polys = [] |
| 88 | + |
| 89 | +wing_polys.append(Polygon([(3, 3), (9, 3), (9, 4), (4, 4)], outline=color[1])) |
| 90 | +wing_polys.append(Polygon([(5, 6), (9, 6), (9, 7), (6, 7)], outline=color[1])) |
| 91 | +wing_polys.append(Polygon([(7, 9), (9, 9), (9, 10), (8, 10)], outline=color[1])) |
| 92 | +wing_polys.append(Polygon([(54, 3), (60, 3), (59, 4), (54, 4)], outline=color[1])) |
| 93 | +wing_polys.append(Polygon([(54, 6), (58, 6), (57, 7), (54, 7)], outline=color[1])) |
| 94 | +wing_polys.append(Polygon([(54, 9), (56, 9), (55, 10), (54, 10)], outline=color[1])) |
| 95 | + |
| 96 | +for wing_poly in wing_polys: |
| 97 | + group.append(wing_poly) |
| 98 | + |
| 99 | + |
| 100 | +def redraw_wings(index): # to change colors |
| 101 | + for wing in wing_polys: |
| 102 | + wing.outline = color[index] |
| 103 | + |
| 104 | + |
| 105 | +# --- Content Setup --- |
| 106 | +deco_font = bitmap_font.load_font("/BellotaText-Bold-21.bdf") |
| 107 | + |
| 108 | +# Create two lines of text. Besides changing the text, you can also |
| 109 | +# customize the color and font (using Adafruit_CircuitPython_Bitmap_Font). |
| 110 | + |
| 111 | +# text positions |
| 112 | +on_x = 15 |
| 113 | +on_y = 9 |
| 114 | +off_x = 12 |
| 115 | +off_y = 9 |
| 116 | +air_x = 15 |
| 117 | +air_y = 25 |
| 118 | + |
| 119 | + |
| 120 | +text_line1 = adafruit_display_text.label.Label( |
| 121 | + deco_font, color=color[3], text="OFF", max_glyphs=6 |
| 122 | +) |
| 123 | +text_line1.x = off_x |
| 124 | +text_line1.y = off_y |
| 125 | + |
| 126 | +text_line2 = adafruit_display_text.label.Label( |
| 127 | + deco_font, color=color[1], text="AIR", max_glyphs=6 |
| 128 | +) |
| 129 | +text_line2.x = air_x |
| 130 | +text_line2.y = air_y |
| 131 | + |
| 132 | +# Put each line of text into the Group |
| 133 | +group.append(text_line1) |
| 134 | +group.append(text_line2) |
| 135 | + |
| 136 | + |
| 137 | +def startup_text(): |
| 138 | + text_line1.text = "ADA" |
| 139 | + text_line1.x = 10 |
| 140 | + text_line1.color = color[2] |
| 141 | + text_line2.text = "FRUIT" |
| 142 | + text_line2.x = 2 |
| 143 | + text_line2.color = color[2] |
| 144 | + redraw_wings(0) |
| 145 | + display.show(group) |
| 146 | + |
| 147 | + |
| 148 | +startup_text() # display the startup text |
| 149 | + |
| 150 | + |
| 151 | +def update_text(state): |
| 152 | + if state: # if switch is on, text is "ON" at startup |
| 153 | + text_line1.text = "ON" |
| 154 | + text_line1.x = on_x |
| 155 | + text_line1.color = color[1] |
| 156 | + text_line2.text = "AIR" |
| 157 | + text_line2.x = air_x |
| 158 | + text_line2.color = color[1] |
| 159 | + redraw_wings(1) |
| 160 | + redraw_frame() |
| 161 | + display.show(group) |
| 162 | + else: # else, text if "OFF" at startup |
| 163 | + text_line1.text = "OFF" |
| 164 | + text_line1.x = off_x |
| 165 | + text_line1.color = color[3] |
| 166 | + text_line2.text = "AIR" |
| 167 | + text_line2.x = air_x |
| 168 | + text_line2.color = color[3] |
| 169 | + redraw_wings(3) |
| 170 | + redraw_frame() |
| 171 | + display.show(group) |
| 172 | + |
| 173 | + |
| 174 | +def get_status(): |
| 175 | + """ |
| 176 | + Get the status whether we are on/off air within operating hours |
| 177 | + If outside of hours, return False |
| 178 | + """ |
| 179 | + # Get the time values we need |
| 180 | + now = time.localtime() |
| 181 | + start_hour, start_minute = OPERATING_TIME_START.split(":") |
| 182 | + end_hour, end_minute = OPERATING_TIME_END.split(":") |
| 183 | + |
| 184 | + # Convert time into a float for easy calculations |
| 185 | + start_time = int(start_hour) + (int(start_minute) / 60) |
| 186 | + end_time = int(end_hour) + (int(end_minute) / 60) |
| 187 | + current_time = now[3] + (now[4] / 60) |
| 188 | + |
| 189 | + if start_time <= current_time < end_time: |
| 190 | + try: |
| 191 | + on_air = network.fetch_data(DATA_SOURCE, json_path=(DATA_LOCATION1,)) |
| 192 | + if len(on_air) > 0: |
| 193 | + return True |
| 194 | + except RuntimeError: |
| 195 | + return False |
| 196 | + |
| 197 | + return False |
| 198 | + |
| 199 | + |
| 200 | +# Synchronize Board's clock to Internet |
| 201 | +network.get_local_time() |
| 202 | +mode_state = get_status() |
| 203 | +update_text(mode_state) |
| 204 | +last_check = None |
| 205 | + |
| 206 | + |
| 207 | +while True: |
| 208 | + if last_check is None or time.monotonic() > last_check + UPDATE_DELAY: |
| 209 | + try: |
| 210 | + status = get_status() |
| 211 | + if status: |
| 212 | + if mode_state == 0: # state has changed, toggle it |
| 213 | + update_text(1) |
| 214 | + mode_state = 1 |
| 215 | + else: |
| 216 | + if mode_state == 1: |
| 217 | + update_text(0) |
| 218 | + mode_state = 0 |
| 219 | + print("On Air:", status) |
| 220 | + last_check = time.monotonic() |
| 221 | + except RuntimeError as e: |
| 222 | + print("Some error occured, retrying! -", e) |
0 commit comments