This project is built around a state machine. These were described in detail in a previous guide. We'll use a class based state machine implementation in this project.
This machine has 4 states:
- The Time state is where the program spends most of its time. This state handles the primary screen: displaying the current time and weather, checking whether the alarm should sound, and allowing access to the alarm settings and mugsy (or other) function.
- The Mugsy state is for some additional function. In the author's case it will be telling his Mugsy robotic pour-over machine to start brewing the first mug of the morning. Details of this state are TBD until Mugsy arrives and is up & running.
- The Alarm state handles sounding the alarm, cancelling it by touching the screen, and triggering snooze using the big button.
- The Settings state handles the mode that allows the user to enable or disable the alarm (by touching the red or green areas) and set the alarm time (by swiping up/down over the hourrs and minutes). Touching the yellow arrow "button" returns to the time screen.
The common State
base class defines the supported methods with their default implementations.
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 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()
__init__() - the constructor. This should create any views, or other objects that live for the life of the system, persisting between times the state is active.
name() - returns the name of the state. This has only used in generating debugging output.
tick(now) - Any time based actions should happen in this method. now is the current value of time.monotonic.
touch(t, touched) - Handle a potential touch. t is the active touch or None. touched is whether there was an active touch last time. The result of this method becomes the touched argument for the next call. This is typically done by returning bool(t)
, but may not always.
enter() - The state is being entered. If the state needs to display anything, the views need to be added to pyportal.splash
here.
exit() - The state is being existed. The default here is to remove everything except the background from pyportal.splash
.
Most of these methods are defaulted to doing nothing. exit
is the exception in that it cleans up pyportal.splash
. Because of this, if a state's exit
method in implemented (because it has something to do), it needs to call super().exit()
.
The Machine
Management of the states is very lightweight. The states are, themselves, responsible for transitioning. All that is required outside the states is to set up the machine:
states = {'time': Time_State(), 'mugsy': Mugsy_State(), 'alarm': Alarm_State(), 'settings': Setting_State()} current_state = None
and provide a way to change states (which takes care of calling exit and enter methods):
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()
With the machine in place, it's just a matter to starting it in the time state and having the core loop simply call touch
and tick
on the current state:
clear_splash() change_to_state("time") while True: touched = current_state.touch(pyportal.touchscreen.touch_point, touched) current_state.tick(time.monotonic())
Text editor powered by tinymce.