/ PyPortal_User_Interface / code.py
code.py
1 # SPDX-FileCopyrightText: 2020 Richard Albritton for Adafruit Industries 2 # 3 # SPDX-License-Identifier: MIT 4 5 import time 6 import board 7 import microcontroller 8 import displayio 9 import busio 10 from analogio import AnalogIn 11 import neopixel 12 import adafruit_adt7410 13 from adafruit_bitmap_font import bitmap_font 14 from adafruit_display_text.label import Label 15 from adafruit_button import Button 16 import adafruit_touchscreen 17 from adafruit_pyportal import PyPortal 18 19 # ------------- Constants ------------- # 20 # Sound Effects 21 soundDemo = "/sounds/sound.wav" 22 soundBeep = "/sounds/beep.wav" 23 soundTab = "/sounds/tab.wav" 24 25 # Hex Colors 26 WHITE = 0xFFFFFF 27 RED = 0xFF0000 28 YELLOW = 0xFFFF00 29 GREEN = 0x00FF00 30 BLUE = 0x0000FF 31 PURPLE = 0xFF00FF 32 BLACK = 0x000000 33 34 # Default Label styling 35 TABS_X = 0 36 TABS_Y = 15 37 38 # Default button styling: 39 BUTTON_HEIGHT = 40 40 BUTTON_WIDTH = 80 41 42 # Default State 43 view_live = 1 44 icon = 1 45 icon_name = "Ruby" 46 button_mode = 1 47 switch_state = 0 48 49 # ------------- Functions ------------- # 50 # Backlight function 51 # Value between 0 and 1 where 0 is OFF, 0.5 is 50% and 1 is 100% brightness. 52 def set_backlight(val): 53 val = max(0, min(1.0, val)) 54 try: 55 board.DISPLAY.auto_brightness = False 56 except AttributeError: 57 pass 58 board.DISPLAY.brightness = val 59 60 61 # Helper for cycling through a number set of 1 to x. 62 def numberUP(num, max_val): 63 num += 1 64 if num <= max_val: 65 return num 66 else: 67 return 1 68 69 70 # Set visibility of layer 71 def layerVisibility(state, layer, target): 72 try: 73 if state == "show": 74 time.sleep(0.1) 75 layer.append(target) 76 elif state == "hide": 77 layer.remove(target) 78 except ValueError: 79 pass 80 81 82 # This will handle switching Images and Icons 83 def set_image(group, filename): 84 """Set the image file for a given goup for display. 85 This is most useful for Icons or image slideshows. 86 :param group: The chosen group 87 :param filename: The filename of the chosen image 88 """ 89 print("Set image to ", filename) 90 if group: 91 group.pop() 92 93 if not filename: 94 return # we're done, no icon desired 95 96 # CircuitPython 6 & 7 compatible 97 image_file = open(filename, "rb") 98 image = displayio.OnDiskBitmap(image_file) 99 image_sprite = displayio.TileGrid( 100 image, pixel_shader=getattr(image, "pixel_shader", displayio.ColorConverter()) 101 ) 102 103 # # CircuitPython 7+ compatible 104 # image = displayio.OnDiskBitmap(filename) 105 # image_sprite = displayio.TileGrid(image, pixel_shader=image.pixel_shader) 106 107 group.append(image_sprite) 108 109 110 # return a reformatted string with word wrapping using PyPortal.wrap_nicely 111 def text_box(target, top, string, max_chars): 112 text = pyportal.wrap_nicely(string, max_chars) 113 new_text = "" 114 test = "" 115 116 for w in text: 117 new_text += "\n" + w 118 test += "M\n" 119 120 text_height = Label(font, text="M", color=0x03AD31) 121 text_height.text = test # Odd things happen without this 122 glyph_box = text_height.bounding_box 123 target.text = "" # Odd things happen without this 124 target.y = int(glyph_box[3] / 2) + top 125 target.text = new_text 126 127 128 def get_Temperature(source): 129 if source: # Only if we have the temperature sensor 130 celsius = source.temperature 131 else: # No temperature sensor 132 celsius = microcontroller.cpu.temperature 133 return (celsius * 1.8) + 32 134 135 136 # ------------- Inputs and Outputs Setup ------------- # 137 light_sensor = AnalogIn(board.LIGHT) 138 try: 139 # attempt to init. the temperature sensor 140 i2c_bus = busio.I2C(board.SCL, board.SDA) 141 adt = adafruit_adt7410.ADT7410(i2c_bus, address=0x48) 142 adt.high_resolution = True 143 except ValueError: 144 # Did not find ADT7410. Probably running on Titano or Pynt 145 adt = None 146 147 # ------------- Screen Setup ------------- # 148 pyportal = PyPortal() 149 pyportal.set_background("/images/loading.bmp") # Display an image until the loop starts 150 pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=1) 151 152 # Touchscreen setup [ Rotate 270 ] 153 display = board.DISPLAY 154 display.rotation = 270 155 156 if board.board_id == "pyportal_titano": 157 screen_width = 320 158 screen_height = 480 159 set_backlight( 160 1 161 ) # 0.3 brightness does not cause the display to be visible on the Titano 162 else: 163 screen_width = 240 164 screen_height = 320 165 set_backlight(0.3) 166 167 # We want three buttons across the top of the screen 168 TAB_BUTTON_Y = 0 169 TAB_BUTTON_HEIGHT = 40 170 TAB_BUTTON_WIDTH = int(screen_width / 3) 171 172 # We want two big buttons at the bottom of the screen 173 BIG_BUTTON_HEIGHT = int(screen_height / 3.2) 174 BIG_BUTTON_WIDTH = int(screen_width / 2) 175 BIG_BUTTON_Y = int(screen_height - BIG_BUTTON_HEIGHT) 176 177 # Initializes the display touch screen area 178 ts = adafruit_touchscreen.Touchscreen( 179 board.TOUCH_YD, 180 board.TOUCH_YU, 181 board.TOUCH_XR, 182 board.TOUCH_XL, 183 calibration=((5200, 59000), (5800, 57000)), 184 size=(screen_width, screen_height), 185 ) 186 187 # ------------- Display Groups ------------- # 188 splash = displayio.Group() # The Main Display Group 189 view1 = displayio.Group() # Group for View 1 objects 190 view2 = displayio.Group() # Group for View 2 objects 191 view3 = displayio.Group() # Group for View 3 objects 192 193 # ------------- Setup for Images ------------- # 194 bg_group = displayio.Group() 195 splash.append(bg_group) 196 set_image(bg_group, "/images/BGimage.bmp") 197 198 icon_group = displayio.Group() 199 icon_group.x = 180 200 icon_group.y = 120 201 icon_group.scale = 1 202 view2.append(icon_group) 203 204 # ---------- Text Boxes ------------- # 205 # Set the font and preload letters 206 font = bitmap_font.load_font("/fonts/Helvetica-Bold-16.bdf") 207 font.load_glyphs(b"abcdefghjiklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890- ()") 208 209 # Text Label Objects 210 feed1_label = Label(font, text="Text Window 1", color=0xE39300) 211 feed1_label.x = TABS_X 212 feed1_label.y = TABS_Y 213 view1.append(feed1_label) 214 215 feed2_label = Label(font, text="Text Window 2", color=0xFFFFFF) 216 feed2_label.x = TABS_X 217 feed2_label.y = TABS_Y 218 view2.append(feed2_label) 219 220 sensors_label = Label(font, text="Data View", color=0x03AD31) 221 sensors_label.x = TABS_X 222 sensors_label.y = TABS_Y 223 view3.append(sensors_label) 224 225 sensor_data = Label(font, text="Data View", color=0x03AD31) 226 sensor_data.x = TABS_X + 16 # Indents the text layout 227 sensor_data.y = 150 228 view3.append(sensor_data) 229 230 # ---------- Display Buttons ------------- # 231 # This group will make it easy for us to read a button press later. 232 buttons = [] 233 234 # Main User Interface Buttons 235 button_view1 = Button( 236 x=0, # Start at furthest left 237 y=0, # Start at top 238 width=TAB_BUTTON_WIDTH, # Calculated width 239 height=TAB_BUTTON_HEIGHT, # Static height 240 label="View 1", 241 label_font=font, 242 label_color=0xFF7E00, 243 fill_color=0x5C5B5C, 244 outline_color=0x767676, 245 selected_fill=0x1A1A1A, 246 selected_outline=0x2E2E2E, 247 selected_label=0x525252, 248 ) 249 buttons.append(button_view1) # adding this button to the buttons group 250 251 button_view2 = Button( 252 x=TAB_BUTTON_WIDTH, # Start after width of a button 253 y=0, 254 width=TAB_BUTTON_WIDTH, 255 height=TAB_BUTTON_HEIGHT, 256 label="View 2", 257 label_font=font, 258 label_color=0xFF7E00, 259 fill_color=0x5C5B5C, 260 outline_color=0x767676, 261 selected_fill=0x1A1A1A, 262 selected_outline=0x2E2E2E, 263 selected_label=0x525252, 264 ) 265 buttons.append(button_view2) # adding this button to the buttons group 266 267 button_view3 = Button( 268 x=TAB_BUTTON_WIDTH * 2, # Start after width of 2 buttons 269 y=0, 270 width=TAB_BUTTON_WIDTH, 271 height=TAB_BUTTON_HEIGHT, 272 label="View 3", 273 label_font=font, 274 label_color=0xFF7E00, 275 fill_color=0x5C5B5C, 276 outline_color=0x767676, 277 selected_fill=0x1A1A1A, 278 selected_outline=0x2E2E2E, 279 selected_label=0x525252, 280 ) 281 buttons.append(button_view3) # adding this button to the buttons group 282 283 button_switch = Button( 284 x=0, # Start at furthest left 285 y=BIG_BUTTON_Y, 286 width=BIG_BUTTON_WIDTH, 287 height=BIG_BUTTON_HEIGHT, 288 label="Light Switch", 289 label_font=font, 290 label_color=0xFF7E00, 291 fill_color=0x5C5B5C, 292 outline_color=0x767676, 293 selected_fill=0x1A1A1A, 294 selected_outline=0x2E2E2E, 295 selected_label=0x525252, 296 ) 297 buttons.append(button_switch) # adding this button to the buttons group 298 299 button_2 = Button( 300 x=BIG_BUTTON_WIDTH, # Starts just after button 1 width 301 y=BIG_BUTTON_Y, 302 width=BIG_BUTTON_WIDTH, 303 height=BIG_BUTTON_HEIGHT, 304 label="Light Color", 305 label_font=font, 306 label_color=0xFF7E00, 307 fill_color=0x5C5B5C, 308 outline_color=0x767676, 309 selected_fill=0x1A1A1A, 310 selected_outline=0x2E2E2E, 311 selected_label=0x525252, 312 ) 313 buttons.append(button_2) # adding this button to the buttons group 314 315 # Add all of the main buttons to the splash Group 316 for b in buttons: 317 splash.append(b) 318 319 # Make a button to change the icon image on view2 320 button_icon = Button( 321 x=150, 322 y=60, 323 width=BUTTON_WIDTH, 324 height=BUTTON_HEIGHT, 325 label="Icon", 326 label_font=font, 327 label_color=0xFFFFFF, 328 fill_color=0x8900FF, 329 outline_color=0xBC55FD, 330 selected_fill=0x5A5A5A, 331 selected_outline=0xFF6600, 332 selected_label=0x525252, 333 style=Button.ROUNDRECT, 334 ) 335 buttons.append(button_icon) # adding this button to the buttons group 336 337 # Add this button to view2 Group 338 view2.append(button_icon) 339 340 # Make a button to play a sound on view2 341 button_sound = Button( 342 x=150, 343 y=170, 344 width=BUTTON_WIDTH, 345 height=BUTTON_HEIGHT, 346 label="Sound", 347 label_font=font, 348 label_color=0xFFFFFF, 349 fill_color=0x8900FF, 350 outline_color=0xBC55FD, 351 selected_fill=0x5A5A5A, 352 selected_outline=0xFF6600, 353 selected_label=0x525252, 354 style=Button.ROUNDRECT, 355 ) 356 buttons.append(button_sound) # adding this button to the buttons group 357 358 # Add this button to view2 Group 359 view3.append(button_sound) 360 361 # pylint: disable=global-statement 362 def switch_view(what_view): 363 global view_live 364 if what_view == 1: 365 button_view1.selected = False 366 button_view2.selected = True 367 button_view3.selected = True 368 layerVisibility("hide", splash, view2) 369 layerVisibility("hide", splash, view3) 370 layerVisibility("show", splash, view1) 371 elif what_view == 2: 372 # global icon 373 button_view1.selected = True 374 button_view2.selected = False 375 button_view3.selected = True 376 layerVisibility("hide", splash, view1) 377 layerVisibility("hide", splash, view3) 378 layerVisibility("show", splash, view2) 379 else: 380 button_view1.selected = True 381 button_view2.selected = True 382 button_view3.selected = False 383 layerVisibility("hide", splash, view1) 384 layerVisibility("hide", splash, view2) 385 layerVisibility("show", splash, view3) 386 387 # Set global button state 388 view_live = what_view 389 print("View {view_num:.0f} On".format(view_num=what_view)) 390 391 392 # pylint: enable=global-statement 393 394 # Set veriables and startup states 395 button_view1.selected = False 396 button_view2.selected = True 397 button_view3.selected = True 398 button_switch.label = "OFF" 399 button_switch.selected = True 400 401 layerVisibility("show", splash, view1) 402 layerVisibility("hide", splash, view2) 403 layerVisibility("hide", splash, view3) 404 405 # Update out Labels with display text. 406 text_box( 407 feed1_label, 408 TABS_Y, 409 "The text on this screen is wrapped so that all of it fits nicely into a " 410 "text box that is {} x {}.".format( 411 feed1_label.bounding_box[2], feed1_label.bounding_box[3] * 2 412 ), 413 30, 414 ) 415 416 text_box(feed2_label, TABS_Y, "Tap on the Icon button to meet a new friend.", 18) 417 418 text_box( 419 sensors_label, 420 TABS_Y, 421 "This screen can display sensor readings and tap Sound to play a WAV file.", 422 28, 423 ) 424 425 board.DISPLAY.show(splash) 426 427 428 # ------------- Code Loop ------------- # 429 while True: 430 touch = ts.touch_point 431 light = light_sensor.value 432 sensor_data.text = "Touch: {}\nLight: {}\nTemp: {:.0f}°F".format( 433 touch, light, get_Temperature(adt) 434 ) 435 436 # Will also cause screen to dim when hand is blocking sensor to touch screen 437 # # Adjust backlight 438 # if light < 1500: 439 # set_backlight(0.1) 440 # elif light < 3000: 441 # set_backlight(0.5) 442 # else: 443 # set_backlight(1) 444 445 # ------------- Handle Button Press Detection ------------- # 446 if touch: # Only do this if the screen is touched 447 # loop with buttons using enumerate() to number each button group as i 448 for i, b in enumerate(buttons): 449 if b.contains(touch): # Test each button to see if it was pressed 450 print("button{} pressed".format(i)) 451 if i == 0 and view_live != 1: # only if view1 is visible 452 pyportal.play_file(soundTab) 453 switch_view(1) 454 while ts.touch_point: 455 pass 456 if i == 1 and view_live != 2: # only if view2 is visible 457 pyportal.play_file(soundTab) 458 switch_view(2) 459 while ts.touch_point: 460 pass 461 if i == 2 and view_live != 3: # only if view3 is visible 462 pyportal.play_file(soundTab) 463 switch_view(3) 464 while ts.touch_point: 465 pass 466 if i == 3: 467 pyportal.play_file(soundBeep) 468 # Toggle switch button type 469 if switch_state == 0: 470 switch_state = 1 471 b.label = "ON" 472 b.selected = False 473 pixel.fill(WHITE) 474 print("Switch ON") 475 else: 476 switch_state = 0 477 b.label = "OFF" 478 b.selected = True 479 pixel.fill(BLACK) 480 print("Switch OFF") 481 # for debounce 482 while ts.touch_point: 483 pass 484 print("Switch Pressed") 485 if i == 4: 486 pyportal.play_file(soundBeep) 487 # Momentary button type 488 b.selected = True 489 print("Button Pressed") 490 button_mode = numberUP(button_mode, 5) 491 if button_mode == 1: 492 pixel.fill(RED) 493 elif button_mode == 2: 494 pixel.fill(YELLOW) 495 elif button_mode == 3: 496 pixel.fill(GREEN) 497 elif button_mode == 4: 498 pixel.fill(BLUE) 499 elif button_mode == 5: 500 pixel.fill(PURPLE) 501 switch_state = 1 502 button_switch.label = "ON" 503 button_switch.selected = False 504 # for debounce 505 while ts.touch_point: 506 pass 507 print("Button released") 508 b.selected = False 509 if i == 5 and view_live == 2: # only if view2 is visible 510 pyportal.play_file(soundBeep) 511 b.selected = True 512 while ts.touch_point: 513 pass 514 print("Icon Button Pressed") 515 icon = numberUP(icon, 3) 516 if icon == 1: 517 icon_name = "Ruby" 518 elif icon == 2: 519 icon_name = "Gus" 520 elif icon == 3: 521 icon_name = "Billie" 522 b.selected = False 523 text_box( 524 feed2_label, 525 TABS_Y, 526 "Every time you tap the Icon button the icon image will " 527 "change. Say hi to {}!".format(icon_name), 528 18, 529 ) 530 set_image(icon_group, "/images/" + icon_name + ".bmp") 531 if i == 6 and view_live == 3: # only if view3 is visible 532 b.selected = True 533 while ts.touch_point: 534 pass 535 print("Sound Button Pressed") 536 pyportal.play_file(soundDemo) 537 b.selected = False