/ MagTag_Tides / code.py
code.py
  1  # SPDX-FileCopyrightText: 2020 Carter Nelson for Adafruit Industries
  2  #
  3  # SPDX-License-Identifier: MIT
  4  
  5  import time
  6  import terminalio
  7  import displayio
  8  import adafruit_imageload
  9  from adafruit_display_text import label
 10  from adafruit_bitmap_font import bitmap_font
 11  from adafruit_magtag.magtag import MagTag
 12  
 13  # --| USER CONFIG |--------------------------
 14  STATION_ID = (
 15      "9447130"  # tide location, find yours here: https://tidesandcurrents.noaa.gov/
 16  )
 17  METRIC = False  # set to True for metric units
 18  VSCALE = 2  # pixels per ft or m
 19  DAILY_UPDATE_HOUR = 3  # 24 hour format
 20  DST_ON = True  # Day Light Saving currently active?
 21  # -------------------------------------------
 22  
 23  # don't change these
 24  PLOT_WIDTH = 116
 25  PLOT_HEIGHT = 116
 26  PLOT_X = 174
 27  PLOT_Y = 6
 28  PLOT_Y_SCALE = round(PLOT_HEIGHT / (4 * VSCALE))
 29  DATE_FONT = bitmap_font.load_font("/fonts/Kanit-Black-24.bdf")
 30  TIME_FONT = bitmap_font.load_font("/fonts/Kanit-Medium-20.bdf")
 31  
 32  # our MagTag
 33  magtag = MagTag()
 34  magtag.json_path = ["predictions"]
 35  
 36  # ----------------------------
 37  # Grid overlay for plot
 38  # ----------------------------
 39  grid_bmp, grid_pal = adafruit_imageload.load("/bmps/tides_bg_land.bmp")
 40  grid_pal.make_transparent(1)
 41  grid_overlay = displayio.TileGrid(grid_bmp, pixel_shader=grid_pal)
 42  
 43  # ----------------------------
 44  # Tide plot (bitmap, palette, tilegrid)
 45  # ----------------------------
 46  tide_plot = displayio.Bitmap(PLOT_WIDTH, PLOT_HEIGHT, 4)
 47  
 48  tide_pal = displayio.Palette(4)
 49  tide_pal[0] = 0x000000  # black
 50  tide_pal[1] = 0x555555  # dark gray
 51  tide_pal[2] = 0xAAAAAA  # light gray
 52  tide_pal[3] = 0xFFFFFF  # white
 53  tide_pal.make_transparent(3)
 54  
 55  tide_tg = displayio.TileGrid(tide_plot, pixel_shader=tide_pal, x=PLOT_X, y=PLOT_Y)
 56  
 57  # ----------------------------
 58  # Plot scale labels
 59  # ----------------------------
 60  plot_y_pos = label.Label(terminalio.FONT, text="+99", color=0x000000)
 61  plot_y_pos.text = "{:>3}".format(PLOT_Y_SCALE)
 62  plot_y_pos.anchor_point = (1.0, 0.5)
 63  plot_y_pos.anchored_position = (178, 34)
 64  
 65  plot_y_neg = label.Label(terminalio.FONT, text="-99", color=0x000000)
 66  plot_y_neg.text = "{:>3}".format(-1 * PLOT_Y_SCALE)
 67  plot_y_neg.anchor_point = (1.0, 0.5)
 68  plot_y_neg.anchored_position = (178, 92)
 69  
 70  plot_y_labels = displayio.Group()
 71  plot_y_labels.append(plot_y_pos)
 72  plot_y_labels.append(plot_y_neg)
 73  
 74  # ----------------------------
 75  # Date label
 76  # ----------------------------
 77  date_label = displayio.Group()
 78  date_text = [label.Label(DATE_FONT, text="A", color=0xFFFFFF) for _ in range(5)]
 79  y_offset = 8
 80  for text in date_text:
 81      date_label.append(text)
 82      text.anchor_point = (0.5, 0)
 83      text.anchored_position = (20, y_offset)
 84      y_offset += 23
 85  
 86  # ----------------------------
 87  # HiLo Times and Icons
 88  # ----------------------------
 89  tide_info = displayio.Group()
 90  
 91  hilo_times = [label.Label(TIME_FONT, text="12:34 P", color=0x000000) for _ in range(4)]
 92  y_offset = 18
 93  for hilo in hilo_times:
 94      tide_info.append(hilo)
 95      hilo.hidden = True
 96      hilo.anchor_point = (1, 0.5)
 97      hilo.anchored_position = (158, y_offset)
 98      y_offset += 28
 99  
100  icon_bmp, icon_pal = adafruit_imageload.load("/bmps/tides_icons.bmp")
101  icon_pal.make_transparent(1)
102  hilo_icons = [
103      displayio.TileGrid(
104          icon_bmp,
105          pixel_shader=icon_pal,
106          width=1,
107          height=1,
108          tile_width=24,
109          tile_height=24,
110      )
111      for _ in range(4)
112  ]
113  y_offset = 6
114  for icon in hilo_icons:
115      tide_info.append(icon)
116      icon.hidden = True
117      icon.x = 46
118      icon.y = y_offset
119      y_offset += 28
120  
121  # ----------------------------
122  # Station ID
123  # ----------------------------
124  station_info = label.Label(
125      terminalio.FONT, text="STATION ID: " + STATION_ID, color=0x000000
126  )
127  station_info.anchor_point = (1, 1)
128  station_info.anchored_position = (158, 126)
129  
130  # ----------------------------
131  # Add all the graphic layers
132  # ----------------------------
133  magtag.splash.append(tide_tg)
134  magtag.splash.append(grid_overlay)
135  magtag.splash.append(plot_y_labels)
136  magtag.splash.append(tide_info)
137  magtag.splash.append(date_label)
138  magtag.splash.append(station_info)
139  
140  # /////////////////////////////////////////////////////////////////////////
141  
142  
143  def get_data_source_url(station=STATION_ID, metric=METRIC, hilo_only=True):
144      """Build and return the URL for the tides API."""
145      date = "{}{:02}{:02}".format(now.tm_year, now.tm_mon, now.tm_mday)
146  
147      URL = "https://api.tidesandcurrents.noaa.gov/api/prod/datagetter?format=json"
148      URL += "&product=predictions"
149      URL += "&interval=hilo" if hilo_only else ""
150      URL += "&datum=mllw"  # MLLW = "tides"
151      URL += "&units=metric" if metric else "&units=english"
152      URL += "&time_zone=lst_ldt" if DST_ON else "&time_zone=lst"
153      URL += "&begin_date=" + date
154      URL += "&end_date=" + date
155      URL += "&station=" + station
156  
157      return URL
158  
159  
160  def get_tide_data():
161      """Fetch JSON tide data and return parsed results in a list."""
162      # Get raw JSON data
163      magtag.url = get_data_source_url(hilo_only=False)
164      raw_data = magtag.fetch()
165  
166      # Results will be stored in a list that is PLOT_WIDTH long
167      new_tide_data = [PLOT_HEIGHT - 1] * PLOT_WIDTH
168  
169      # Convert raw data to display coordinates
170      for data in raw_data:
171          _, t = data["t"].split(" ")  # date and time
172          h, m = t.split(":")  # hours and minutes
173          v = data["v"]  # water level
174          x = round((PLOT_WIDTH - 1) * (60 * float(h) + float(m)) / 1434)
175          y = (PLOT_HEIGHT // 2) - round(VSCALE * float(v))
176          y = 0 if y < 0 else y
177          y = PLOT_HEIGHT - 1 if y >= PLOT_HEIGHT else y
178          new_tide_data[x] = y
179  
180      return new_tide_data
181  
182  
183  def get_hilo_data():
184      """Get high / low times."""
185      # Get raw JSON data
186      magtag.url = get_data_source_url(hilo_only=True)
187  
188      return magtag.fetch()
189  
190  
191  def show_today():
192      """Display month and day."""
193      month_text = (
194          "JAN",
195          "FEB",
196          "MAR",
197          "APR",
198          "MAY",
199          "JUN",
200          "JUL",
201          "AUG",
202          "SEP",
203          "OCT",
204          "NOV",
205          "DEC",
206      )[now.tm_mon - 1]
207      day_text = "{:2}".format(now.tm_mday)
208  
209      date_label[0].text = month_text[0]
210      date_label[1].text = month_text[1]
211      date_label[2].text = month_text[2]
212      date_label[3].text = day_text[0]
213      date_label[4].text = day_text[1]
214  
215  
216  def plot_tides():
217      """Graphical plot of water level."""
218      tide_plot.fill(3)
219      for x in range(PLOT_WIDTH):
220          y = tide_data[x]
221          for yfill in range(y, PLOT_HEIGHT):
222              try:
223                  tide_plot[x, yfill] = 2
224              except IndexError:
225                  pass
226          tide_plot[x, y] = 0
227  
228  
229  def show_hilo():
230      """Show high / low times."""
231      for i in hilo_icons:
232          i.hidden = True
233      for t in hilo_times:
234          t.hidden = True
235      for i, data in enumerate(hilo_data):
236          # make it visible
237          hilo_icons[i].hidden = False
238          hilo_times[i].hidden = False
239          # icon
240          hilo_icons[i][0] = 0 if data["type"] == "H" else 1
241          # time
242          h, m = data["t"].split(" ")[1].split(":")
243          m = int(m)
244          h = int(h)
245          ampm = "A" if h < 12 else "P"
246          h = h if h < 13 else h - 12
247          hilo_times[i].text = "{:>2}:{:02} {}".format(h, m, ampm)
248  
249  
250  def time_to_sleep():
251      """Compute amount of time to sleep."""
252      # daily event time
253      event_time = time.struct_time(
254          (now[0], now[1], now[2], DAILY_UPDATE_HOUR, 0, 0, -1, -1, now[8])
255      )
256      # how long is that from now?
257      remaining = time.mktime(event_time) - time.mktime(now)
258      # is that today or tomorrow?
259      if remaining < 0:  # ah its aready happened today...
260          remaining += 24 * 60 * 60  # wrap around to the next day
261      # return it
262      return remaining
263  
264  
265  # ===========
266  #  M A I N
267  # ===========
268  # get current time
269  magtag.get_local_time()
270  now = time.localtime()
271  
272  # show today's date
273  show_today()
274  
275  # get and plot tide levels
276  tide_data = get_tide_data()
277  plot_tides()
278  
279  # get and show hilo tide times
280  hilo_data = get_hilo_data()
281  show_hilo()
282  
283  # refresh display
284  time.sleep(magtag.display.time_to_refresh + 1)
285  magtag.display.refresh()
286  time.sleep(magtag.display.time_to_refresh + 1)
287  
288  # ZZZZZZzzzzzzzzz
289  now = time.localtime()
290  magtag.exit_and_deep_sleep(time_to_sleep())
291  #
292  # code.py runs again when board wakes up
293  #