/ PyPortal_Titano_Weather_Station / code.py
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)