code.py
  1  # SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  # pylint: disable=redefined-outer-name, eval-used, wrong-import-order
  5  
  6  import time
  7  import terminalio
  8  import displayio
  9  import adafruit_imageload
 10  from adafruit_display_text import label
 11  from adafruit_magtag.magtag import MagTag
 12  from secrets import secrets
 13  
 14  # --| USER CONFIG |--------------------------
 15  METRIC = False  # set to True for metric units
 16  # -------------------------------------------
 17  
 18  # ----------------------------
 19  # Define various assets
 20  # ----------------------------
 21  BACKGROUND_BMP = "/bmps/weather_bg.bmp"
 22  ICONS_LARGE_FILE = "/bmps/weather_icons_70px.bmp"
 23  ICONS_SMALL_FILE = "/bmps/weather_icons_20px.bmp"
 24  ICON_MAP = ("01", "02", "03", "04", "09", "10", "11", "13", "50")
 25  DAYS = ("Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday")
 26  MONTHS = (
 27      "January",
 28      "February",
 29      "March",
 30      "April",
 31      "May",
 32      "June",
 33      "July",
 34      "August",
 35      "September",
 36      "October",
 37      "November",
 38      "December",
 39  )
 40  magtag = MagTag()
 41  
 42  # ----------------------------
 43  # Backgrounnd bitmap
 44  # ----------------------------
 45  magtag.graphics.set_background(BACKGROUND_BMP)
 46  
 47  # ----------------------------
 48  # Weather icons sprite sheet
 49  # ----------------------------
 50  icons_large_bmp, icons_large_pal = adafruit_imageload.load(ICONS_LARGE_FILE)
 51  icons_small_bmp, icons_small_pal = adafruit_imageload.load(ICONS_SMALL_FILE)
 52  
 53  # /////////////////////////////////////////////////////////////////////////
 54  
 55  
 56  def get_data_source_url(api="onecall", location=None):
 57      """Build and return the URL for the OpenWeather API."""
 58      if api.upper() == "GEO":
 59          URL = "https://api.openweathermap.org/geo/1.0/direct?q="
 60          URL += location
 61      elif api.upper() == "GEOREV":
 62          URL = "https://api.openweathermap.org/geo/1.0/reverse?limit=1"
 63          URL += "&lat={}".format(location[0])
 64          URL += "&lon={}".format(location[1])
 65      elif api.upper() == "ONECALL":
 66          URL = "https://api.openweathermap.org/data/2.5/onecall?exclude=minutely,hourly,alerts"
 67          URL += "&lat={}".format(location[0])
 68          URL += "&lon={}".format(location[1])
 69      else:
 70          raise ValueError("Unknown API type: " + api)
 71      return URL + "&appid=" + secrets["openweather_token"]
 72  
 73  
 74  def get_latlon(city_name):
 75      """Use the Geolocation API to determine lat/lon for given city."""
 76      magtag.url = get_data_source_url(api="geo", location=city_name)
 77      raw_data = eval(magtag.fetch())[0]
 78      return raw_data["lat"], raw_data["lon"]
 79  
 80  
 81  def get_city(latlon_location):
 82      """Use the Geolocation API to determine city for given lat/lon."""
 83      magtag.url = get_data_source_url(api="georev", location=latlon_location)
 84      raw_data = eval(magtag.fetch())[0]
 85      return raw_data["name"] + ", " + raw_data["country"]
 86  
 87  
 88  def get_forecast(location):
 89      """Use OneCall API to fetch forecast and timezone data."""
 90      resp = magtag.network.fetch(get_data_source_url(api="onecall", location=location))
 91      json_data = resp.json()
 92      return json_data["daily"], json_data["current"]["dt"], json_data["timezone_offset"]
 93  
 94  
 95  def make_banner(x=0, y=0):
 96      """Make a single future forecast info banner group."""
 97      day_of_week = label.Label(terminalio.FONT, text="DAY", color=0x000000)
 98      day_of_week.anchor_point = (0, 0.5)
 99      day_of_week.anchored_position = (0, 10)
100  
101      icon = displayio.TileGrid(
102          icons_small_bmp,
103          pixel_shader=icons_small_pal,
104          x=25,
105          y=0,
106          width=1,
107          height=1,
108          tile_width=20,
109          tile_height=20,
110      )
111  
112      day_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
113      day_temp.anchor_point = (0, 0.5)
114      day_temp.anchored_position = (50, 10)
115  
116      group = displayio.Group(x=x, y=y)
117      group.append(day_of_week)
118      group.append(icon)
119      group.append(day_temp)
120  
121      return group
122  
123  
124  def temperature_text(tempK):
125      if METRIC:
126          return "{:3.0f}C".format(tempK - 273.15)
127      else:
128          return "{:3.0f}F".format(32.0 + 1.8 * (tempK - 273.15))
129  
130  
131  def wind_text(speedms):
132      if METRIC:
133          return "{:3.0f}m/s".format(speedms)
134      else:
135          return "{:3.0f}mph".format(2.23694 * speedms)
136  
137  
138  def update_banner(banner, data):
139      """Update supplied forecast banner with supplied data."""
140      banner[0].text = DAYS[time.localtime(data["dt"]).tm_wday][:3].upper()
141      banner[1][0] = ICON_MAP.index(data["weather"][0]["icon"][:2])
142      banner[2].text = temperature_text(data["temp"]["day"])
143  
144  
145  def update_today(data, tz_offset=0):
146      """Update today info banner."""
147      date = time.localtime(data["dt"])
148      sunrise = time.localtime(data["sunrise"] + tz_offset)
149      sunset = time.localtime(data["sunset"] + tz_offset)
150  
151      today_date.text = "{} {} {}, {}".format(
152          DAYS[date.tm_wday].upper(),
153          MONTHS[date.tm_mon - 1].upper(),
154          date.tm_mday,
155          date.tm_year,
156      )
157      today_icon[0] = ICON_MAP.index(data["weather"][0]["icon"][:2])
158      today_morn_temp.text = temperature_text(data["temp"]["morn"])
159      today_day_temp.text = temperature_text(data["temp"]["day"])
160      today_night_temp.text = temperature_text(data["temp"]["night"])
161      today_humidity.text = "{:3d}%".format(data["humidity"])
162      today_wind.text = wind_text(data["wind_speed"])
163      today_sunrise.text = "{:2d}:{:02d} AM".format(sunrise.tm_hour, sunrise.tm_min)
164      today_sunset.text = "{:2d}:{:02d} PM".format(sunset.tm_hour - 12, sunset.tm_min)
165  
166  
167  def go_to_sleep(current_time):
168      """Enter deep sleep for time needed."""
169      # compute current time offset in seconds
170      hour, minutes, seconds = time.localtime(current_time)[3:6]
171      seconds_since_midnight = 60 * (hour * 60 + minutes) + seconds
172      three_fifteen = (3 * 60 + 15) * 60
173      # wake up 15 minutes after 3am
174      seconds_to_sleep = (24 * 60 * 60 - seconds_since_midnight) + three_fifteen
175      print(
176          "Sleeping for {} hours, {} minutes".format(
177              seconds_to_sleep // 3600, (seconds_to_sleep // 60) % 60
178          )
179      )
180      magtag.exit_and_deep_sleep(seconds_to_sleep)
181  
182  
183  # ===========
184  # Location
185  # ===========
186  if isinstance(secrets["openweather_location"], str):
187      # Get lat/lon using city name
188      city = secrets["openweather_location"]
189      print("Getting lat/lon for city:", city)
190      latlon = get_latlon(city)
191  elif isinstance(secrets["openweather_location"], tuple):
192      # Get city name using lat/lon
193      latlon = secrets["openweather_location"]
194      print("Getting city name for lat/lon:", latlon)
195      city = get_city(latlon)
196  else:
197      raise ValueError("Unknown location:", secrets["openweather_location"])
198  
199  print("City =", city)
200  print("Lat/Lon = ", latlon)
201  
202  # ===========
203  # U I
204  # ===========
205  today_date = label.Label(terminalio.FONT, text="?" * 30, color=0x000000)
206  today_date.anchor_point = (0, 0)
207  today_date.anchored_position = (15, 13)
208  
209  city_name = label.Label(terminalio.FONT, text=city, color=0x000000)
210  city_name.anchor_point = (0, 0)
211  city_name.anchored_position = (15, 24)
212  
213  today_icon = displayio.TileGrid(
214      icons_large_bmp,
215      pixel_shader=icons_small_pal,
216      x=10,
217      y=40,
218      width=1,
219      height=1,
220      tile_width=70,
221      tile_height=70,
222  )
223  
224  today_morn_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
225  today_morn_temp.anchor_point = (0.5, 0)
226  today_morn_temp.anchored_position = (118, 59)
227  
228  today_day_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
229  today_day_temp.anchor_point = (0.5, 0)
230  today_day_temp.anchored_position = (149, 59)
231  
232  today_night_temp = label.Label(terminalio.FONT, text="+100F", color=0x000000)
233  today_night_temp.anchor_point = (0.5, 0)
234  today_night_temp.anchored_position = (180, 59)
235  
236  today_humidity = label.Label(terminalio.FONT, text="100%", color=0x000000)
237  today_humidity.anchor_point = (0, 0.5)
238  today_humidity.anchored_position = (105, 95)
239  
240  today_wind = label.Label(terminalio.FONT, text="99m/s", color=0x000000)
241  today_wind.anchor_point = (0, 0.5)
242  today_wind.anchored_position = (155, 95)
243  
244  today_sunrise = label.Label(terminalio.FONT, text="12:12 PM", color=0x000000)
245  today_sunrise.anchor_point = (0, 0.5)
246  today_sunrise.anchored_position = (45, 117)
247  
248  today_sunset = label.Label(terminalio.FONT, text="12:12 PM", color=0x000000)
249  today_sunset.anchor_point = (0, 0.5)
250  today_sunset.anchored_position = (130, 117)
251  
252  today_banner = displayio.Group()
253  today_banner.append(today_date)
254  today_banner.append(city_name)
255  today_banner.append(today_icon)
256  today_banner.append(today_morn_temp)
257  today_banner.append(today_day_temp)
258  today_banner.append(today_night_temp)
259  today_banner.append(today_humidity)
260  today_banner.append(today_wind)
261  today_banner.append(today_sunrise)
262  today_banner.append(today_sunset)
263  
264  future_banners = [
265      make_banner(x=210, y=18),
266      make_banner(x=210, y=39),
267      make_banner(x=210, y=60),
268      make_banner(x=210, y=81),
269      make_banner(x=210, y=102),
270  ]
271  
272  magtag.splash.append(today_banner)
273  for future_banner in future_banners:
274      magtag.splash.append(future_banner)
275  
276  # ===========
277  #  M A I N
278  # ===========
279  print("Fetching forecast...")
280  forecast_data, utc_time, local_tz_offset = get_forecast(latlon)
281  
282  print("Updating...")
283  update_today(forecast_data[0], local_tz_offset)
284  for day, forecast in enumerate(forecast_data[1:6]):
285      update_banner(future_banners[day], forecast)
286  
287  print("Refreshing...")
288  time.sleep(magtag.display.time_to_refresh + 1)
289  magtag.display.refresh()
290  time.sleep(magtag.display.time_to_refresh + 1)
291  
292  print("Sleeping...")
293  go_to_sleep(utc_time + local_tz_offset)
294  #  entire code will run again after deep sleep cycle
295  #  similar to hitting the reset button