Ejemplo n.º 1
0
async def make_summary_file(fname_pattern='public.txt'):
    # record **public** values and helpful data into a text file
    from main import dis, pa, settings
    from files import CardSlot, CardMissingError
    from actions import needs_microsd

    dis.fullscreen('Generating...')

    # generator function:
    body = generate_public_contents()

    # choose a filename

    try:
        with CardSlot() as card:
            fname, nice = card.pick_filename(fname_pattern)

            # do actual write
            with open(fname, 'wb') as fd:
                for part in body:
                    fd.write(part.encode())

    except CardMissingError:
        await needs_microsd()
        return
    except Exception as e:
        await ux_show_story('Failed to write!\n\n\n' + str(e))
        return

    msg = '''Summary file written:\n\n%s''' % nice
    await ux_show_story(msg)
Ejemplo n.º 2
0
    async def interact(self):
        # Prompt user w/ details and get approval
        from main import dis

        ch = await ux_show_story(MSG_SIG_TEMPLATE.format(msg=self.text, 
                            addr=self.address, subpath=self.subpath))

        if ch != 'y':
            # they don't want to!
            self.refused = True
        else:
            dis.fullscreen('Signing...', percent=.25)

            # do the signature itself!
            with stash.SensitiveValues() as sv:
                dis.progress_bar_show(.50)

                node = sv.derive_path(self.subpath)
                pk = node.private_key()
                sv.register(pk)

                digest = sv.chain.hash_message(self.text.encode())

                dis.progress_bar_show(.75)
                self.result = tcc.secp256k1.sign(pk, digest)

            dis.progress_bar_show(1.0)

        self.done()
Ejemplo n.º 3
0
async def restore_complete_doit(fname_or_fd, words):
    from main import dis

    # build password
    password = '******'.join(words)

    # filename already picked, taste it and maybe consider using it's data.
    try:
        fd = open(fname_or_fd, 'rb') if isinstance(fname_or_fd, str) else fname_or_fd
    except:
        await ux_show_story('Unable to open backup file. \n\n' + str(fname_or_fd))
        return

    try:
        if not words:
            contents = fd.read()
        else:
            try:
                compat7z.check_file_headers(fd)
            except Exception as e:
                await ux_show_story('Unable to read backup file. Has it been touched?'
                                        '\n\nError: ' + str(e))
                return

            dis.fullscreen("Decrypting...")
            try:
                zz = compat7z.Builder()
                fname, contents = zz.read_file(fd, password, progress_fcn=dis.progress_bar_show)

                assert fname == 'ckcc-backup.txt', "Wrong filename in archive"

                # simple quick sanity check
                assert contents[0:1] == b'#' and contents[-1:] == b'\n', "Corrupted after decrypt"

            except Exception as e:
                # assume everything here is "password wrong" errors
                print("pw wrong?  %s" % e)

                await ux_show_story('Unable to decrypt backup file. Incorrect password?'
                                        '\n\nTried:\n\n' + password)

                return
    finally:
        fd.close()

    vals = {}
    for line in contents.decode().split('\n'):
        if not line: continue
        if line[0] == '#': continue

        try:
            k,v = line.split(' = ', 1)
            #print("%s = %s" % (k, v))

            vals[k] = ujson.loads(v)
        except:
            print("unable to decode line: %r" % line)
            # but keep going!

    await restore_from_dict(vals)
Ejemplo n.º 4
0
    async def handle_upload(self, offset, total_size, data):
        from main import dis, sf, hsm_active
        from utils import check_firmware_hdr
        from sigheader import FW_HEADER_OFFSET, FW_HEADER_SIZE

        # maintain a running SHA256 over what's received
        if offset == 0:
            self.file_checksum = tcc.sha256()

        assert offset % 256 == 0, 'alignment'
        assert offset + len(data) <= total_size <= MAX_UPLOAD_LEN, 'long'

        if hsm_active:
            # additional restrictions in HSM mode
            assert offset + len(data) <= total_size <= MAX_TXN_LEN, 'psbt'
            if offset == 0:
                assert data[0:5] == b'psbt\xff', 'psbt'

        for pos in range(offset, offset + len(data), 256):
            if pos % 4096 == 0:
                # erase here
                dis.fullscreen("Receiving...", offset / total_size)

                sf.sector_erase(pos)

                while sf.is_busy():
                    await sleep_ms(10)

            # write up to 256 bytes
            here = data[pos - offset:pos - offset + 256]

            self.file_checksum.update(here)

            # Very special case for firmware upgrades: intercept and modify
            # header contents on the fly, and also fail faster if wouldn't work
            # on this specific hardware.
            # - workaround: ckcc-protocol upgrade process understates the file
            #   length and appends hdr, but that's kinda a bug, so support both
            if (pos == (FW_HEADER_OFFSET & ~255)
                    or pos == (total_size - FW_HEADER_SIZE)
                    or pos == total_size):

                prob = check_firmware_hdr(memoryview(here)[-128:],
                                          None,
                                          bad_magic_ok=True)
                if prob:
                    raise ValueError(prob)

            sf.write(pos, here)

            # full page write: 0.6 to 3ms
            while sf.is_busy():
                await sleep_ms(1)

        if offset + len(data) >= total_size and not hsm_active:
            # probably done
            dis.progress_bar_show(1.0)
            ux.restore_menu()

        return offset
Ejemplo n.º 5
0
def wipe_flash_filesystem():
    # erase and re-format the flash filesystem (/flash/)
    import ckcc, pyb
    from main import dis, settings

    dis.fullscreen('Erasing...')
    os.umount('/flash')

    # from extmod/vfs.h
    BP_IOCTL_SEC_COUNT = (4)
    BP_IOCTL_SEC_SIZE = (5)

    # block-level erase
    fl = pyb.Flash()
    bsize = fl.ioctl(BP_IOCTL_SEC_SIZE, 0)
    assert bsize == 512
    bcount = fl.ioctl(BP_IOCTL_SEC_COUNT, 0)

    blk = bytearray(bsize)
    ckcc.rng_bytes(blk)

    # trickiness: actual flash blocks are offset by 0x100 (FLASH_PART1_START_BLOCK)
    # so fake MBR can be inserted. Count also inflated by 2X, but not from ioctl above.
    for n in range(bcount):
        fl.writeblocks(n + 0x100, blk)
        ckcc.rng_bytes(blk)

        dis.progress_bar_show(n * 2 / bcount)

    # rebuild and mount /flash
    dis.fullscreen('Rebuilding...')
    ckcc.wipe_fs()

    # re-store settings
    settings.save()
Ejemplo n.º 6
0
    async def try_login(self, retry=True):
        from main import pa, numpad

        while retry:
            self.reset()

            pin = await self.interact()

            if pin is None:
                # Perhaps they are having trouble with touch pad?
                numpad.sensitivity = 2
                continue
            
            pa.setup(pin, self.is_secondary)

            if pa.is_delay_needed() or pa.num_fails:
                await self.do_delay(pa)

            # do the actual login attempt now
            dis.fullscreen("Wait...")
            try:
                ok = pa.login()
                if ok: break
            except RuntimeError as e:
                # I'm a brick and other stuff can happen here
                print("pa.login: %r" % e)

            await ux_show_story('''\
That's not the right PIN!\n
Please check all digits carefully, and that prefix verus suffix break point is correct.
Your next attempt will take even longer, so please keep that in mind.
''', title='Wrong PIN')
Ejemplo n.º 7
0
def wipe_microsd_card():
    import ckcc, pyb
    from main import dis

    try:
        os.umount('/sd')
    except:
        pass

    sd = pyb.SDCard()
    assert sd

    if not sd.present(): return

    # power cycle so card details (like size) are re-read from current card
    sd.power(0)
    sd.power(1)

    dis.fullscreen('Part Erase...')
    cutoff = 1024  # arbitrary
    blk = bytearray(512)

    for bnum in range(cutoff):
        ckcc.rng_bytes(blk)
        sd.writeblocks(bnum, blk)
        dis.progress_bar_show(bnum / cutoff)

    dis.fullscreen('Formating...')

    # remount, with newfs option
    os.mount(sd, '/sd', readonly=0, mkfs=1)
Ejemplo n.º 8
0
async def remember_bip39_passphrase():
    # Compute current xprv and switch to using that as root secret.
    import stash
    from main import dis, pa

    if not stash.bip39_passphrase:
        if not await ux_confirm(
                '''You do not have a BIP39 passphrase set right now, so this command does little except forget the seed words. It does not enhance security.'''
        ):
            return

    dis.fullscreen('Check...')

    with stash.SensitiveValues() as sv:
        if sv.mode != 'words':
            # not a BIP39 derived secret, so cannot work.
            await ux_show_story(
                '''The wallet secret was not based on a seed phrase, so we cannot add a BIP39 passphrase at this time.''',
                title='Failed')
            return

        nv = SecretStash.encode(xprv=sv.node)

    dis.fullscreen('Saving...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Ejemplo n.º 9
0
async def make_json_wallet(label, generator, fname_pattern='new-wallet.json'):
    # Record **public** values and helpful data into a JSON file

    from main import dis, pa, settings
    from files import CardSlot, CardMissingError
    from actions import needs_microsd

    dis.fullscreen('Generating...')

    body = generator()

    # choose a filename

    try:
        with CardSlot() as card:
            fname, nice = card.pick_filename(fname_pattern)

            # do actual write
            with open(fname, 'wt') as fd:
                ujson.dump(body, fd)

    except CardMissingError:
        await needs_microsd()
        return
    except Exception as e:
        await ux_show_story('Failed to write!\n\n\n' + str(e))
        return

    msg = '''%s file written:\n\n%s''' % (label, nice)
    await ux_show_story(msg)
Ejemplo n.º 10
0
async def remember_bip39_passphrase():
    # Compute current xprv and switch to using that as root secret.
    import stash
    from main import dis, pa

    dis.fullscreen('Check...')

    with stash.SensitiveValues() as sv:
        if sv.mode != 'words':
            # not a BIP39 derived secret, so cannot work.
            await ux_show_story('''The wallet secret was not based on a seed phrase, so we cannot add a BIP39 passphrase at this time.''', title='Failed')
            return

        nv = SecretStash.encode(xprv=sv.node)

    # Important: won't write new XFP to nvram if pw still set
    stash.bip39_passphrase = ''

    dis.fullscreen('Saving...')
    pa.change(new_secret=nv)

    # re-read settings since key is now different
    # - also captures xfp, xpub at this point
    pa.new_main_secret(nv)

    # check and reload secret
    pa.reset()
    pa.login()
Ejemplo n.º 11
0
    async def append(self, xfp, bip39pw):
        # encrypt and save; always appends.
        from ux import ux_dramatic_pause
        from main import dis
        from actions import needs_microsd

        while 1:
            dis.fullscreen('Saving...')

            try:
                with CardSlot() as card:
                    self._calc_key(card)

                    data = self._read(card) if self.key else []

                    data.append(dict(xfp=xfp, pw=bip39pw))

                    encrypt = tcc.AES(tcc.AES.CTR | tcc.AES.Encrypt, self.key)

                    msg = encrypt.update(ujson.dumps(data))

                    with open(self.filename(card), 'wb') as fd:
                        fd.write(msg)

                await ux_dramatic_pause("Saved.", 1)
                return

            except CardMissingError:
                ch = await needs_microsd()
                if ch == 'x':  # undocumented, but needs escape route
                    break
Ejemplo n.º 12
0
    def activate(self, new_file):
        # user approved the HSM activation, so apply it.
        from main import pa, dis

        import main
        assert not main.hsm_active
        main.hsm_active = self

        self.start_time = utime.ticks_ms()

        if new_file:
            dis.fullscreen("Saving...")

            # save config for next run
            with open(POLICY_FNAME, 'w+t') as f:
                ujson.dump(self.save(), f)

            # that changes the flash, so need to update
            # the hash stored in SE
            pa.greenlight_firmware()
            dis.show()

        if self.set_sl:
            self.save_storage_locker()

        self.reset_period()

        if self.boot_to_hsm and not new_file:
            # In boot-to-HSM mode, we cant be sure PIN holder has authority
            # to spend, so maybe they are rebooting to reset the period.
            # Assume period has already been used up (conservative model)
            for r in self.rules:
                if r.per_period:
                    self.record_spend(r, r.per_period)
Ejemplo n.º 13
0
    def make_msg(start):
        msg = ''
        if start == 0:
            msg = "Press 1 to save to MicroSD."
            if version.has_fatram:
                msg += " 4 to view QR Codes."
            msg += '\n\n'
        msg += "Addresses %d..%d:\n\n" % (start, start + n - 1)

        addrs = []
        chain = chains.current_chain()

        dis.fullscreen('Wait...')

        with stash.SensitiveValues() as sv:

            for idx in range(start, start + n):
                subpath = path.format(account=0, change=0, idx=idx)
                node = sv.derive_path(subpath, register=False)
                addr = chain.address(node, addr_fmt)
                addrs.append(addr)

                msg += "%s =>\n%s\n\n" % (subpath, addr)

                dis.progress_bar_show(idx / n)

            stash.blank_object(node)

        msg += "Press 9 to see next group, 7 to go back. X to quit."

        return msg, addrs
Ejemplo n.º 14
0
    def __init__(self, *args):
        super().__init__()

        from main import dis
        dis.fullscreen('Wait...')

        # this must set self.address and do other slow setup
        self.setup(*args)
Ejemplo n.º 15
0
        def make_msg():
            msg = ''
            if n > 1:
                if start == 0:
                    msg = "Press 1 to save to MicroSD."
                    if version.has_fatram and not ms_wallet:
                        msg += " 4 to view QR Codes."
                    msg += '\n\n'
                msg += "Addresses %d..%d:\n\n" % (start, start + n - 1)
            else:
                # single address, from deep path given by user
                msg += "Showing single address."
                if version.has_fatram:
                    msg += " Press 4 to view QR Codes."
                msg += '\n\n'

            addrs = []
            chain = chains.current_chain()

            dis.fullscreen('Wait...')

            if ms_wallet:
                # IMPORTANT safety feature: never show complete address
                # but show enough they can verify addrs shown elsewhere.
                # - makes a redeem script
                # - converts into addr
                # - assumes 0/0 is first address.
                for (i, paths, addr, script) in ms_wallet.yield_addresses(start, n):
                    if i == 0 and ms_wallet.N <= 4:
                        msg += '\n'.join(paths) + '\n =>\n'
                    else:
                        msg += '.../0/%d =>\n' % i

                    addrs.append(addr)
                    msg += truncate_address(addr) + '\n\n'
                    dis.progress_bar_show(i/n)

            else:
                # single-singer wallets

                with stash.SensitiveValues() as sv:

                    for idx in range(start, start + n):
                        deriv = path.format(account=self.account_num, change=0, idx=idx)
                        node = sv.derive_path(deriv, register=False)
                        addr = chain.address(node, addr_fmt)
                        addrs.append(addr)

                        msg += "%s =>\n%s\n\n" % (deriv, addr)

                        dis.progress_bar_show(idx/n)

                    stash.blank_object(node)

            if n > 1:
                msg += "Press 9 to see next group, 7 to go back. X to quit."

            return msg, addrs
Ejemplo n.º 16
0
async def choose_first_address(*a):
    # Choose from a truncated list of index 0 common addresses, remember
    # the last address the user selected and use it as the default
    from main import settings, dis
    chain = chains.current_chain()

    dis.fullscreen('Wait...')

    with stash.SensitiveValues() as sv:

        def truncate_address(addr):
            # Truncates address to width of screen, replacing middle chars
            middle = "-"
            leftover = SCREEN_CHAR_WIDTH - len(middle)
            start = addr[0:(leftover + 1) // 2]
            end = addr[len(addr) - (leftover // 2):]
            return start + middle + end

        # Create list of choices (address_index_0, path, addr_fmt)
        choices = []
        for name, path, addr_fmt in chains.CommonDerivations:
            if '{coin_type}' in path:
                path = path.replace('{coin_type}', str(chain.b44_cointype))
            subpath = path.format(account=0, change=0, idx=0)
            node = sv.derive_path(subpath, register=False)
            address = chain.address(node, addr_fmt)
            choices.append((truncate_address(address), path, addr_fmt))

            dis.progress_bar_show(len(choices) / len(chains.CommonDerivations))

        stash.blank_object(node)

    picked = None

    async def clicked(_1, _2, item):
        if picked is None:
            picked = item.arg
        the_ux.pop()

    items = [
        MenuItem(address, f=clicked, arg=i)
        for i, (address, path, addr_fmt) in enumerate(choices)
    ]
    menu = MenuSystem(items)
    menu.goto_idx(settings.get('axi', 0))
    the_ux.push(menu)

    await menu.interact()

    if picked is None:
        return None

    # update last clicked address
    settings.put('axi', picked)
    address, path, addr_fmt = choices[picked]

    return (path, addr_fmt)
Ejemplo n.º 17
0
    def __init__(self, subpath, addr_fmt):
        super().__init__()
        self.subpath = subpath

        from main import dis
        dis.fullscreen('Wait...')

        with stash.SensitiveValues() as sv:
            node = sv.derive_path(subpath)
            self.address = sv.chain.address(node, addr_fmt)
Ejemplo n.º 18
0
async def make_summary_file(fname_pattern='public.txt'):
    from main import dis

    # record **public** values and helpful data into a text file
    dis.fullscreen('Generating...')

    # generator function:
    body = generate_public_contents()

    await write_text_file(fname_pattern, body, 'Summary')
Ejemplo n.º 19
0
async def initial_pin_setup(*a):
    # First time they select a PIN of any type.
    from login import LoginUX
    lll = LoginUX()
    pin = await lll.get_new_pin(
        'Choose PIN', '''\
Pick the main wallet's PIN code now. Be more clever, but an example:

123-4567

It has two parts: prefix (123-) and suffix (-4567). \
Each part must between 2 to 6 digits long. Total length \
can be as long as 12 digits.

The prefix part determines the anti-phishing words you will \
see each time you login.

Your new PIN protects access to \
this Coldcard device and is not a factor in the wallet's \
seed words or private keys.

THERE IS ABSOLUTELY NO WAY TO RECOVER A FORGOTTEN PIN! Write it down.
''')
    del lll

    if pin is None: return

    # A new pin is to be set!
    from main import pa, dis, settings, loop
    dis.fullscreen("Saving...")

    try:
        assert pa.is_blank()

        pa.change(new_pin=pin)

        # check it? kinda, but also get object into normal "logged in" state
        pa.setup(pin)
        ok = pa.login()
        assert ok

        # must re-read settings after login, because they are encrypted
        # with a key derived from the main secret.
        settings.set_key()
        settings.load()
    except Exception as e:
        print("Exception: %s" % e)

    # Allow USB protocol, now that we are auth'ed
    from usb import enable_usb
    enable_usb(loop, False)

    from menu import MenuSystem
    from flow import EmptyWallet
    return MenuSystem(EmptyWallet)
Ejemplo n.º 20
0
async def ux_dramatic_pause(msg, seconds):
    from main import dis

    # show a full-screen msg, with a dramatic pause + progress bar
    n = seconds * 8
    dis.fullscreen(msg)
    for i in range(n):
        dis.progress_bar_show(i / n)
        await sleep_ms(125)

    ux_clear_keys()
Ejemplo n.º 21
0
    def __init__(self, text, subpath, addr_fmt, approved_cb=None):
        super().__init__()
        self.text = text
        self.subpath = subpath
        self.approved_cb = approved_cb

        from main import dis
        dis.fullscreen('Wait...')

        with stash.SensitiveValues() as sv:
            node = sv.derive_path(subpath)
            self.address = sv.chain.address(node, addr_fmt)
Ejemplo n.º 22
0
    def wipe_most(self):
        # erase everything except settings: takes 5 seconds at least
        from nvstore import SLOTS
        end = SLOTS[0]

        from main import dis
        dis.fullscreen("Cleanup...")

        for addr in range(0, end, self.BLOCK_SIZE):
            self.block_erase(addr)
            dis.progress_bar_show(addr/end)

            while self.is_busy():
                pass
Ejemplo n.º 23
0
    async def render(self):
        # Choose from a truncated list of index 0 common addresses, remember
        # the last address the user selected and use it as the default
        from main import settings, dis
        chain = chains.current_chain()

        dis.fullscreen('Wait...')

        with stash.SensitiveValues() as sv:

            # Create list of choices (address_index_0, path, addr_fmt)
            choices = []
            for name, path, addr_fmt in chains.CommonDerivations:
                if '{coin_type}' in path:
                    path = path.replace('{coin_type}', str(chain.b44_cointype))

                if self.account_num != 0 and '{account}' not in path:
                    # skip derivations that are not affected by account number
                    continue

                deriv = path.format(account=self.account_num, change=0, idx=0)
                node = sv.derive_path(deriv, register=False)
                address = chain.address(node, addr_fmt)
                choices.append( (truncate_address(address), path, addr_fmt) )

                dis.progress_bar_show(len(choices) / len(chains.CommonDerivations))

            stash.blank_object(node)

        items = [MenuItem(address, f=self.pick_single, arg=(path, addr_fmt)) 
                        for i, (address, path, addr_fmt) in enumerate(choices)]

        # some other choices
        if self.account_num == 0:
            items.append(MenuItem("Account Number", f=self.change_account))
            items.append(MenuItem("Custom Path", menu=self.make_custom))

            # if they have MS wallets, add those next
            for ms in MultisigWallet.iter_wallets():
                if not ms.addr_fmt: continue
                items.append(MenuItem(ms.name, f=self.pick_multisig, arg=ms))
        else:
            items.append(MenuItem("Account: %d" % self.account_num, f=self.change_account))

        self.goto_idx(settings.get('axi', 0))      # weak

        self.replace_items(items)
Ejemplo n.º 24
0
def set_bip39_passphrase(pw):
    # apply bip39 passphrase for now (volatile)

    # takes a bit, so show something
    from main import dis
    dis.fullscreen("Working...")

    # set passphrase
    import stash
    stash.bip39_passphrase = pw

    # capture updated XFP
    with stash.SensitiveValues() as sv:
        # can't do it without original seed words (late, but caller has checked)
        assert sv.mode == 'words'

        sv.capture_xpub()
Ejemplo n.º 25
0
def set_bip39_passphrase(pw):
    # apply bip39 passphrase for now (volatile)
    # - return None or error msg
    import stash

    stash.bip39_passphrase = pw

    # takes a bit, so show something
    from main import dis
    dis.fullscreen("Working...")

    with stash.SensitiveValues() as sv:
        if sv.mode != 'words':
            # can't do it without original seed woods
            return 'No BIP39 seed words'

        sv.capture_xpub()
Ejemplo n.º 26
0
def clear_seed():
    from main import dis, pa, settings
    import utime

    dis.fullscreen('Clearing...')

    # clear settings associated with this key, since it will be no more
    settings.blank()

    # save a blank secret (all zeros is a special case, detected by bootloader)
    nv = bytes(AE_SECRET_LEN)
    pa.change(new_secret=nv)

    dis.fullscreen('Reboot...')
    utime.sleep(1)

    # security: need to reboot to really be sure to clear the secrets from main memory.
    from machine import reset
    reset()
Ejemplo n.º 27
0
async def ship_wo_bag(*a):
    # Factory command: for dev and test units that have no bag number, and never will.
    ok = await ux_confirm('''Not recommended! DO NOT USE for units going to paying customers.''')
    if not ok: return

    import callgate
    from main import dis, pa, is_devmode

    failed = callgate.set_bag_number(b'NOT BAGGED')      # 32 chars max

    if failed:
        await ux_dramatic_pause('FAILED', 30)
    else:
        # lock the bootrom firmware forever
        callgate.set_rdp_level(2 if not is_devmode else 0)

        # bag number affects green light status (as does RDP level)
        pa.greenlight_firmware()
        dis.fullscreen('No Bag. DONE')
        callgate.show_logout(1)
Ejemplo n.º 28
0
def sign_message_digest(digest, subpath, prompt):
    # do the signature itself!
    from main import dis

    if prompt:
        dis.fullscreen(prompt, percent=.25)

    with stash.SensitiveValues() as sv:
        dis.progress_bar_show(.50)

        node = sv.derive_path(subpath)
        pk = node.private_key()
        sv.register(pk)

        dis.progress_bar_show(.75)
        rv = tcc.secp256k1.sign(pk, digest)

    dis.progress_bar_show(1)

    return rv
Ejemplo n.º 29
0
    async def handle_upload(self, offset, total_size, data):
        from main import dis, sf

        # maintain a running SHA256 over what's received
        if offset == 0:
            self.file_checksum = tcc.sha256()

        assert offset % 256 == 0, 'alignment'
        assert offset + len(data) <= total_size <= MAX_UPLOAD_LEN, 'long'

        rb = bytearray(256)
        for pos in range(offset, offset + len(data), 256):
            if pos % 4096 == 0:
                # erase here
                sf.sector_erase(pos)

                dis.fullscreen("Receiving...")
                dis.progress_bar_show(offset / total_size)

                while sf.is_busy():
                    await sleep_ms(10)

            # write up to 256 bytes
            here = data[pos - offset:pos - offset + 256]
            sf.write(pos, here)

            # full page write: 0.6 to 3ms
            while sf.is_busy():
                await sleep_ms(1)

            # use actual read back for verify
            sf.read(pos, rb)
            self.file_checksum.update(rb[0:len(here)])

        if offset + len(data) >= total_size:
            # probably done
            dis.progress_bar_show(1.0)
            ux.restore_menu()

        return offset
Ejemplo n.º 30
0
async def make_address_summary_file(path,
                                    addr_fmt,
                                    fname_pattern='addresses.txt'):
    # write addresses into a text file on the MicroSD
    from main import dis
    from files import CardSlot, CardMissingError
    from actions import needs_microsd

    # simple: always set number of addresses.
    # - takes 60 seconds, to write 250 addresses on actual hardware
    count = 250

    dis.fullscreen('Saving 0-%d' % count)

    # generator function
    body = generate_address_csv(path, addr_fmt, count)

    # pick filename and write
    try:
        with CardSlot() as card:
            fname, nice = card.pick_filename(fname_pattern)

            # do actual write
            with open(fname, 'wb') as fd:
                for idx, part in enumerate(body):
                    fd.write(part.encode())

                    if idx % 5 == 0:
                        dis.progress_bar_show(idx / count)

    except CardMissingError:
        await needs_microsd()
        return
    except Exception as e:
        await ux_show_story('Failed to write!\n\n\n' + str(e))
        return

    msg = '''Address summary file written:\n\n%s''' % nice
    await ux_show_story(msg)