The CircuitPython code begins by importing the libraries.
import time import board import displayio from adafruit_clue import clue from simpleio import map_range from adafruit_bitmap_font import bitmap_font from adafruit_lsm6ds import LSM6DS33, Rate, AccelRange from adafruit_progressbar import ProgressBar from adafruit_display_text.label import Label
After the libraries are imported, the on-board NeoPixel is turned off. This is done in an effort to conserve battery life.
clue.pixel.brightness = (0.0)
Next, the CLUE's accelerometer is defined and will be called as sensor
in the code. There are built-in functions in the CLUE's CircuitPython library for common accelerometer uses but this is how you can access it directly.
sensor = LSM6DS33(board.I2C())
Then, the overall step goal is setup. You can edit this number to match your goal.
step_goal = 10000
Some state machines are created for the physical A and B buttons on the front of the CLUE. These will be used to adjust the brightness of the screen.
a_state = False b_state = False
Speaking of screen brightness, this is followed by an array called bright_level
, which holds the three brightness level options for the screen: 0
(off), 0.5
(half brightness) and 1
(full brightness).
bright_level = [0, 0.5, 1]
This is followed by various states and variables that will be used in the main loop. Their purposes are commented next to them.
countdown = 0 # variable for the step goal progress bar clock = 0 # variable used to keep track of time for the steps per hour counter clock_count = 0 # holds the number of hours that the step counter has been running clock_check = 0 # holds the result of the clock divided by 3600 seconds (1 hour) last_step = 0 # state used to properly counter steps mono = time.monotonic() # time.monotonic() device mode = 1 # state used to track screen brightness steps_log = 0 # holds total steps to check for steps per hour steps_remaining = 0 # holds the remaining steps needed to reach the step goal sph = 0 # holds steps per hour
Up next is the graphics setup. First, the on-board file locations for the graphical background and the fonts are defined.
clue_bgBMP = "/clue_bgBMP.bmp" small_font = "/fonts/Roboto-Medium-16.bdf" med_font = "/fonts/Roboto-Bold-24.bdf" big_font = "/fonts/Roboto-Black-48.bdf"
Next, the glyphs are defined that will be used with the bitmap fonts and the setup for the bitmap fonts is completed.
glyphs = b'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-,.: ' small_font = bitmap_font.load_font(small_font) small_font.load_glyphs(glyphs) med_font = bitmap_font.load_font(med_font) med_font.load_glyphs(glyphs) big_font = bitmap_font.load_font(big_font) big_font.load_glyphs(glyphs)
The CLUE's display is setup to be clue_display
and the default brightness is set to half.
clue_display = board.DISPLAY clue_display.brightness = 0.5
Next, the graphics group is created along with the tilegrid
. This will hold the bitmap background.
clueGroup = displayio.Group() clue_bg = displayio.OnDiskBitmap(open("/clue_bgBMP.bmp", "rb")) clue_tilegrid = displayio.TileGrid(clue_bg, pixel_shader=getattr(clue_bg, 'pixel_shader', displayio.ColorConverter())) clueGroup.append(clue_tilegrid)
Following the bitmap background, a ProgressBar
is created. This bar will illustrate your progress to hitting your step goal at the top of the display.
bar_group = displayio.Group() prog_bar = ProgressBar, (11, 239, 25, bar_color=0x652f8f) bar_group.append(prog_bar) clueGroup.append(bar_group)
The final graphical elements are text objects. They'll hold the text for steps remaining, the step count and steps per hour. The text objects are added to the clueGroup
graphic and then the clueGroup
is shown on the CLUE's display on boot with clue_display.show(clueGroup)
.
steps_countdown = Label(small_font, text='%d Steps Remaining' % step_goal, color=clue.WHITE) steps_countdown.x = 55 steps_countdown.y = 12 text_steps = Label(big_font, text="0 ", color=0xe90e8b) text_steps.x = 45 text_steps.y = 70 text_sph = Label(med_font, text=" -- ", color=0x29abe2) text_sph.x = 8 text_sph.y = 195 clueGroup.append(text_sph) clueGroup.append(steps_countdown) clueGroup.append(text_steps) clue_display.show(clueGroup)
The final lines of code before the loop setup the accelerometer on the CLUE board and enable the pedometer.
sensor.accelerometer_range = AccelRange.RANGE_2G sensor.accelerometer_data_rate = Rate.RATE_26_HZ sensor.gyro_data_rate = Rate.RATE_SHUTDOWN sensor.pedometer_enable = True
After all the setup, the loop begins with button debouncing for the A and B buttons located on the front of the CLUE.
while True: if not clue.button_a and not a_state: a_state = True if not clue.button_b and not b_state: b_state = True
This is followed by setting up the variable steps
to hold the step count being collected by the CLUE's pedometer.
steps = sensor.pedometer_steps
Next, countdown
is setup to hold what will be the progress bar's data that will show how close you are to your steps goal. This utilizes the map_range
function, which is handy for mapping different value ranges so that they play well together.
You'll see that there are five values in the parentheses: steps
, 0
, step_goal
, 0.0
and 1.0
. Ignoring steps
for a moment, the value range of 0
to step_goal
is being mapped to 0.0
and 1.0
, which is the range that the ProgressBar library looks for when creating a ProgressBar object. The first item, in this case steps
, is what will be subtracted from the total value range (in this case step_goal
).
In summary, the step_goal
range is being mapped to the ProgressBar's range, which will have your total steps at the time subtracted in order to display the progress towards your goal.
countdown = map_range(steps, 0, step_goal, 0.0, 1.0)
This leads to the actual step counting. It begins with an if
statement that checks if the step count has changed.
if abs(steps-last_step) > 1:
If it has, then time.monotonic()
is logged as step_time
and last_steps
is updated to hold the value of steps
.
step_time = time.monotonic() last_step = steps
Additionally, the display is updated to show the current step count. The text_steps
text object displays the value being held by steps
.
text_steps.text = '%d' % steps
This is followed by some time tracking that will be used to calculate your steps per hour. clock
is setup to hold the difference between the two time.monotonic()
devices step_time
and mono
. mono
is logged before the loop and by doing this you can track how long steps have actually been taking place rather than the CLUE's time that it has been operating.
clock = step_time - mono
To get the steps per hour calculation, there is an if
statement that checks if steps
have been counted for more than an hour, or 3600 seconds. If they have, then the steps per hour will be updated. This is done by having clock_check
hold the value of clock
divided by 3600
. This is done so that you can get a count of how many hours have passed.
Your step count is then held by steps_log
. Another variable, clock_count
, is increased by the rounded result of clock_check
. This means that the total hour count is increased. The steps per hour, being held by sph
, is then updated to divide steps_log
, which is the total steps you've taken, by the clock_count
, which is the total number of hours that you've been counting steps. The result of this calculation is then pushed to the text_sph
object to update the Clue's display.
Finally, clock
is reset to 0
to begin counting down to the next hour and mono
is updated to hold time.monotonic()
.
if clock > 3600: clock_check = clock / 3600 steps_log = steps clock_count += round(clock_check) print('hour count: %d' % clock_count) sph = steps_log / clock_count text_sph.text = '%d' % sph clock = 0 mono = time.monotonic()
The progress bar is updated by setting bar.progress
to hold the value of countdown
as a float. This simultaneously updates the display and the math going on behind the scenes with the ProgressBar library.
bar.progress = float(countdown)
Up next is a new if
statement, this time checking to see if you have reached your steps goal. If you haven't, then the remaining steps are stored in the variable steps_remaining
. This value is then stored in the steps_countdown
text object and is sent to the display to be shown over the progress bar.
However, if the goal has been met, then the steps_countdown
text will read "Steps Goal Met!".
if step_goal - steps > 0: steps_remaining = step_goal - steps steps_countdown.text = '%d Steps Remaining' % steps_remaining else: steps_countdown.text = 'Steps Goal Met!'
The last portion of the loop is for adjusting the CLUE's screen brightness. This will come in handy for preserving battery life. If you press the A button, then the brightness will decrease and if you press the B button, the brightness will increase.
There are three brightness levels, which are held in the bright_level
array. The variable mode
is being used to track the current brightness level and position in the bright_level
array. If A is pressed, then mode
is decreased by 1
and if B is pressed, then mode
is increased by 1
. The result is a change in the screen's brightness level. However, if mode
is less than 0
or greater than 2
, the brightness does not change since you will no longer be in range of the bright_level
array.
if clue.button_a and a_state: mode -= 1 a_state = False if mode < 0: mode = 0 clue_display.brightness = bright_level[mode] else: clue_display.brightness = bright_level[mode] if clue.button_b and b_state: mode += 1 b_state = False if mode > 2: mode = 2 clue_display.brightness = bright_level[mode] else: clue_display.brightness = bright_level[mode]