diff --git a/preload/apps/digiclk/__init__.py b/preload/apps/digiclk/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..bed1b4aa67cd08f13a901e1a0b497585c9a003be --- /dev/null +++ b/preload/apps/digiclk/__init__.py @@ -0,0 +1,314 @@ +import os +import sys +import leds +import display +import buttons + +sys.path.append('/apps/digiclk/') +import monotime as utime +import draw + +DIGITS = [ + (True, True, True, True, True, True, False), + (False, True, True, False, False, False, False), + (True, True, False, True, True, False, True), + (True, True, True, True, False, False, True), + (False, True, True, False, False, True, True), + (True, False, True, True, False, True, True), + (True, False, True, True, True, True, True), + (True, True, True, False, False, False, False), + (True, True, True, True, True, True, True), + (True, True, True, True, False, True, True) +] + +def renderNum(d, num, x): + draw.Grid7Seg(d, x, 0, 7, DIGITS[num // 10], (255, 255, 255)) + draw.Grid7Seg(d, x + 5, 0, 7, DIGITS[num % 10], (255, 255, 255)) + +def renderColon(d): + draw.GridVSeg(d, 11, 2, 7, 2, (255, 255, 255)) + draw.GridVSeg(d, 11, 4, 7, 2, (255, 255, 255)) + +def renderText(d, text, blankidx = None): + bs = bytearray(text) + + if blankidx != None: + bs[blankidx:blankidx+1] = b'_' + + d.print(MODES[MODE] + ' ' + bs.decode(), fg = (255, 255, 255), bg = None, posx = 0, posy = 7 * 8) + +def renderBar(d, num): + d.rect(20, 78, 20 + num * 2, 80, col = (255, 255, 255)) + +def render(d): + ltime = utime.localtime() + years = ltime[0] + months = ltime[1] + days = ltime[2] + hours = ltime[3] + mins = ltime[4] + secs = ltime[5] + + d.clear() + + if MODE == CHANGE_YEAR: + renderNum(d, years // 100, 1) + renderNum(d, years % 100, 13) + elif MODE == CHANGE_MONTH: + renderNum(d, months, 13) + elif MODE == CHANGE_DAY: + renderNum(d, days, 13) + else: + renderNum(d, hours, 1) + renderNum(d, mins, 13) + + if MODE not in (CHANGE_YEAR, CHANGE_MONTH, CHANGE_DAY) and secs % 2 == 0: + renderColon(d) + + renderText(d, NAME, None) + renderBar(d, secs) + + d.update() + +LONG_DELAY = 400 +BUTTON_UPDATE_TIME = 100 +BUTTON_SEL = 1 << 0 +BUTTON_SEL_LONG = 1 << 1 +BUTTON_UP = 1 << 2 +BUTTON_UP_LONG = 1 << 3 +BUTTON_DOWN = 1 << 4 +BUTTON_DOWN_LONG = 1 << 5 +pressed_prev = 0 +button_long_prev = { + BUTTON_SEL: False, + BUTTON_UP: False, + BUTTON_DOWN: False +} +button_times = { + BUTTON_SEL: 0, + BUTTON_UP: 0, + BUTTON_DOWN: 0 +} +def checkButton(button, button_long, osbutton, pressed, t): + cur_buttons = 0 + + if pressed & osbutton and not pressed_prev & osbutton: + button_times[button] = t + button_long_prev[button] = False + elif pressed_prev & osbutton: + if button_times[button] + LONG_DELAY < t: + cur_buttons |= button_long + button_times[button] = t + button_long_prev[button] = True + elif not pressed & osbutton and not button_long_prev[button]: + cur_buttons |= button + + return cur_buttons + +def checkButtons(): + global pressed_prev + + t = utime.time_monotonic_ms() + pressed = buttons.read(buttons.BOTTOM_LEFT | buttons.TOP_RIGHT | buttons.BOTTOM_RIGHT) + cur_buttons = 0 + + cur_buttons |= checkButton(BUTTON_SEL, BUTTON_SEL_LONG, buttons.BOTTOM_LEFT, pressed, t) + cur_buttons |= checkButton(BUTTON_UP, BUTTON_UP_LONG, buttons.TOP_RIGHT, pressed, t) + cur_buttons |= checkButton(BUTTON_DOWN, BUTTON_DOWN_LONG, buttons.BOTTOM_RIGHT, pressed, t) + + pressed_prev = pressed + return cur_buttons + +def modTime(yrs, mth, day, hrs, mns, sec): + ltime = utime.localtime() + new = utime.mktime((ltime[0] + yrs, ltime[1] + mth, ltime[2] + day, ltime[3] + hrs, ltime[4] + mns, ltime[5] + sec, None, None)) + utime.set_time(new) + +def ctrl_display(bs): + global MODE, updated + updated = True + if bs & BUTTON_SEL_LONG: + MODE = CHANGE_HOURS + else: + updated = False + +def ctrl_chg_hrs(bs): + global MODE, updated + updated = True + if bs & BUTTON_SEL_LONG: + MODE = DISPLAY + elif bs & BUTTON_SEL: + MODE = CHANGE_MINUTES + elif bs & BUTTON_UP_LONG: + modTime(0, 0, 0, 10, 0, 0) + elif bs & BUTTON_DOWN_LONG: + modTime(0, 0, 0, -10, 0, 0) + elif bs & BUTTON_UP: + modTime(0, 0, 0, 1, 0, 0) + elif bs & BUTTON_DOWN: + modTime(0, 0, 0, -1, 0, 0) + else: + updated = False + +def ctrl_chg_mns(bs): + global MODE, updated + updated = True + if bs & BUTTON_SEL_LONG: + MODE = DISPLAY + elif bs & BUTTON_SEL: + MODE = CHANGE_SECONDS + elif bs & BUTTON_UP_LONG: + modTime(0, 0, 0, 0, 10, 0) + elif bs & BUTTON_DOWN_LONG: + modTime(0, 0, 0, 0, -10, 0) + elif bs & BUTTON_UP: + modTime(0, 0, 0, 0, 1, 0) + elif bs & BUTTON_DOWN: + modTime(0, 0, 0, 0, -1, 0) + else: + updated = False + +def ctrl_chg_sec(bs): + global MODE, updated + updated = True + if bs & BUTTON_SEL_LONG: + MODE = DISPLAY + elif bs & BUTTON_SEL: + MODE = CHANGE_YEAR + elif bs & BUTTON_UP_LONG: + modTime(0, 0, 0, 0, 0, 10) + elif bs & BUTTON_DOWN_LONG: + modTime(0, 0, 0, 0, 0, -10) + elif bs & BUTTON_UP: + modTime(0, 0, 0, 0, 0, 1) + elif bs & BUTTON_DOWN: + modTime(0, 0, 0, 0, 0, -1) + else: + updated = False + +def ctrl_chg_yrs(bs): + global MODE, updated + updated = True + if bs & BUTTON_SEL_LONG: + MODE = DISPLAY + elif bs & BUTTON_SEL: + MODE = CHANGE_MONTH + elif bs & BUTTON_UP_LONG: + modTime(10, 0, 0, 0, 0, 0) + elif bs & BUTTON_DOWN_LONG: + modTime(-10, 0, 0, 0, 0, 0) + elif bs & BUTTON_UP: + modTime(1, 0, 0, 0, 0, 0) + elif bs & BUTTON_DOWN: + modTime(-1, 0, 0, 0, 0, 0) + else: + updated = False + +def ctrl_chg_mth(bs): + global MODE, updated + updated = True + if bs & BUTTON_SEL_LONG: + MODE = DISPLAY + elif bs & BUTTON_SEL: + MODE = CHANGE_DAY + elif bs & BUTTON_UP_LONG: + modTime(0, 6, 0, 0, 0, 0) + elif bs & BUTTON_DOWN_LONG: + modTime(0, -6, 0, 0, 0, 0) + elif bs & BUTTON_UP: + modTime(0, 1, 0, 0, 0, 0) + elif bs & BUTTON_DOWN: + modTime(0, -1, 0, 0, 0, 0) + else: + updated = False + +def ctrl_chg_day(bs): + global MODE, updated + updated = True + if bs & BUTTON_SEL_LONG: + MODE = DISPLAY + elif bs & BUTTON_SEL: + MODE = CHANGE_HOURS + elif bs & BUTTON_UP_LONG: + modTime(0, 0, 10, 0, 0, 0) + elif bs & BUTTON_DOWN_LONG: + modTime(0, 0, -10, 0, 0, 0) + elif bs & BUTTON_UP: + modTime(0, 0, 1, 0, 0, 0) + elif bs & BUTTON_DOWN: + modTime(0, 0, -1, 0, 0, 0) + else: + updated = False + +NAME = None +FILENAME = 'nickname.txt' +def load_nickname(): + global NAME + if FILENAME in os.listdir('.'): + with open("nickname.txt", "rb") as f: + name = f.read().strip() + else: + name = b'no nick' + + if len(name) > 7: + name = name[0:7] + else: + name = b' ' * (7 - len(name)) + name + + NAME = name + +# MODE values +DISPLAY = 0 +CHANGE_HOURS = 1 +CHANGE_MINUTES = 2 +CHANGE_SECONDS = 3 +CHANGE_YEAR = 4 +CHANGE_MONTH = 5 +CHANGE_DAY = 6 + +MODE = DISPLAY +MODES = { + DISPLAY: '---', + CHANGE_HOURS: 'HRS', + CHANGE_MINUTES: 'MNS', + CHANGE_SECONDS: 'SEC', + CHANGE_YEAR: 'YRS', + CHANGE_MONTH: 'MTH', + CHANGE_DAY: 'DAY', +} +updated = False + +CTRL_FNS = { + DISPLAY: ctrl_display, + CHANGE_HOURS: ctrl_chg_hrs, + CHANGE_MINUTES: ctrl_chg_mns, + CHANGE_SECONDS: ctrl_chg_sec, + CHANGE_YEAR: ctrl_chg_yrs, + CHANGE_MONTH: ctrl_chg_mth, + CHANGE_DAY: ctrl_chg_day, +} + +def main(): + global updated + try: + load_nickname() + with display.open() as d: + last_secs, secs = 0, 0 + last_msecs, msecs = 0, 0 + while True: + updated = False + + bs = checkButtons() + CTRL_FNS[MODE](bs) + + last_secs, secs = secs, utime.time_monotonic() + if updated or secs > last_secs: + render(d) + + last_msecs, msecs = msecs, utime.time_monotonic_ms() + if msecs - last_msecs < BUTTON_UPDATE_TIME: + utime.sleep_ms(BUTTON_UPDATE_TIME - (msecs - last_msecs)) + except KeyboardInterrupt: + pass + +main() diff --git a/preload/apps/digiclk/draw.py b/preload/apps/digiclk/draw.py new file mode 100644 index 0000000000000000000000000000000000000000..108689b38aebf21de9e276126a7133c15d435d38 --- /dev/null +++ b/preload/apps/digiclk/draw.py @@ -0,0 +1,72 @@ +def _ceilDiv(a, b): + return (a + (b - 1)) // b + +def TipHeight(w): + return _ceilDiv(w, 2) - 1 + +def Tip(d, x, y, w, c, invert = False, swapAxes = False): + h = TipHeight(w) + for dy in range(h): + for dx in range(dy + 1, w - 1 - dy): + px = x + dx + py = y + dy if not invert else y + h - 1 - dy + if swapAxes: + px, py = py, px + d.pixel(px, py, col = c) + +def Seg(d, x, y, w, h, c, swapAxes = False): + tip_h = TipHeight(w) + body_h = h - 2 * tip_h + + Tip(d, x, y, w, c, invert = True, swapAxes = swapAxes) + + px1, px2 = x, x + w + py1, py2 = y + tip_h, y + tip_h + body_h + if swapAxes: + px1, px2, py1, py2 = py1, py2, px1, px2 + d.rect(px1, py1, px2 - 1, py2 - 1, col = c) + + Tip(d, x, y + tip_h + body_h, w, c, invert = False, swapAxes = swapAxes) + +def VSeg(d, x, y, w, l, c): + Seg(d, x, y, w, l, c) + +def HSeg(d, x, y, w, l, c): + Seg(d, y, x, w, l, c, swapAxes = True) + +def GridSeg(d, x, y, w, l, c, swapAxes = False): + sw = w - 2 + tip_h = TipHeight(sw) + + x = x * w + y = y * w + l = (l - 1) * w + Seg(d, x + 1, y + tip_h + 3, sw, l - 3, c, swapAxes = swapAxes) + +def GridVSeg(d, x, y, w, l, c): + GridSeg(d, x, y, w, l, c) + +def GridHSeg(d, x, y, w, l, c): + GridSeg(d, y, x, w, l, c, swapAxes = True) + +def Grid(d, x1, y1, x2, y2, w, c): + for x in range(x1 * w, x2 * w): + for y in range(y1 * w, y2 * w): + if x % w == 0 or x % w == w - 1 or y % w == 0 or y % w == w - 1: + d.pixel(x, y, col = c) + +def Grid7Seg(d, x, y, w, segs, c): + if segs[0]: + GridHSeg(d, x, y, w, 4, c) + if segs[1]: + GridVSeg(d, x + 3, y, w, 4, c) + if segs[2]: + GridVSeg(d, x + 3, y + 3, w, 4, c) + if segs[3]: + GridHSeg(d, x, y + 6, w, 4, c) + if segs[4]: + GridVSeg(d, x, y + 3, w, 4, c) + if segs[5]: + GridVSeg(d, x, y, w, 4, c) + if segs[6]: + GridHSeg(d, x, y + 3, w, 4, c) diff --git a/preload/apps/digiclk/metadata.json b/preload/apps/digiclk/metadata.json new file mode 100644 index 0000000000000000000000000000000000000000..ff0c81709b7773e8248f784c63196385391e60f7 --- /dev/null +++ b/preload/apps/digiclk/metadata.json @@ -0,0 +1,7 @@ +{ + "name": "DigiClk", + "description": "A Digital Clock, configurable via buttons", + "category": "wearable", + "author": "yrlf", + "revision": 1 +} diff --git a/preload/apps/digiclk/monotime.py b/preload/apps/digiclk/monotime.py new file mode 100644 index 0000000000000000000000000000000000000000..cdf366d678fe69cf9043e543216491b22875292f --- /dev/null +++ b/preload/apps/digiclk/monotime.py @@ -0,0 +1,59 @@ +import utime as _utime + +_offset_ms = 0 + +def time_monotonic(): + return _utime.time() + _offset_ms // 1000 + +def time_monotonic_ms(): + return _utime.time_ms() + _offset_ms + +def sleep(s): + return _utime.sleep(s) + +def sleep_ms(ms): + return _utime.sleep_ms(ms) + +def sleep_us(us): + return _utime.sleep_us(us) + +def time(): + return _utime.time() + +def time_ms(): + return _utime.time_ms() + +def set_time(t): + global _offset_ms + + cur_t = _utime.time_ms() + _utime.set_time(t) + new_t = _utime.time_ms() + + diff = cur_t - new_t + _offset_ms += diff + +def set_unix_time(t): + global _offset_ms + + cur_t = _utime.time_ms() + _utime.set_unix_time(t) + new_t = _utime.time_ms() + + diff = cur_t - new_t + _offset_ms += diff + +def localtime(s = None): + if s != None: + return _utime.localtime(s) + else: + return _utime.localtime() + +def mktime(t): + return _utime.mktime(t) + +def alarm(s, cb = None): + if cb != None: + return _utime.alarm(s, cb) + else: + return _utime.alarm(s)