summaryrefslogtreecommitdiff
path: root/wasp/apps/steps.py
blob: 451edd2c54212ef5c87060473a1d7a8e73653904 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# SPDX-License-Identifier: LGPL-3.0-or-later
# Copyright (C) 2020 Daniel Thompson

"""Step counter
~~~~~~~~~~~~~~~

Provide a daily step count.

    .. figure:: res/StepsApp.png
        :width: 179

The step counts automatically reset at midnight.
"""

import wasp

import fonts
import icons
import time
import watch

# 2-bit RLE, generated from res/feet.png, 240 bytes
feet = (
    b'\x02'
    b'00'
    b'\x13\xc1-\xc4+\xc6*\xc6*\xc6&\xc3\x01\xc6\t\xc2'
    b'\x1b\xc3\x02\xc5\x08\xc4\x1a\xc4\x01\xc5\x08\xc5\x19\xc4\x02\xc3'
    b'\x08\xc6\x17\xc1\x02\xc3\x02\xc3\x08\xc6\x16\xc3\x02\xc1\x0e\xc6'
    b'\x01\xc3\x12\xc3\x11\xc6\x01\xc3\x13\xc2\x05\xc2\n\xc5\x02\xc3'
    b'\x10\xc2\x01\xc2\x02\xc6\n\xc4\x01\xc4\x10\xc2\x04\xc7\x0b\xc1'
    b'\x03\xc3\x11\xc3\x02\xc8\x10\xc2\x01\xc3\r\xc2\x02\xc9\x13\xc3'
    b'\x0b\xc1\x05\xc9\x0c\xc2\x05\xc3\x0b\xc2\x03\xc9\x0c\xc5\x03\xc2'
    b'\x0c\xc2\x02\xca\x0c\xc6\x05\xc2\t\xc2\x02\xca\x0c\xc7\x03\xc3'
    b'\r\xca\x0c\xc8\x02\xc3\x0c\xca\r\xc9\x02\xc1\r\xca\r\xc9'
    b'\x04\xc2\n\xca\x0e\xc9\x02\xc3\n\xca\x0e\xc9\x02\xc2\x0b\xca'
    b'\x0e\xca\x0e\xca\x0e\xca\x0f\xc9\x0e\xca\x0f\xca\r\xca\x0f\xca'
    b'\r\xca\x10\xcb\x0b\xca\x10\xcc\n\xca\x10\xcd\t\xca\x11\xcc'
    b'\x08\xca\x12\xcc\x07\xcb\x13\xcb\x06\xcb\x14\xcb\x05\xcc\x15\xca'
    b'\x04\xcc\x16\xc9\x05\xcc\x17\xc7\x05\xcd\x17\xc7\x05\xcc\x1a\xc4'
    b"\x07\xcb%\xca&\xca'\xc8)\xc6+\xc4\x0e"
)

class StepCounterApp():
    """Step counter application."""
    NAME = 'Steps'
    ICON = icons.app

    def __init__(self):
        watch.accel.reset()
        self._scroll = wasp.widgets.ScrollIndicator()
        self._wake = 0

    def foreground(self):
        """Cancel the alarm and draw the application.

        Cancelling the alarm has two effects. Firstly it ensures the
        step count won't change whilst we are watching it and, secondly
        it ensures that if the time of day has been set to a value in
        the past that we reconfigure the alarm.

        This does in the side effect that if the application of open at
        midnight then the reset doesn't happen for that day.
        """
        wasp.system.cancel_alarm(self._wake, self._reset)
        wasp.system.bar.clock = True
        self._page = -1
        self._draw()
        wasp.system.request_event(wasp.EventMask.SWIPE_UPDOWN)
        wasp.system.request_tick(1000)

    def background(self):
        """Set an alarm to trigger at midnight and reset the counter."""
        now = watch.rtc.get_localtime()
        yyyy = now[0]
        mm = now[1]
        dd = now[2]
        then = (yyyy, mm, dd+1, 0, 0, 0, 0, 0, 0)

        self._wake = time.mktime(then)
        wasp.system.set_alarm(self._wake, self._reset)

    def _reset(self):
        """"Reset the step counter and re-arm the alarm."""
        watch.accel.steps = 0
        self._wake += 24 * 60 * 60
        wasp.system.set_alarm(self._wake, self._reset)

    def swipe(self, event):
        if event[0] == wasp.EventType.DOWN:
            if self._page == -1:
                return
            self._page -= 1
        else:
            self._page += 1

        mute = wasp.watch.display.mute
        mute(True)
        self._draw()
        mute(False)

    def tick(self, ticks):
        if self._page == -1:
            self._update()

    def _draw(self):
        """Draw the display from scratch."""
        draw = wasp.watch.drawable
        draw.fill()

        if self._page == -1:
            self._update()
            wasp.system.bar.draw()
        else:
            self._update_graph()

    def _update(self):
        draw = wasp.watch.drawable

        # Draw the icon
        draw.blit(feet, 12, 132-24)

        # Update the status bar
        now = wasp.system.bar.update()

        # Update the scroll indicator
        scroll = self._scroll
        scroll.up = False
        scroll.draw()

        # Update the step count
        count = watch.accel.steps
        t = str(count)
        w = fonts.width(fonts.sans36, t)
        draw.set_font(fonts.sans36)
        draw.set_color(draw.lighten(wasp.system.theme('spot1'), wasp.system.theme('contrast')))
        draw.string(t, 228-w, 132-18)

    def _update_graph(self):
        draw = watch.drawable
        draw.set_font(fonts.sans24)
        draw.set_color(0xffff)

        # Draw the date
        now = int(watch.rtc.time())
        then = now - ((24*60*60) * self._page)
        walltime = time.localtime(then)
        draw.string('{:02d}-{:02d}'.format(walltime[2], walltime[1]), 0, 0)

        # Get the iterable step date for the currently selected date
        data = wasp.system.steps.data(then)

        # Bail if there is no data
        if not data:
            draw.string('No data', 239-160, 0, 160, right=True)
            return

        color = wasp.system.theme('spot2')

        # Draw the frame
        draw.fill(0x3969, 0, 39, 240, 1)
        draw.fill(0x3969, 0, 239, 240, 1)
        for i in (0, 60, 90, 120, 150, 180, 239):
            draw.fill(0x3969, i, 39, 1, 201)

        total = 0
        for x, d in enumerate(data):
            if d == 0 or x < 2:
                # TODO: the x < 2 conceals BUGZ
                continue
            total += d
            d = d // 3
            if d > 200:
                draw.fill(0xffff, x, 239-200, 1, 200)
            else:
                draw.fill(color, x, 239-d, 1, d)

        draw.string(str(total), 239-160, 0, 160, right=True)