/ PyPortal_Electioncal_US / electioncal_graphics.py
electioncal_graphics.py
  1  # SPDX-FileCopyrightText: 2020 Alvaro Figueroa for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  import time
  6  import json
  7  import displayio
  8  from adafruit_display_text.label import Label
  9  from adafruit_bitmap_font import bitmap_font
 10  
 11  cwd = ("/"+__file__).rsplit('/', 1)[0] # the current working directory (where this file is)
 12  
 13  small_font = cwd+"/fonts/Arial-12.bdf"
 14  medium_font = cwd+"/fonts/Arial-16.bdf"
 15  
 16  class Electioncal_Graphics(displayio.Group):
 17      def __init__(self, root_group, *, am_pm=True):
 18          super().__init__()
 19          self.am_pm = am_pm
 20          root_group.append(self)
 21          self._icon_group = displayio.Group()
 22          self.append(self._icon_group)
 23          self._text_group = displayio.Group()
 24          self.append(self._text_group)
 25  
 26          self._icon_sprite = None
 27          self._icon_file = None  # Remove when CircuitPython 6 support is dropped
 28          self.set_icon(cwd+"/icons/electioncal.bmp")
 29  
 30          self.small_font = bitmap_font.load_font(small_font)
 31          self.medium_font = bitmap_font.load_font(medium_font)
 32          glyphs = b'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,.: '
 33          self.small_font.load_glyphs(glyphs)
 34          self.medium_font.load_glyphs(glyphs)
 35  
 36          self.date_text = Label(self.small_font)
 37          self.date_text.x = 15
 38          self.date_text.y = 195
 39          self.date_text.color = 0xFFFFFF
 40          self._text_group.append(self.date_text)
 41  
 42          self.url_text = Label(self.small_font)
 43          self.url_text.x = 15
 44          self.url_text.y = 220
 45          self.url_text.color = 0xFFFFFF
 46          self._text_group.append(self.url_text)
 47          self.url_text.text = "Visit us at https://electioncal.us"
 48  
 49          self.state_text = Label(self.small_font)
 50          self.state_text.x = 15
 51          self.state_text.y = 10
 52          self.state_text.color = 0xFFFFFF
 53          self._text_group.append(self.state_text)
 54  
 55          self.election_date_text = Label(self.medium_font)
 56          self.election_date_text.x = 15
 57          self.election_date_text.y = 60
 58          self.election_date_text.color = 0xFFFFFF
 59          self._text_group.append(self.election_date_text)
 60  
 61          self.election_name_text = Label(self.small_font)
 62          self.election_name_text.x = 15
 63          self.election_name_text.y = 95
 64          self.election_name_text.color = 0xFFFFFF
 65          self._text_group.append(self.election_name_text)
 66  
 67          self.election_name_text_line2 = Label(self.small_font)
 68          self.election_name_text_line2.x = 15
 69          self.election_name_text_line2.y = 120
 70          self.election_name_text_line2.color = 0xFFFFFF
 71          self._text_group.append(self.election_name_text_line2)
 72  
 73  
 74      def load_data(self, election_data):
 75          try:
 76              self.electioncal = json.loads(election_data)  # pylint: disable=attribute-defined-outside-init
 77              self.state_text.text = self.electioncal["dates"][1]["county"] + ", " + self.electioncal["dates"][0]["state"] # pylint: disable=line-too-long
 78          except ValueError:
 79              print("Error loading JSON data: Please check the configuration of county and state, in code.py") # pylint: disable=line-too-long
 80              raise
 81  
 82      def elections_cycle(self):
 83          self.update_time()
 84          num_elections = len(self.electioncal["dates"])
 85  
 86          for i in range(0,num_elections):
 87              if self.date_text.text[10:] < self.electioncal["dates"][i]["date"]:
 88                  self.election_date_text.text = self.electioncal["dates"][i]["date"]
 89                  # splitting the line at around 40 chars seems ok for regular PyPortal
 90                  self.election_name_text_line2.text, self.election_name_text.text = self.paragrapher(self.electioncal["dates"][i]["name"], 40) # pylint: disable=line-too-long
 91                  time.sleep(30)
 92  
 93      def update_time(self):
 94          """Fetch the time.localtime(), parse it out and update the display text"""
 95          now = time.localtime()
 96          hour = now[3]
 97          minute = now[4]
 98          year = now[0]
 99          month = now[1]
100          day = now[2]
101          time_format_str = "%d:%02d"
102          date_format_str = "%d-%02d-%02d"
103          if self.am_pm:
104              if hour >= 12:
105                  hour -= 12
106                  time_format_str = time_format_str+" PM"
107              else:
108                  time_format_str = time_format_str+" AM"
109              if hour == 0:
110                  hour = 12
111          time_str = time_format_str % (hour, minute) # pylint: disable=unused-variable
112          date_str = date_format_str % (year, month, day)
113          self.date_text.text = "Today is: " + date_str
114  
115      def paragrapher(self, text, cut): # pylint: disable=no-self-use
116          """ Cuts a long line into two, having spaces in mind.
117          Note we return line2 first as it looks better to clear the line2
118          before printing a line1 with empty line2
119          We run from cut, backwards till we find a space.
120          """
121          if len(text) > cut:
122              for i in range(cut,0,-1):
123                  if text[i] == " ":
124                      break
125              line1 = text[0:i]
126              line2 = text[i+1:80]
127          else:
128              line1 = text
129              line2 = ""
130          return line2, line1
131  
132      def set_icon(self, filename):
133          """The background image to a bitmap file.
134  
135          :param filename: The filename of the chosen icon
136  
137          """
138          print("Set icon to ", filename)
139          if self._icon_group:
140              self._icon_group.pop()
141  
142          if not filename:
143              return  # we're done, no icon desired
144  
145          # CircuitPython 6 & 7 compatible
146          if self._icon_file:
147              self._icon_file.close()
148          self._icon_file = open(filename, "rb")
149          icon = displayio.OnDiskBitmap(self._icon_file)
150          self._icon_sprite = displayio.TileGrid(
151              icon,
152              pixel_shader=getattr(icon, 'pixel_shader', displayio.ColorConverter())
153          )
154  
155          # # CircuitPython 7+ compatible
156          # icon = displayio.OnDiskBitmap(filename)
157          # self._icon_sprite = displayio.TileGrid(icon, pixel_shader=icon.pixel_shader)
158  
159          self._icon_group.append(self._icon_sprite)