Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # SPDX-FileCopyrightText: 2019 Dave Astels for Adafruit Industries
- #
- # SPDX-License-Identifier: MIT
- """
- PyPortal based alarm clock.
- Adafruit invests time and resources providing this open source code.
- Please support Adafruit and open source hardware by purchasing
- products from Adafruit!
- Written by Dave Astels for Adafruit Industries
- Copyright (c) 2019 Adafruit Industries
- Licensed under the MIT license.
- All text above must be included in any redistribution.
- """
- # pylint:disable=redefined-outer-name,no-member,global-statement
- # pylint:disable=no-self-use,too-many-branches,too-many-statements
- # pylint:disable=useless-super-delegation, too-many-locals
- import time
- import board
- from adafruit_pyportal import PyPortal
- from adafruit_bitmap_font import bitmap_font
- from adafruit_display_text.label import Label
- import analogio
- import displayio
- import adafruit_logging as logging
- import busio
- import rtc
- import adafruit_gps
- import neopixel
- from microcontroller import nvm
- from adafruit_bme280 import basic as bmE280
- import adafruit_ahtx0 as ahT20
- # init logfile
- LOG_FILE = "/sd/gps.txt"
- LOG_MODE = "a"
- # read time zone
- tz_cur = nvm[0]
- ####################
- # setup hardware
- i2c = busio.I2C(board.SCL, board.SDA, frequency=200_000)
- gps = adafruit_gps.GPS_GtopI2C(i2c, debug=False) # Use I2C interface
- gps.send_command(b"PMTK314,0,1,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0")
- gps.send_command(b"PMTK220,1000")
- pixels = neopixel.NeoPixel(board.NEOPIXEL, 1)
- bmE280.sea_level_pressure = 1013.25
- outside = ahT20.AHTx0(i2c)
- inside = bmE280.Adafruit_BME280_I2C(i2c)
- while gps.timestamp_utc is None:
- print("No time data from GPS yet")
- pixels[0] = (0, 0, 0)
- time.sleep(.7)
- pixels[0] = (128, 0, 0)
- time.sleep(.7)
- gps.update()
- pixels[0] = (0, 0, 0)
- the_rtc = rtc.RTC()
- the_rtc.datetime = time.struct_time(gps.timestamp_utc)
- pyportal = PyPortal()
- light = analogio.AnalogIn(board.LIGHT)
- ####################
- # variables
- speed = 0
- # alarm support
- alarm_background = 'red_alertl.bmp'
- alarm_file = 'alarm.wav'
- alarm_enabled = False
- alarm_armed = False
- alarm_interval = 10.0
- alarm_hour = 9
- alarm_minute = 45
- snooze_time = None
- snooze_interval = 600.0
- # pressure support
- mugsy_background = 'mugsy_backgroundl.bmp'
- # weather support
- icon_file = None
- icon_sprite = None
- celcius = None
- # display/data refresh timers
- refresh_time = None
- update_time = None
- weather_refresh = None
- # The most recently fetched time
- current_time = None
- # track whether we're in low light mode
- low_light = False
- ####################
- # Load the fonts
- time_font = bitmap_font.load_font('/fonts/Anton-Regular-104.bdf')
- time_font.load_glyphs(b'0123456789:') # pre-load glyphs for fast printing
- alarm_font = bitmap_font.load_font('/fonts/Helvetica-Bold-36.bdf')
- alarm_font.load_glyphs(b'0123456789:')
- temperature_font = bitmap_font.load_font('/fonts/Arial-16.bdf')
- temperature_font.load_glyphs(b'0123456789CF')
- ####################
- # Set up logging
- logger = logging.getLogger('alarm_clock')
- logger.addHandler(logging.FileHandler("/sd/test.txt", "a"))
- logger.setLevel(logging.ERROR) # change as desired
- ####################
- # Functions
- def debug(*args, **kwargs):
- # lets both print and write to the file
- print(*args, **kwargs)
- print(*args, **kwargs, file=outfile)
- def debugp(*args, **kwargs):
- # lets both print and write to the file
- print(*args, **kwargs)
- print(*args, **kwargs, file=pressfile)
- def create_text_areas(configs):
- """Given a list of area specifications, create and return test areas."""
- text_areas = []
- for cfg in configs:
- textarea = Label(cfg['font'], text=' '*cfg['size'])
- textarea.x = cfg['x']
- textarea.y = cfg['y']
- textarea.color = cfg['color']
- text_areas.append(textarea)
- return text_areas
- def clear_splash():
- for _ in range(len(pyportal.splash) - 1):
- pyportal.splash.pop()
- def touch_in_button(t, b):
- in_horizontal = b['left'] <= t[0] <= b['right']
- in_vertical = b['top'] <= t[1] <= b['bottom']
- return in_horizontal and in_vertical
- touched = False
- ####################
- # states
- class State(object):
- """State abstract base class"""
- def __init__(self):
- pass
- @property
- def name(self):
- """Return the name of teh state"""
- return ''
- def tick(self, now):
- """Handle a tick: one pass through the main loop"""
- pass
- # pylint:disable=unused-argument
- def touch(self, t, touched):
- """Handle a touch event.
- :param (x, y, z) - t: the touch location/strength"""
- return bool(t)
- def enter(self):
- """Just after the state is entered."""
- pass
- def exit(self):
- """Just before the state exits."""
- clear_splash()
- class Time_State(State):
- """This state manages the primary time display screen/mode"""
- def __init__(self):
- super().__init__()
- self.background_day = 'main_background_dayl.bmp'
- self.background_night = 'main_background_nightl.bmp'
- self.refresh_time = None
- self.update_time = None
- self.weather_refresh = None
- text_area_configs = [dict(x=160, y=220, size=4, color=0xFFFFFF, font=time_font),
- dict(x=290, y=100, size=4, color=0xFF0000, font=alarm_font),
- dict(x=125, y=90, size=6, color=0xFFFFFF, font=temperature_font),
- dict(x=125, y=125, size=6, color=0xFFFFFF, font=temperature_font),
- dict(x=200, y=90, size=6, color=0xFFFFFF, font=temperature_font),
- dict(x=200, y=125, size=6, color=0xFFFFFF, font=temperature_font),
- dict(x=145, y=150, size=6, color=0x00FFFF, font=temperature_font)]
- self.text_areas = create_text_areas(text_area_configs)
- self.weather_icon = displayio.Group()
- self.weather_icon.x = 125
- self.weather_icon.y = 25
- self.icon_file = None
- # each button has it's edges as well as the state to transition to when touched
- self.buttons = [dict(left=0, top=72, right=118, bottom=158, next_state='settings'),
- dict(left=0, top=208, right=118, bottom=294, next_state='pressurel')]
- @property
- def name(self):
- return 'time'
- def adjust_backlight_based_on_light(self, force=False):
- """Check light level. Adjust the backlight and background image if it's dark."""
- global low_light
- if light.value <= 800 and (force or not low_light):
- pyportal.set_backlight(0.01)
- pyportal.set_background(self.background_night)
- low_light = True
- elif force or (light.value >= 2000 and low_light):
- pyportal.set_backlight(1.00)
- pyportal.set_background(self.background_day)
- low_light = False
- def tick(self, now):
- global alarm_armed, snooze_time, update_time, current_time, time_string, tz_cur, outfile, t_stamp
- # check light level and adjust background & backlight
- self.adjust_backlight_based_on_light()
- # only query the online time once per hour (and on first run)
- if (not self.refresh_time) or ((now - self.refresh_time) > 3600):
- logger.debug('Fetching time')
- try:
- gps.update()
- the_rtc.datetime = time.struct_time(gps.timestamp_utc)
- self.refresh_time = now
- except RuntimeError as e:
- self.refresh_time = now - 3000 # delay 10 minutes before retrying
- logger.error('Some error occured, retrying! - %s', str(e))
- # only query the weather every 10 minutes (and on first run)
- if (not self.weather_refresh) or (now - self.weather_refresh) > 4:
- logger.debug('Fetching weather')
- try:
- gps.update()
- if gps.speed_knots is not None:
- speed = '%5d mph' % round(gps.speed_knots * 1.151)
- else:
- speed = "0"
- self.text_areas[1].text = speed
- intemp = inside.temperature
- it_text = '%3d F' % round(((intemp * 9 / 5) + 32))
- self.text_areas[2].text = it_text
- inrh = inside.relative_humidity
- humi_text = '%3d ' % round(inrh)
- self.text_areas[3].text = humi_text
- outtemp = outside.temperature
- ot_text = '%3d F' % round(((outtemp * 9 / 5) + 32))
- self.text_areas[4].text = ot_text
- outrh = outside.relative_humidity
- humo_text = '%3d ' % round(outrh)
- self.text_areas[5].text = humo_text
- if gps.altitude_m is not None:
- altm = '%5d M' % round(gps.altitude_m)
- else:
- altm = "0"
- self.text_areas[6].text = altm
- self.weather_refresh = now
- t_stamp = time.mktime(gps.timestamp_utc)
- with open(LOG_FILE, LOG_MODE) as outfile:
- debug(t_stamp, gps.latitude, gps.longitude, gps.altitude_m, speed,
- " : ", intemp, inrh, outtemp, outrh, inside.pressure)
- try:
- board.DISPLAY.refresh(target_frames_per_second=60)
- except AttributeError:
- board.DISPLAY.refresh_soon()
- board.DISPLAY.wait_for_frame()
- except RuntimeError as e:
- self.weather_refresh = now - 540 # delay a minute before retrying
- logger.error("Some error occured, retrying! - %s", str(e))
- if (not update_time) or ((now - update_time) > 30):
- # Update the time
- update_time = now
- current_time = time.localtime()
- time_string = '%02d:%02d' % (((current_time.tm_hour-tz_cur) % 24), current_time.tm_min)
- self.text_areas[0].text = time_string
- try:
- board.DISPLAY.refresh(target_frames_per_second=60)
- except AttributeError:
- board.DISPLAY.refresh_soon()
- board.DISPLAY.wait_for_frame()
- def touch(self, t, touched):
- if t and not touched: # only process the initial touch
- for button_index in range(len(self.buttons)):
- b = self.buttons[button_index]
- if touch_in_button(t, b):
- change_to_state(b['next_state'])
- break
- return bool(t)
- def enter(self):
- self.adjust_backlight_based_on_light(force=True)
- for ta in self.text_areas:
- pyportal.splash.append(ta)
- pyportal.splash.append(self.weather_icon)
- if snooze_time:
- # CircuitPython 7+ compatible
- icon = displayio.OnDiskBitmap("/icons/zzz.bmp")
- icon_sprite = displayio.TileGrid(icon, pixel_shader=icon.pixel_shader)
- self.snooze_icon.append(icon_sprite)
- pyportal.splash.append(self.snooze_icon)
- try:
- board.DISPLAY.refresh(target_frames_per_second=60)
- except AttributeError:
- board.DISPLAY.refresh_soon()
- board.DISPLAY.wait_for_frame()
- def exit(self):
- super().exit()
- for _ in range(len(self.snooze_icon)):
- self.snooze_icon.pop()
- class Pressure_State(Time_State):
- """This state records a pressure log """
- def __init__(self):
- super().__init__()
- @property
- def name(self):
- return 'pressurel'
- def tick(self, now):
- # Once the job is done, go back to the main screen
- change_to_state('time')
- def enter(self):
- global low_light, pressfile, PLOG_FILE, LOG_MODE, current_time
- low_light = False
- stamped = False
- plog_path = "/sd/"
- pyportal.set_backlight(1.00)
- pyportal.set_background(mugsy_background)
- try:
- board.DISPLAY.refresh(target_frames_per_second=60)
- except AttributeError:
- board.DISPLAY.refresh_soon()
- board.DISPLAY.wait_for_frame()
- gps.update()
- t_stamp = time.mktime(gps.timestamp_utc)
- PLOG_FILE = f"{t_stamp}pressure.txt"
- loop = 1200
- timep = time.monotonic_ns()
- while loop > 0:
- with open((plog_path+PLOG_FILE), LOG_MODE) as pressfile:
- if stamped is False:
- debugp(t_stamp, gps.latitude, gps.longitude, gps.altitude_m)
- stamped = True
- debugp(((time.monotonic_ns() - timep) / 1000000000), inside.pressure)
- loop -= 1
- class Setting_State(State):
- """This state lets the user enable/disable the alarm and set its time.
- Swiping up/down adjusts the hours & miniutes separately."""
- def __init__(self):
- super().__init__()
- self.previous_touch = None
- self.background = 'settings_backgroundl.bmp'
- text_area_configs = [dict(x=160, y=220, size=4, color=0xFFFFFF, font=time_font)]
- self.text_areas = create_text_areas(text_area_configs)
- self.buttons = [dict(left=0, top=40, right=115, bottom=123), # on
- dict(left=0, top=130, right=115, bottom=200), # return
- dict(left=0, top=208, right=100, bottom=295), # off
- dict(left=150, top=0, right=300, bottom=320), # hours
- dict(left=310, top=0, right=480, bottom=320)] # minutes
- @property
- def name(self):
- return 'settings'
- def touch(self, t, touched):
- global alarm_hour, alarm_minute, alarm_enabled, time_string, tz_cur
- if t:
- if touch_in_button(t, self.buttons[0]): # increase offset
- logger.debug('increase offset')
- tz_cur += 1
- current_time = time.localtime()
- time_string = '%02d:%02d' % (((current_time.tm_hour-tz_cur) % 24), current_time.tm_min)
- self.text_areas[0].text = time_string
- elif touch_in_button(t, self.buttons[1]): # return
- logger.debug('RETURN touched')
- nvm[0] = tz_cur
- change_to_state('time')
- elif touch_in_button(t, self.buttons[2]): # decrease offset
- logger.debug('decrease offset')
- tz_cur -= 1
- current_time = time.localtime()
- time_string = '%02d:%02d' % (((current_time.tm_hour-tz_cur) % 24), current_time.tm_min)
- self.text_areas[0].text = time_string
- try:
- board.DISPLAY.refresh(target_frames_per_second=60)
- except AttributeError:
- board.DISPLAY.refresh_soon()
- board.DISPLAY.wait_for_frame()
- else:
- self.previous_touch = None
- return bool(t)
- def enter(self):
- global snooze_time
- snooze_time = None
- pyportal.set_background(self.background)
- for ta in self.text_areas:
- pyportal.splash.append(ta)
- if alarm_enabled:
- self.text_areas[0].text = '%02d:%02d' % (alarm_hour, alarm_minute) # set time textarea
- else:
- self.text_areas[0].text = ' '
- ####################
- # State management
- states = {'time': Time_State(),
- 'pressurel': Pressure_State(),
- 'settings': Setting_State()}
- current_state = None
- def change_to_state(state_name):
- global current_state
- if current_state:
- logger.debug('Exiting %s', current_state.name)
- current_state.exit()
- current_state = states[state_name]
- logger.debug('Entering %s', current_state.name)
- current_state.enter()
- ####################
- # And... go
- clear_splash()
- change_to_state("time")
- while True:
- touched = current_state.touch(pyportal.touchscreen.touch_point, touched)
- current_state.tick(time.monotonic())
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement