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