Ejemplo n.º 1
0
    def draw_busy(self, msg, percent):
        from display import FontTiny
        from glob import dis

        self.last_percent = 0.5

        # centered in bottom part of screen.
        y = 48

        if percent is not None:
            self.percent = percent

            # reset display once we're at 100%
            if percent >= 0.995:  # ~ last pixel
                self.percent = None
                self.busy_text = msg = None

        if msg is not None:
            self.busy_text = msg

        if self.busy_text is not None:
            # clear under it
            dis.clear_rect(0, y, 128, 64 - y)
            dis.text(None, y, self.busy_text)

        if self.percent is not None:
            x = int(128 * self.percent)
            dis.dis.hline(0, 63, x, 1)
            dis.dis.hline(x + 1, 63, 127, 0)

        dis.show()
Ejemplo n.º 2
0
async def ux_enter_number(prompt, max_value):
    # return the decimal number which the user has entered
    # - default/blank value assumed to be zero
    # - clamps large values to the max
    from glob import dis
    from display import FontTiny
    from math import log

    # allow key repeat on X only
    press = PressRelease('1234567890y')

    y = 26
    value = ''
    max_w = int(log(max_value, 10) + 1)

    dis.clear()
    dis.text(0, 0, prompt)
    dis.text(None, -1, "X to DELETE, or OK when DONE.", FontTiny)
    dis.save()

    while 1:
        dis.restore()

        # text centered
        if value:
            bx = dis.text(None, y, value)
            dis.icon(bx + 1, y + 11, 'space')
        else:
            dis.icon(64 - 7, y + 11, 'space')

        dis.show()

        ch = await press.wait()
        if ch == 'y':

            if not value: return 0
            return min(max_value, int(value))

        elif ch == 'x':
            if value:
                value = value[0:-1]
            else:
                # quit if they press X on empty screen
                return 0
        else:
            if len(value) == max_w:
                value = value[0:-1] + ch
            else:
                value += ch

            # cleanup leading zeros and such
            value = str(int(value))
Ejemplo n.º 3
0
    async def wait_til_state(want):
        dis.clear()
        dis.text(None, 10, 'MicroSD Card:')
        dis.text(None,
                 34,
                 'Remove' if sd.present() else 'Insert',
                 font=FontLarge)
        dis.show()

        while 1:
            if want == sd.present(): return
            await sleep_ms(100)
            if ux_poll_once():
                raise RuntimeError("MicroSD test aborted")
Ejemplo n.º 4
0
async def test_numpad():
    # do an interactive self test

    keys = list('123456789x0y')

    for ch in keys:
        dis.clear()
        dis.text(0, 0, "Numpad Test. Press:")
        dis.text(None, 24, ch if ch != 'y' else 'OK', FontLarge)
        dis.show()

        k = await ux_wait_keyup(ch + 'x')
        if k == 'x' and ch != 'x':
            raise RuntimeError("numpad test aborted")
        assert k == ch
Ejemplo n.º 5
0
async def test_oled():
    # all on/off tests
    for ph in (1, 0):
        dis.clear()
        dis.dis.fill(ph)
        dis.text(None, 2, "Selftest", invert=ph)
        dis.text(None,
                 30,
                 "All on?" if ph else 'All off?',
                 invert=ph,
                 font=FontLarge)
        dis.show()

        ch = await ux_wait_keyup('yx')
        if ch != 'y':
            raise RuntimeError("OLED test aborted")
Ejemplo n.º 6
0
    def show(self):
        from glob import dis, hsm_active

        # Plan: show "time til period reset", and some stats,
        # but never show amounts or private info.

        dis.dis.buffer[:] = self.screen_buf[:]

        left = hsm_active.get_time_left()
        if left is None:
            left = ' n/a'
        elif left == -1:
            left = ' --'
        else:
            left = period_display(left)

        # 3 statistics; see draw_background for X positions
        y = 28 + 1
        for x, val in [(14, str(hsm_active.approvals)),
                       (51, str(hsm_active.refusals)), (98, left)]:
            tw = 7 * len(val)  # = dis.width(val, FontSmall)
            dis.text(x - tw // 2, y, val)

        # heartbeat display
        if 1:
            #self.phase = (utime.ticks_ms() // 50) % len(cylon)
            self.phase = (self.phase + 1) % len(cylon)
            x = cylon[self.phase]
            w = 12
            dis.dis.line(x, 63, x + w - 1, 63, True)

        if self.digits:
            # UX "feedback" for digits
            if len(self.digits) < 6:
                msg = self.digits + ('#' * (6 - len(self.digits)))
            elif self.digits:
                msg = self.digits

            # dis.width('######', FontSmall) == 42
            x, y, w, h = 80, 0, 42, 14
            dis.clear_rect(x, y, x + w, y + h)
            dis.text(x, y, msg)

        # contains a dis.show()
        self.draw_busy(None, None)
Ejemplo n.º 7
0
async def test_sd_active():
    # Mark 2: SD Card active light.
    from machine import Pin
    led = Pin('SD_ACTIVE', Pin.OUT)

    for ph in range(2):
        gg = not ph
        led.value(gg)

        dis.clear()
        if gg:
            dis.text(0, 16, "<-- Green ON?")
        else:
            dis.text(0, 16, "<-- Green off?")

        dis.show()
        k = await ux_wait_keyup('xy')
        assert k == 'y'  # "SD Active LED bust"
Ejemplo n.º 8
0
async def test_sflash():
    dis.clear()
    dis.text(None, 18, 'Serial Flash')
    dis.show()

    from sflash import SF
    from ustruct import pack
    import ngu

    msize = 1024 * 1024
    SF.chip_erase()

    for phase in [0, 1]:
        steps = 7 * 4
        for i in range(steps):
            dis.progress_bar(i / steps)
            dis.show()
            await sleep_ms(250)
            if not SF.is_busy(): break

        assert not SF.is_busy()  # "didn't finish"

        # leave chip blank
        if phase == 1: break

        buf = bytearray(32)
        for addr in range(0, msize, 1024):
            SF.read(addr, buf)
            assert set(buf) == {255}  # "not blank"

            rnd = ngu.hash.sha256s(pack('I', addr))
            SF.write(addr, rnd)
            SF.read(addr, buf)
            assert buf == rnd  #  "write failed"

            dis.progress_bar_show(addr / msize)

        # check no aliasing, also right size part
        for addr in range(0, msize, 1024):
            expect = ngu.hash.sha256s(pack('I', addr))
            SF.read(addr, buf)
            assert buf == expect  # "readback failed"

            dis.progress_bar_show(addr / msize)
Ejemplo n.º 9
0
    def show(self):
        #
        # Redraw the menu.
        #
        from glob import dis
        dis.clear()

        #print('cur=%d ypos=%d' % (self.cursor, self.ypos))

        # subclass hook
        self.early_draw(dis)

        x, y = (10, 2)
        h = 14
        for n in range(self.ypos + PER_M + 1):
            if n + self.ypos >= self.count: break
            msg = self.items[n + self.ypos].label
            is_sel = (self.cursor == n + self.ypos)
            if is_sel:
                dis.dis.fill_rect(0, y, 128, h - 1, 1)
                dis.icon(2, y, 'wedge', invert=1)
                dis.text(x, y, msg, invert=1)
            else:
                dis.text(x, y, msg)

            if msg[0] == ' ' and self.space_indicators:
                dis.icon(x - 2, y + 11, 'space', invert=is_sel)

            if self.chosen is not None and (n + self.ypos) == self.chosen:
                dis.icon(108, y, 'selected', invert=is_sel)

            y += h
            if y > 128: break

        # subclass hook
        self.late_draw(dis)

        if self.count > PER_M:
            dis.scroll_bar(self.ypos / (self.count - PER_M))

        dis.show()
Ejemplo n.º 10
0
def show_fatal_error(msg):
    # show a multi-line error message, over some kinda "fatal" banner
    from glob import dis
    from display import FontTiny

    dis.clear()
    lines = msg.split('\n')[-6:]
    dis.text(None, 1, '>>>> Yikes!! <<<<')

    y = 13 + 2
    for num, ln in enumerate(lines):
        ln = ln.strip()

        if ln[0:6] == 'File "':
            # convert: File "main.py", line 63, in interact
            #    into: main.py:63  interact
            ln = ln[6:].replace('", line ', ':').replace(', in ', '  ')

        dis.text(0, y + (num * 8), ln, FontTiny)

    dis.show()
Ejemplo n.º 11
0
    def draw_background(self):
        # Render and capture static parts of screen one-time.
        from glob import dis
        from display import FontTiny

        dis.clear()
        dis.text(6, 0, "HSM MODE")
        dis.show()  # cover the 300ms or so it takes to draw the rest below

        dis.hline(15)

        x, y = 0, 28
        for lab, xoff, val in [
            ('APPROVED', 0, '0'),
            ('REFUSED', 0, '0'),
            ('PERIOD LEFT', 5, 'xx'),
        ]:
            nx = dis.text(x + xoff, y - 7, lab, FontTiny)
            hw = nx - x
            if lab == 'REFUSED':
                dis.dis.line(nx + 2, 0, nx + 2, y + 16, 1)
            else:
                if not xoff:
                    dis.dis.line(nx + 2, y - 12, nx + 2, y + 16, 1)

            # keep this:
            #print('%s @ x=%d' % (lab, x+(hw//2)-2))

            # was:
            #tw = 7*len(val)     # = dis.width(val, FontSmall)
            #dis.text(x+((hw-tw)//2)-1, y+1, val)
            x = nx + 7

        dis.hline(y + 17)

        # no local confirmation code entered, typically
        dis.text(80, 0, '######')

        # save this static background
        self.screen_buf = dis.dis.buffer[:]
Ejemplo n.º 12
0
    def redraw(self):
        # Redraw screen.
        from glob import dis
        from display import FontSmall, FontTiny

        # what we are showing inside the QR
        msg = self.addrs[self.idx]

        # make the QR, if needed.
        if not self.qr_data:
            dis.busy_bar(True)

            self.render_qr(msg)

        # draw display
        dis.clear()

        w = 29  # because version=3
        XO, YO = 7, 3  # offsets

        if not self.invert:
            dis.dis.fill_rect(XO - YO, 0, 64, 64, 1)

        inv = self.invert
        for x in range(w):
            for y in range(w):
                px = self.qr_data.get(x, y)
                X = (x * 2) + XO
                Y = (y * 2) + YO
                dis.dis.fill_rect(X, Y, 2, 2, px if inv else (not px))

        x, y = 73, 0 if self.is_alnum else 2
        sidebar, ll = self.sidebar or (msg, 7)
        for i in range(0, len(sidebar), ll):
            dis.text(x, y, sidebar[i:i + ll], FontSmall)
            y += 10 if self.is_alnum else 12

        if not inv and len(self.addrs) > 1:
            # show path number, very tiny
            ai = str(self.start_n + self.idx)
            if len(ai) == 1:
                dis.text(0, 30, ai[0], FontTiny)
            else:
                dis.text(0, 27, ai[0], FontTiny)
                dis.text(0, 27 + 7, ai[1], FontTiny)

        dis.busy_bar(False)  # includes show
Ejemplo n.º 13
0
    async def add_numbers(self, *a):
        # collect a series of digits
        from glob import dis
        from display import FontTiny, FontSmall
        global pp_sofar

        # allow key repeat on X only
        press = PressRelease('1234567890y')

        footer = "X to DELETE, or OK when DONE."
        lx = 6
        y = 16
        here = ''

        dis.clear()
        dis.text(None, -1, footer, FontTiny)
        dis.save()

        while 1:
            dis.restore()

            # text centered
            msg = here
            by = y
            bx = dis.text(lx, y, msg[0:16])
            dis.text(lx, y - 9,
                     str(pp_sofar, 'ascii').replace(' ', '_'), FontTiny)

            if len(msg) > 16:
                # second line when needed (left just)
                by += 15
                bx = dis.text(lx, by, msg[16:])

            if len(here) < 32:
                dis.icon(bx, by - 2, 'sm_box')

            dis.show()

            ch = await press.wait()
            if ch == 'y':
                pp_sofar += here
                self.check_length()
                return
            elif ch == 'x':
                if here:
                    here = here[0:-1]
                else:
                    # quit if they press X on empty screen
                    return
            else:
                if len(here) < 32:
                    here += ch
Ejemplo n.º 14
0
async def test_secure_element():

    assert not get_is_bricked()  # bricked already

    # test right chips installed
    is_fat = ckcc.is_stm32l496()
    if is_fat:
        assert version.has_608  # expect 608a
        assert version.hw_label == 'mk3'
    else:
        assert not version.has_608  # expect 508a
        assert version.hw_label != 'mk3'

    if ckcc.is_simulator(): return

    for ph in range(5):
        gg = get_genuine()

        dis.clear()
        if gg:
            dis.text(-1, 8, "Green ON? -->")
        else:
            dis.text(-1, 50, "Red ON? -->")

        dis.show()
        k = await ux_wait_keyup('xy')
        assert k == 'y'  # "LED bust"

        if ph and gg:
            # stop once it's on and we've tested both states
            return

        # attempt to switch to other state
        if gg:
            clear_genuine()
        else:
            # very slow!
            dis.text(0, 0, "Wait")
            dis.show()
            set_genuine()
            ux_clear_keys()

        ng = get_genuine()
        assert ng != gg  # "Could not invert LED"
Ejemplo n.º 15
0
async def ux_show_story(msg,
                        title=None,
                        escape=None,
                        sensitive=False,
                        strict_escape=False):
    # show a big long string, and wait for XY to continue
    # - returns character used to get out (X or Y)
    # - can accept other chars to 'escape' as well.
    # - accepts a stream or string
    from glob import dis, numpad
    from display import FontLarge

    lines = []
    if title:
        # kinda weak rendering but it works.
        lines.append('\x01' + title)

    if hasattr(msg, 'readline'):
        msg.seek(0)
        for ln in msg:
            if ln[-1] == '\n':
                ln = ln[:-1]

            if len(ln) > CH_PER_W:
                lines.extend(word_wrap(ln, CH_PER_W))
            else:
                # ok if empty string, just a blank line
                lines.append(ln)

        # no longer needed & rude to our caller, but let's save the memory
        msg.close()
        del msg
        gc.collect()
    else:
        for ln in msg.split('\n'):
            if len(ln) > CH_PER_W:
                lines.extend(word_wrap(ln, CH_PER_W))
            else:
                # ok if empty string, just a blank line
                lines.append(ln)

    # trim blank lines at end, add our own marker
    while not lines[-1]:
        lines = lines[:-1]

    lines.append('EOT')

    #print("story:\n\n\"" + '"\n"'.join(lines))
    #lines[0] = '111111111121234567893'

    top = 0
    H = 5
    ch = None
    pr = PressRelease()
    while 1:
        # redraw
        dis.clear()

        y = 0
        for ln in lines[top:top + H]:
            if ln == 'EOT':
                dis.hline(y + 3)
            elif ln and ln[0] == '\x01':
                dis.text(0, y, ln[1:], FontLarge)
                y += 21
            else:
                dis.text(0, y, ln)

                if sensitive and len(ln) > 3 and ln[2] == ':':
                    dis.mark_sensitive(y, y + 13)

                y += 13

        dis.scroll_bar(top / len(lines))
        dis.show()

        # wait to do something
        ch = await pr.wait()
        if escape and (ch == escape or ch in escape):
            # allow another way out for some usages
            return ch
        elif ch in 'xy':
            if not strict_escape:
                return ch
        elif ch == '0':
            top = 0
        elif ch == '7':  # page up
            top = max(0, top - H)
        elif ch == '9':  # page dn
            top = min(len(lines) - 2, top + H)
        elif ch == '5':  # scroll up
            top = max(0, top - 1)
        elif ch == '8':  # scroll dn
            top = min(len(lines) - 2, top + 1)
Ejemplo n.º 16
0
async def spinner_edit(pw, confirm_exit=True):
    # Allow them to pick each digit using "D-pad"
    from glob import dis
    from display import FontTiny, FontSmall

    # Should allow full unicode, NKDN
    # - but limited to what we can show in FontSmall
    # - so really just ascii; not even latin-1
    # - 8-bit codepoints only
    my_rng = range(32, 127)          # FontSmall.code_range
    symbols = b' !"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~'
    letters = b'abcdefghijklmnopqrstuvwxyz'
    Letters = b'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    numbers = b'1234567890'
    #assert len(set(symbols+letters+Letters+numbers)) == len(my_rng)
    
    footer1 = "1=Letters  2=Numbers  3=Symbols"
    footer2 = "4=SwapCase  0=HELP"
    y = 20
    pw = bytearray(pw or 'A')

    pos = len(pw)-1       # which part being changed
    n_visible = const(9)
    scroll_x = max(pos - n_visible, 0)

    def cycle_set(which, direction=1):
        # pick next item in set of choices
        for n, s in enumerate(which):
            if pw[pos] == s:
                try:
                    pw[pos] = which[n+direction]
                except IndexError:
                    pw[pos] = which[0 if direction==1 else -1]
                return
        pw[pos] = which[0]

    def change(dx):
        # next/prev within the same subset of related chars
        ch = pw[pos]
        for subset in [symbols, letters, Letters, numbers]:
            if ch in subset:
                return cycle_set(subset, dx)

        # probably unreachable code: numeric up/down
        ch = pw[pos] + dx
        if ch not in my_rng:
            ch = (my_rng.stop-1) if dx < 0 else my_rng.start
            assert ch in my_rng
        pw[pos] = ch

    # pre-render the fixed stuff
    dis.clear()
    dis.text(None, -10, footer1, FontTiny)
    dis.text(None, -1, footer2, FontTiny)
    dis.save()

    # no key-repeat on certain keys
    press = PressRelease('4xy')
    while 1:
        dis.restore()

        lr = pos - scroll_x     # left/right distance of cursor
        if lr < 4 and scroll_x:
            scroll_x -= 1
        elif lr < 0:
            scroll_x = pos
        elif lr >= (n_visible-1):
            # past right edge
            scroll_x += 1

        for i in range(n_visible):
            # calc abs position in string
            ax = scroll_x + i
            x = 4 + (13*i)
            try:
                ch = pw[ax]
            except IndexError:
                continue

            if ax == pos:
                # draw cursor
                if len(pw) < 2*n_visible:
                    dis.text(x-4, y-19, '0x%02X' % ch, FontTiny)
                dis.icon(x-2, y-10, 'spin')

            if ch == 0x20:
                dis.icon(x, y+11, 'space')
            else:
                dis.text(x, y, chr(ch) if ch in my_rng else chr(215), FontSmall)

        if scroll_x > 0:
            dis.text(2, y-14, str(pw, 'ascii')[0:scroll_x].replace(' ', '_'), FontTiny)
        if scroll_x + n_visible < len(pw):
            dis.text(-1, 1, "MORE>", FontTiny)

        dis.show()

        ch = await press.wait()
        if ch == 'y':
            return str(pw, 'ascii')
        elif ch == 'x':
            if len(pw) > 1:
                # delete current char
                pw = pw[0:pos] + pw[pos+1:]
                if pos >= len(pw):
                    pos = len(pw)-1
            else:
                if confirm_exit:
                    pp = await ux_show_story(
                        "OK to leave without any changes? Or X to cancel leaving.")
                    if pp == 'x': continue
                return None

        elif ch == '7':      # left
            pos -= 1
            if pos < 0: pos = 0
        elif ch == '9':      # right
            pos += 1
            if pos >= len(pw):
                if len(pw) < 100 and pw[-3:] != b'   ':
                    pw += ' '       # expand with spaces
                else:
                    pos -= 1        # abort addition

        elif ch == '5':     # up
            change(1)
        elif ch == '8':     # down
            change(-1)
        elif ch == '1':     # alpha
            cycle_set(b'Aa')
        elif ch == '4':     # toggle case
            if (pw[pos] & ~0x20) in range(65, 91):
                pw[pos] ^= 0x20
        elif ch == '2':     # numbers
            cycle_set(numbers)
        elif ch == '3':     # symbols (all of them)
            cycle_set(symbols)
        elif ch == '0':     # help
            help_msg = '''\
Use arrow keys (5789) to select letter and move around. 

1=Letters (Aa..)
2=Numbers (12..)
3=Symbols (!@#&*)
4=Swap Case (q/Q)
X=Delete char

Add more characters by moving past end (right side).'''

            if confirm_exit:
                help_msg += '\nTo quit without changes, delete everything.'
            await ux_show_story(help_msg)
Ejemplo n.º 17
0
async def add_dice_rolls(count, seed, judge_them):
    from glob import dis
    from display import FontTiny, FontLarge

    md = sha256(seed)
    pr = PressRelease()

    # fixed parts of screen
    dis.clear()
    y = 38
    dis.text(0, y, "Press 1-6 for each dice"); y += 13
    dis.text(0, y, "roll to mix in.")
    dis.save()

    while 1:
        # Note: cannot scroll this msg because 5=up arrow
        dis.restore()
        dis.text(None, 0, '%d rolls' % count, FontLarge)

        hx = str(b2a_hex(md.digest()), 'ascii')
        dis.text(0, 20, hx[0:32], FontTiny)
        dis.text(0, 20+7, hx[32:], FontTiny)

        dis.show()

        ch = await pr.wait()

        if ch in '123456':
            count += 1

            dis.restore()
            dis.text(None, 0, '%d rolls' % count, FontLarge)
            dis.show()

            # this is slow enough to see
            md.update(ch)

        elif ch == 'x':
            # Because the change (roll) has already been applied,
            # only let them abort if it's early still
            if count < 10 and judge_them:
                return 0, seed
        elif ch == 'y':
            if count < 99 and judge_them:
                if not count:
                    return 0, seed
                ok = await ux_confirm('''\
You only provided %d dice rolls, and each roll adds only 2.585 bits of entropy. \
For 128-bit security, which is considered the minimum, you need 50 rolls, and \
for 256-bits of security, 99 rolls.''' % count)
                if not ok: continue
            break

    if count:
        seed = md.digest()

    return count, seed
Ejemplo n.º 18
0
async def test_microsd():
    if ckcc.is_simulator(): return

    async def wait_til_state(want):
        dis.clear()
        dis.text(None, 10, 'MicroSD Card:')
        dis.text(None,
                 34,
                 'Remove' if sd.present() else 'Insert',
                 font=FontLarge)
        dis.show()

        while 1:
            if want == sd.present(): return
            await sleep_ms(100)
            if ux_poll_once():
                raise RuntimeError("MicroSD test aborted")

    try:
        import pyb
        sd = pyb.SDCard()
        sd.power(0)

        # test presence switch
        for ph in range(7):
            await wait_til_state(not sd.present())

            if ph >= 2 and sd.present():
                # debounce
                await sleep_ms(100)
                if sd.present(): break
                if ux_poll_once():
                    raise RuntimeError("MicroSD test aborted")

        dis.clear()
        dis.text(None, 10, 'MicroSD Card:')
        dis.text(None, 34, 'Testing', font=FontLarge)
        dis.show()

        # card inserted
        assert sd.present()  #, "SD not present?"

        # power up?
        sd.power(1)
        await sleep_ms(100)

        try:
            blks, bsize, *unused = sd.info()
            assert bsize == 512
        except:
            assert 0  # , "card info"

        # just read it a bit, writing would prove little
        buf = bytearray(512)
        msize = 256 * 1024
        for addr in range(0, msize, 1024):
            sd.readblocks(addr, buf)
            dis.progress_bar_show(addr / msize)

            if addr == 0:
                assert buf[-2:] == b'\x55\xaa'  # "Bad read"

        # force removal, so cards don't get stuck in finished units
        await wait_til_state(False)

    finally:
        # CRTICAL: power it back down
        sd.power(0)