/ literary-clock / code.py
code.py
1 # SPDX-FileCopyrightText: 2022 Eva Herrada for Adafruit Industries 2 # SPDX-License-Identifier: MIT 3 4 import time 5 6 import ssl 7 import gc 8 import socketpool 9 import wifi 10 import adafruit_minimqtt.adafruit_minimqtt as MQTT 11 from adafruit_io.adafruit_io import IO_MQTT 12 import adafruit_datetime 13 import adafruit_display_text 14 from adafruit_display_text import label 15 import board 16 from adafruit_bitmap_font import bitmap_font 17 import displayio 18 from adafruit_display_shapes.rect import Rect 19 20 UTC_OFFSET = -4 21 22 quotes = {} 23 with open("quotes.csv", "r", encoding="UTF-8") as F: 24 for quote_line in F: 25 split = quote_line.split("|") 26 quotes[split[0]] = split[1:] 27 28 display = board.DISPLAY 29 splash = displayio.Group() 30 display.show(splash) 31 32 arial = bitmap_font.load_font("fonts/Arial-12.pcf") 33 bold = bitmap_font.load_font("fonts/Arial-Bold-12.pcf") 34 LINE_SPACING = 0.8 35 HEIGHT = arial.get_bounding_box()[1] 36 QUOTE_X = 10 37 QUOTE_Y = 7 38 39 rect = Rect(0, 0, 296, 128, fill=0xFFFFFF, outline=0xFFFFFF) 40 splash.append(rect) 41 42 quote = label.Label( 43 font=arial, 44 x=QUOTE_X, 45 y=QUOTE_Y, 46 color=0x000000, 47 line_spacing=LINE_SPACING, 48 ) 49 50 splash.append(quote) 51 time_label = label.Label( 52 font=bold, 53 color=0x000000, 54 line_spacing=LINE_SPACING, 55 ) 56 splash.append(time_label) 57 58 time_label_2 = label.Label( 59 font=bold, 60 color=0x000000, 61 line_spacing=LINE_SPACING, 62 ) 63 splash.append(time_label_2) 64 65 after_label = label.Label( 66 font=arial, 67 color=0x000000, 68 line_spacing=LINE_SPACING, 69 ) 70 splash.append(after_label) 71 72 after_label_2 = label.Label( 73 font=arial, 74 color=0x000000, 75 line_spacing=LINE_SPACING, 76 ) 77 splash.append(after_label_2) 78 79 author_label = label.Label( 80 font=arial, x=QUOTE_X, y=115, color=0x000000, line_spacing=LINE_SPACING 81 ) 82 splash.append(author_label) 83 84 try: 85 from secrets import secrets 86 except ImportError: 87 print("WiFi secrets are kept in secrets.py, please add them there!") 88 raise 89 90 aio_username = secrets["aio_username"] 91 aio_key = secrets["aio_key"] 92 93 print(f"Connecting to {secrets['ssid']}") 94 wifi.radio.connect(secrets["ssid"], secrets["password"]) 95 print(f"Connected to {secrets['ssid']}!") 96 97 98 def get_width(font, text): 99 return sum(font.get_glyph(ord(c)).shift_x for c in text) 100 101 102 def smart_split(text, font, width): 103 words = "" 104 spl = text.split(" ") 105 for i, word in enumerate(spl): 106 words += f" {word}" 107 lwidth = get_width(font, words) 108 if width + lwidth > 276: 109 spl[i] = "\n" + spl[i] 110 text = " ".join(spl) 111 break 112 return text 113 114 115 def connected(client): # pylint: disable=unused-argument 116 io.subscribe_to_time("iso") 117 118 119 def disconnected(client): # pylint: disable=unused-argument 120 print("Disconnected from Adafruit IO!") 121 122 123 def update_text(hour_min): 124 quote.text = ( 125 time_label.text 126 ) = time_label_2.text = after_label.text = after_label_2.text = "" 127 128 before, time_text, after = quotes[hour_min][0].split("^") 129 text = adafruit_display_text.wrap_text_to_pixels(before, 276, font=arial) 130 quote.text = "\n".join(text) 131 132 for line in text: 133 width = get_width(arial, line) 134 135 time_text = smart_split(time_text, bold, width) 136 137 split_time = time_text.split("\n") 138 if time_text[0] != "\n": 139 time_label.x = time_x = QUOTE_X + width 140 time_label.y = time_y = QUOTE_Y + int((len(text) - 1) * HEIGHT * LINE_SPACING) 141 time_label.text = split_time[0] 142 if "\n" in time_text: 143 time_label_2.x = time_x = QUOTE_X 144 time_label_2.y = time_y = QUOTE_Y + int(len(text) * HEIGHT * LINE_SPACING) 145 wrapped = adafruit_display_text.wrap_text_to_pixels( 146 split_time[1], 276, font=arial 147 ) 148 time_label_2.text = "\n".join(wrapped) 149 width = get_width(bold, split_time[-1]) + time_x - QUOTE_X 150 151 if after: 152 after = smart_split(after, arial, width) 153 154 split_after = after.split("\n") 155 if after[0] != "\n": 156 after_label.x = QUOTE_X + width 157 after_label.y = time_y 158 after_label.text = split_after[0] 159 if "\n" in after: 160 after_label_2.x = QUOTE_X 161 after_label_2.y = time_y + int(HEIGHT * LINE_SPACING) 162 wrapped = adafruit_display_text.wrap_text_to_pixels( 163 split_after[1], 276, font=arial 164 ) 165 after_label_2.text = "\n".join(wrapped) 166 167 author = f"{quotes[hour_min][2]} - {quotes[hour_min][1]}" 168 author_label.text = adafruit_display_text.wrap_text_to_pixels( 169 author, 276, font=arial 170 )[0] 171 time.sleep(display.time_to_refresh + 0.1) 172 display.refresh() 173 174 175 LAST = None 176 177 178 def message(client, feed_id, payload): # pylint: disable=unused-argument 179 global LAST # pylint: disable=global-statement 180 timezone = adafruit_datetime.timezone.utc 181 timezone._offset = adafruit_datetime.timedelta( # pylint: disable=protected-access 182 seconds=UTC_OFFSET * 3600 183 ) 184 datetime = adafruit_datetime.datetime.fromisoformat(payload[:-1]).replace( 185 tzinfo=timezone 186 ) 187 local_datetime = datetime.tzinfo.fromutc(datetime) 188 print(local_datetime) 189 hour_min = f"{local_datetime.hour:02}:{local_datetime.minute:02}" 190 if local_datetime.minute != LAST: 191 if hour_min in quotes: 192 update_text(hour_min) 193 194 LAST = local_datetime.minute 195 gc.collect() 196 197 198 # Create a socket pool 199 pool = socketpool.SocketPool(wifi.radio) 200 201 # Initialize a new MQTT Client object 202 mqtt_client = MQTT.MQTT( 203 broker="io.adafruit.com", 204 port=1883, 205 username=secrets["aio_username"], 206 password=secrets["aio_key"], 207 socket_pool=pool, 208 ssl_context=ssl.create_default_context(), 209 ) 210 211 # Initialize an Adafruit IO MQTT Client 212 io = IO_MQTT(mqtt_client) 213 214 # Connect the callback methods defined above to Adafruit IO 215 io.on_connect = connected 216 io.on_disconnect = disconnected 217 io.on_message = message 218 219 # Connect to Adafruit IO 220 print("Connecting to Adafruit IO...") 221 io.connect() 222 223 while True: 224 try: 225 io.loop() 226 except (ValueError, RuntimeError) as e: 227 print("Failed to get data, retrying\n", e) 228 wifi.reset() 229 io.reconnect() 230 continue 231 time.sleep(1)