code.py
  1  # SPDX-FileCopyrightText: 2020 Liz Clark for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  import time
  6  from calendar import alarms
  7  from calendar import timers
  8  import board
  9  import displayio
 10  from digitalio import DigitalInOut, Direction, Pull
 11  from adafruit_button import Button
 12  from adafruit_pyportal import PyPortal
 13  import openweather_graphics  # pylint: disable=wrong-import-position
 14  
 15  # Get wifi details and more from a secrets.py file
 16  try:
 17      from secrets import secrets
 18  except ImportError:
 19      print("WiFi secrets are kept in secrets.py, please add them there!")
 20      raise
 21  
 22  # Use cityname, country code where countrycode is ISO3166 format.
 23  # E.g. "New York, US" or "London, GB"
 24  LOCATION = secrets['location']
 25  
 26  # Set up where we'll be fetching data from
 27  DATA_SOURCE = "http://api.openweathermap.org/data/2.5/weather?q="+LOCATION
 28  DATA_SOURCE += "&appid="+secrets['openweather_token']
 29  # You'll need to get a token from openweather.org, looks like 'b6907d289e10d714a6e88b30761fae22'
 30  DATA_LOCATION = []
 31  
 32  # Initialize the pyportal object and let us know what data to fetch and where
 33  # to display it
 34  pyportal = PyPortal(url=DATA_SOURCE,
 35                      json_path=DATA_LOCATION,
 36                      status_neopixel=board.NEOPIXEL,
 37                      default_bg=0x000000)
 38  
 39  display = board.DISPLAY
 40  
 41  #  the alarm sound file locations
 42  alarm_sound_trash = "/sounds/trash.wav"
 43  alarm_sound_bed = "/sounds/sleep.wav"
 44  alarm_sound_eat = "/sounds/eat.wav"
 45  
 46  #  the alarm sounds in an array that matches the order of the gfx & alarm check-ins
 47  alarm_sounds = [alarm_sound_trash, alarm_sound_bed,
 48                  alarm_sound_eat, alarm_sound_eat, alarm_sound_eat]
 49  
 50  #  setting up the bitmaps for the alarms
 51  
 52  #  sleep alarm
 53  sleep_bitmap = displayio.OnDiskBitmap(open("/sleepBMP.bmp", "rb"))
 54  sleep_tilegrid = displayio.TileGrid(sleep_bitmap, pixel_shader=getattr(sleep_bitmap, 'pixel_shader', displayio.ColorConverter()))
 55  group_bed = displayio.Group()
 56  group_bed.append(sleep_tilegrid)
 57  
 58  #  trash alarm
 59  trash_bitmap = displayio.OnDiskBitmap(open("/trashBMP.bmp", "rb"))
 60  trash_tilegrid = displayio.TileGrid(trash_bitmap, pixel_shader=getattr(trash_bitmap, 'pixel_shader', displayio.ColorConverter()))
 61  group_trash = displayio.Group()
 62  group_trash.append(trash_tilegrid)
 63  
 64  #  meal alarm
 65  eat_bitmap = displayio.OnDiskBitmap(open("/eatBMP.bmp", "rb"))
 66  eat_tilegrid = displayio.TileGrid(eat_bitmap, pixel_shader=getattr(eat_bitmap, 'pixel_shader', displayio.ColorConverter()))
 67  group_eat = displayio.Group()
 68  group_eat.append(eat_tilegrid)
 69  
 70  #  snooze touch screen buttons
 71  #  one for each alarm bitmap
 72  snooze_controls = [
 73      {'label': "snooze_trash", 'pos': (4, 222), 'size': (236, 90), 'color': None},
 74      {'label': "snooze_bed", 'pos': (4, 222), 'size': (236, 90), 'color': None},
 75      {'label': "snooze_eat", 'pos': (4, 222), 'size': (236, 90), 'color': None},
 76      ]
 77  
 78  #  setting up the snooze buttons as buttons
 79  snooze_buttons = []
 80  for s in snooze_controls:
 81      snooze_button = Button(x=s['pos'][0], y=s['pos'][1],
 82                             width=s['size'][0], height=s['size'][1],
 83                             style=Button.RECT,
 84                             fill_color=s['color'], outline_color=None,
 85                             name=s['label'])
 86      snooze_buttons.append(snooze_button)
 87  
 88  #  dismiss touch screen buttons
 89  #  one for each alarm bitmap
 90  dismiss_controls = [
 91      {'label': "dismiss_trash", 'pos': (245, 222), 'size': (230, 90), 'color': None},
 92      {'label': "dismiss_bed", 'pos': (245, 222), 'size': (230, 90), 'color': None},
 93      {'label': "dismiss_eat", 'pos': (245, 222), 'size': (230, 90), 'color': None},
 94      ]
 95  
 96  #  setting up the dismiss buttons as buttons
 97  dismiss_buttons = []
 98  for d in dismiss_controls:
 99      dismiss_button = Button(x=d['pos'][0], y=d['pos'][1],
100                              width=d['size'][0], height=d['size'][1],
101                              style=Button.RECT,
102                              fill_color=d['color'], outline_color=None,
103                              name=d['label'])
104      dismiss_buttons.append(dismiss_button)
105  
106  #  adding the touch screen buttons to the different alarm gfx groups
107  group_trash.append(snooze_buttons[0].group)
108  group_trash.append(dismiss_buttons[0].group)
109  group_bed.append(snooze_buttons[1].group)
110  group_bed.append(dismiss_buttons[1].group)
111  group_eat.append(snooze_buttons[2].group)
112  group_eat.append(dismiss_buttons[2].group)
113  
114  #  setting up the hardware snooze/dismiss buttons
115  switch_snooze = DigitalInOut(board.D3)
116  switch_snooze.direction = Direction.INPUT
117  switch_snooze.pull = Pull.UP
118  
119  switch_dismiss = DigitalInOut(board.D4)
120  switch_dismiss.direction = Direction.INPUT
121  switch_dismiss.pull = Pull.UP
122  
123  #  grabbing the alarm times from the calendar file
124  #  'None' is the placeholder for trash, which is weekly rather than daily
125  alarm_checks = [None, alarms['bed'],alarms['breakfast'],alarms['lunch'],alarms['dinner']]
126  #  all of the alarm graphics
127  alarm_gfx = [group_trash, group_bed, group_eat, group_eat, group_eat]
128  
129  #  allows for the openweather_graphics to show
130  gfx = openweather_graphics.OpenWeather_Graphics(pyportal.splash, am_pm=True, celsius=False)
131  
132  #  state machines
133  localtile_refresh = None
134  weather_refresh = None
135  dismissed = None
136  touched = None
137  start = None
138  alarm = None
139  snoozed = None
140  touch_button_snooze = None
141  touch_button_dismiss = None
142  phys_dismiss = None
143  phys_snooze = None
144  mode = 0
145  button_mode = 0
146  
147  #  weekday array
148  weekday = ["Mon.", "Tues.", "Wed.", "Thurs.", "Fri.", "Sat.", "Sun."]
149  
150  #  weekly alarm setup. checks for weekday and time
151  weekly_alarms = [alarms['trash']]
152  weekly_day = [alarms['trash'][0]]
153  weekly_time = [alarms['trash'][1]]
154  
155  while True:
156      # while esp.is_connected:
157      # only query the online time once per hour (and on first run)
158      if (not localtile_refresh) or (time.monotonic() - localtile_refresh) > 3600:
159          try:
160              print("Getting time from internet!")
161              pyportal.get_local_time()
162              localtile_refresh = time.monotonic()
163          except RuntimeError as e:
164              print("Some error occured, retrying! -", e)
165              continue
166  
167      if not alarm:
168      # only query the weather every 10 minutes (and on first run)
169      #  only updates if an alarm is not active
170          if (not weather_refresh) or (time.monotonic() - weather_refresh) > 600:
171              try:
172                  value = pyportal.fetch()
173                  print("Response is", value)
174                  gfx.display_weather(value)
175                  weather_refresh = time.monotonic()
176              except RuntimeError as e:
177                  print("Some error occured, retrying! -", e)
178                  continue
179      #  updates time to check alarms
180      #  checks every 30 seconds
181      #  identical to def(update_time) in openweather_graphics.py
182      if (not start) or (time.monotonic() - start) > 30:
183          #  grabs all the time data
184          clock = time.localtime()
185          date = clock[2]
186          hour = clock[3]
187          minute = clock[4]
188          day = clock[6]
189          today = weekday[day]
190          format_str = "%d:%02d"
191          date_format_str = " %d, %d"
192          if hour >= 12:
193              hour -= 12
194              format_str = format_str+" PM"
195          else:
196              format_str = format_str+" AM"
197          if hour == 0:
198              hour = 12
199          #  formats date display
200          today_str = today
201          time_str = format_str % (hour, minute)
202          #  checks for weekly alarms
203          for i in weekly_alarms:
204              w = weekly_alarms.index(i)
205              if time_str == weekly_time[w] and today == weekly_day[w]:
206                  print("trash time")
207                  alarm = True
208                  if alarm and not dismissed and not snoozed:
209                      display.show(alarm_gfx[w])
210                      pyportal.play_file(alarm_sounds[w])
211                  mode = w
212                  print("mode is:", mode)
213          #  checks for daily alarms
214          for i in alarm_checks:
215              a = alarm_checks.index(i)
216              if time_str == alarm_checks[a]:
217                  alarm = True
218                  if alarm and not dismissed and not snoozed:
219                      display.show(alarm_gfx[a])
220                      pyportal.play_file(alarm_sounds[a])
221                  mode = a
222                  print(mode)
223          #  calls update_time() from openweather_graphics to update
224          #  clock display
225          gfx.update_time()
226          gfx.update_date()
227          #  resets time counter
228          start = time.monotonic()
229  
230      #  allows for the touchscreen buttons to work
231      if mode > 1:
232          button_mode = 2
233      else:
234          button_mode = mode
235          #  print("button mode is", button_mode)
236  
237      #  hardware snooze/dismiss button setup
238      if switch_dismiss.value and phys_dismiss:
239          phys_dismiss = False
240      if switch_snooze.value and phys_snooze:
241          phys_snooze = False
242      if not switch_dismiss.value and not phys_dismiss:
243          phys_dismiss = True
244          print("pressed dismiss button")
245          dismissed = True
246          alarm = False
247          display.show(pyportal.splash)
248          touched = time.monotonic()
249          mode = mode
250      if not switch_snooze.value and not phys_snooze:
251          phys_snooze = True
252          print("pressed snooze button")
253          display.show(pyportal.splash)
254          snoozed = True
255          alarm = False
256          touched = time.monotonic()
257          mode = mode
258  
259      #  touchscreen button setup
260      touch = pyportal.touchscreen.touch_point
261      if not touch and touch_button_snooze:
262          touch_button_snooze = False
263      if not touch and touch_button_dismiss:
264          touch_button_dismiss = False
265      if touch:
266          if snooze_buttons[button_mode].contains(touch) and not touch_button_snooze:
267              print("Touched snooze")
268              display.show(pyportal.splash)
269              touch_button_snooze = True
270              snoozed = True
271              alarm = False
272              touched = time.monotonic()
273              mode = mode
274          if dismiss_buttons[button_mode].contains(touch) and not touch_button_dismiss:
275              print("Touched dismiss")
276              dismissed = True
277              alarm = False
278              display.show(pyportal.splash)
279              touch_button_dismiss = True
280              touched = time.monotonic()
281              mode = mode
282  
283      #  this is a little delay so that the dismissed state
284      #  doesn't collide with the alarm if it's dismissed
285      #  during the same time that the alarm activates
286      if (not touched) or (time.monotonic() - touched) > 70:
287          dismissed = False
288      #  snooze portion
289      #  pulls snooze_time from calendar and then when it's up
290      #  splashes the snoozed alarm's graphic, plays the alarm sound and goes back into
291      #  alarm state
292      if (snoozed) and (time.monotonic() - touched) > timers['snooze_time']:
293          print("snooze over")
294          snoozed = False
295          alarm = True
296          mode = mode
297          display.show(alarm_gfx[mode])
298          pyportal.play_file(alarm_sounds[mode])
299          print(mode)