From 2bb6aee87e39612e857f0306e9b3549b59d409bb Mon Sep 17 00:00:00 2001 From: Jeff S Date: Thu, 31 Mar 2022 14:24:37 +0000 Subject: [PATCH] Hotfixes for release 22.03.0 - Less intrusive wdt - Add wdt feed calls to firmware update and printing - Reduce mem usage when printing - Update selfcustody.pem --- Dockerfile | 1 + selfcustody.pem | 4 ++-- src/krux/camera.py | 6 +++--- src/krux/context.py | 8 ++------ src/krux/firmware.py | 2 ++ src/krux/input.py | 16 ++++++---------- src/krux/metadata.py | 2 +- src/krux/printers/thermal.py | 22 ++++++++++++++-------- src/krux/wdt.py | 27 +++++++++++++++++++++++++++ tests/pages/test_page.py | 2 +- tests/printers/test_thermal.py | 2 ++ tests/test_camera.py | 31 ++++++++++++------------------- tests/test_input.py | 21 ++++++++++----------- 13 files changed, 83 insertions(+), 61 deletions(-) create mode 100644 src/krux/wdt.py diff --git a/Dockerfile b/Dockerfile index 286571d33..7e7473f5b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -82,6 +82,7 @@ RUN mkdir build && \ cp -r vendor/embit/src/embit build && \ rm -rf build/embit/util/prebuilt && \ rm -f build/embit/util/ctypes_secp256k1.py && \ + rm -f build/embit/util/py_secp256k1.py && \ cp -r vendor/urtypes/src/urtypes build && \ cp -r vendor/foundation-ur-py/src/ur build && \ cp -r firmware/MaixPy/projects/"${DEVICE}"/builtin_py/. build && \ diff --git a/selfcustody.pem b/selfcustody.pem index ec4635deb..19f1650a8 100644 --- a/selfcustody.pem +++ b/selfcustody.pem @@ -1,4 +1,4 @@ -----BEGIN PUBLIC KEY----- -MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgADMc0I9lfvPOoC4/CGh95q/0mTIsNiIpQH -ZbKrI8f28KU= +MDYwEAYHKoZIzj0CAQYFK4EEAAoDIgADM56IMVfkWJHmHKnfTNO7iV7zLUdbjnk1 +WeoQo2dmaJs= -----END PUBLIC KEY----- diff --git a/src/krux/camera.py b/src/krux/camera.py index 5e0c9041e..08e5ed4fe 100644 --- a/src/krux/camera.py +++ b/src/krux/camera.py @@ -23,13 +23,13 @@ import sensor import lcd from .qr import QRPartParser +from .wdt import wdt class Camera: """Camera is a singleton interface for interacting with the device's camera""" - def __init__(self, ctx): - self.ctx = ctx + def __init__(self): self.initialize_sensor() def initialize_sensor(self): @@ -52,7 +52,7 @@ def capture_qr_code_loop(self, callback): prev_parsed_count = 0 new_part = False while True: - self.ctx.wdt.feed() + wdt.feed() stop = callback(parser.total_count(), parser.parsed_count(), new_part) if stop: break diff --git a/src/krux/context.py b/src/krux/context.py index 73e97a48b..a5e20fb55 100644 --- a/src/krux/context.py +++ b/src/krux/context.py @@ -21,7 +21,6 @@ # THE SOFTWARE. import gc import board -import machine from .logging import Logger from .settings import settings from .display import Display @@ -29,8 +28,6 @@ from .camera import Camera from .light import Light -RESET_TIMEOUT = 30000 - class Context: """Context is a singleton containing all 'global' state that lives throughout the @@ -38,11 +35,10 @@ class Context: """ def __init__(self): - self.wdt = machine.WDT(timeout=RESET_TIMEOUT) self.log = Logger(settings.log.path, settings.log.level) self.display = Display() - self.input = Input(self) - self.camera = Camera(self) + self.input = Input() + self.camera = Camera() self.light = Light() if "LED_W" in board.config["krux.pins"] else None self.printer = None self.wallet = None diff --git a/src/krux/firmware.py b/src/krux/firmware.py index 8063ad945..087a3ff92 100644 --- a/src/krux/firmware.py +++ b/src/krux/firmware.py @@ -30,6 +30,7 @@ from .metadata import SIGNER_PUBKEY from .display import Display from .i18n import t +from .wdt import wdt MAX_FIRMWARE_SIZE = 0x300000 @@ -77,6 +78,7 @@ def write_data( total_read = 0 read_attempts = 0 while True: + wdt.feed() if sha_suffix is None: pct_cb(total_read / data_size) if total_read == data_size: diff --git a/src/krux/input.py b/src/krux/input.py index 6fd1f644a..5f4f3222f 100644 --- a/src/krux/input.py +++ b/src/krux/input.py @@ -22,6 +22,7 @@ import board from Maix import GPIO from fpioa_manager import fm +from .wdt import wdt BUTTON_ENTER = 0 BUTTON_PAGE = 1 @@ -35,8 +36,7 @@ class Input: """Input is a singleton interface for interacting with the device's buttons""" - def __init__(self, ctx=None): - self.ctx = ctx + def __init__(self): self.entropy = 0 fm.register(board.config["krux.pins"]["BUTTON_A"], fm.fpioa.GPIOHS21) self.enter = GPIO(GPIO.GPIOHS21, GPIO.IN, GPIO.PULL_UP) @@ -49,15 +49,13 @@ def wait_for_button(self, block=True): """ # Loop until all buttons are released (if currently pressed) while self.enter.value() == PRESSED or self.page.value() == PRESSED: - if self.ctx is not None: - self.ctx.wdt.feed() + wdt.feed() self.entropy += 1 # Wait for first button press checks = 0 while self.enter.value() == RELEASED and self.page.value() == RELEASED: - if self.ctx is not None: - self.ctx.wdt.feed() + wdt.feed() checks += 1 if not block and checks > NONBLOCKING_CHECKS: break @@ -65,16 +63,14 @@ def wait_for_button(self, block=True): if self.enter.value() == PRESSED: # Wait for release while self.enter.value() == PRESSED: - if self.ctx is not None: - self.ctx.wdt.feed() + wdt.feed() self.entropy += 1 return BUTTON_ENTER if self.page.value() == PRESSED: # Wait for release while self.page.value() == PRESSED: - if self.ctx is not None: - self.ctx.wdt.feed() + wdt.feed() self.entropy += 1 return BUTTON_PAGE return None diff --git a/src/krux/metadata.py b/src/krux/metadata.py index a4b555b24..095eaea33 100644 --- a/src/krux/metadata.py +++ b/src/krux/metadata.py @@ -20,4 +20,4 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. VERSION = "22.03.0" -SIGNER_PUBKEY = "0331cd08f657ef3cea02e3f08687de6aff499322c36222940765b2ab23c7f6f0a5" +SIGNER_PUBKEY = "03339e883157e45891e61ca9df4cd3bb895ef32d475b8e793559ea10a36766689b" diff --git a/src/krux/printers/thermal.py b/src/krux/printers/thermal.py index ca092df47..b4970fe5a 100644 --- a/src/krux/printers/thermal.py +++ b/src/krux/printers/thermal.py @@ -41,6 +41,7 @@ from fpioa_manager import fm from machine import UART from ..settings import settings +from ..wdt import wdt from . import Printer @@ -132,6 +133,7 @@ def setup(self): def write_bytes(self, *args): """Writes bytes to the printer at a stable speed""" for arg in args: + wdt.feed() self.uart_conn.write(arg if isinstance(arg, bytes) else bytes([arg])) # Calculate time to issue one byte to the printer. # 11 bits (not 8) to accommodate idle, start and @@ -179,16 +181,20 @@ def clear(self): def print_qr_code(self, qr_code): """Prints a QR code, scaling it up as large as possible""" - lines = qr_code.strip().split("\n") + size = 0 + while qr_code[size] != "\n": + size += 1 - width = len(lines) - height = len(lines) - - scale = settings.printer.thermal.paper_width // width - for y in range(height): + scale = settings.printer.thermal.paper_width // size + for y in range(size): # Scale the line (width) by scaling factor - line_y = "".join([char * scale for char in lines[y]]) - line_bytes = int(line_y, 2).to_bytes((len(line_y) + 7) // 8, "big") + line = 0 + for char in qr_code[y * (size + 1) : y * (size + 1) + size]: + bit = int(char) + for _ in range(scale): + line <<= 1 + line |= bit + line_bytes = line.to_bytes((size * scale + 7) // 8, "big") # Print height * scale lines out to scale by for _ in range(scale): self.write_bytes(18, 42, 1, len(line_bytes)) diff --git a/src/krux/wdt.py b/src/krux/wdt.py new file mode 100644 index 000000000..cd0590e18 --- /dev/null +++ b/src/krux/wdt.py @@ -0,0 +1,27 @@ +# The MIT License (MIT) + +# Copyright (c) 2021 Tom J. Sun + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in +# all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +# THE SOFTWARE. +import machine + +RESET_TIMEOUT = 30000 + +# Create a watchdog timer that resets the device if not fed for 30s +wdt = machine.WDT(timeout=RESET_TIMEOUT) diff --git a/tests/pages/test_page.py b/tests/pages/test_page.py index 34b1f8199..f90d939fd 100644 --- a/tests/pages/test_page.py +++ b/tests/pages/test_page.py @@ -36,8 +36,8 @@ def test_capture_qr_code(mocker): input=mock.MagicMock( wait_for_button=mock.MagicMock(side_effect=[BUTTON_ENTER, BUTTON_PAGE]) ), + camera=Camera(), ) - ctx.camera = Camera(ctx) page = MockPage(ctx) diff --git a/tests/printers/test_thermal.py b/tests/printers/test_thermal.py index b9fe6d667..2de23674e 100644 --- a/tests/printers/test_thermal.py +++ b/tests/printers/test_thermal.py @@ -86,6 +86,7 @@ def test_clear(mocker): def test_print_qr_code(mocker): mocker.patch("krux.printers.thermal.UART", new=MockUART) + import krux from krux.printers.thermal import AdafruitPrinter p = AdafruitPrinter() @@ -96,3 +97,4 @@ def test_print_qr_code(mocker): assert p.write_bytes.call_count == 701 p.feed.assert_called_once() + krux.printers.thermal.wdt.feed.assert_called() diff --git a/tests/test_camera.py b/tests/test_camera.py index 8b0ce10c8..9a2410a1c 100644 --- a/tests/test_camera.py +++ b/tests/test_camera.py @@ -5,8 +5,7 @@ def test_init(mocker): from krux.camera import Camera spy = mocker.spy(Camera, "initialize_sensor") - ctx = mock.MagicMock(wdt=mock.MagicMock()) - c = Camera(ctx) + c = Camera() assert isinstance(c, Camera) spy.assert_called() @@ -16,8 +15,7 @@ def test_initialize_sensor(): import krux from krux.camera import Camera - ctx = mock.MagicMock(wdt=mock.MagicMock()) - c = Camera(ctx) + c = Camera() c.initialize_sensor() @@ -42,8 +40,7 @@ def test_capture_qr_code_loop(mocker): import krux from krux.camera import Camera - ctx = mock.MagicMock(wdt=mock.MagicMock()) - c = Camera(ctx) + c = Camera() prev_parsed_count = -1 @@ -63,7 +60,7 @@ def progress_callback(total_count, parsed_count, is_new): assert format == MockQRPartParser.FORMAT assert prev_parsed_count == MockQRPartParser.TOTAL - 1 krux.camera.sensor.run.assert_called_with(0) - ctx.wdt.feed.assert_called() + krux.camera.wdt.feed.assert_called() def test_capture_qr_code_loop_returns_early_when_requested(mocker): @@ -74,8 +71,7 @@ def test_capture_qr_code_loop_returns_early_when_requested(mocker): import krux from krux.camera import Camera - ctx = mock.MagicMock(wdt=mock.MagicMock()) - c = Camera(ctx) + c = Camera() prev_parsed_count = -1 @@ -95,7 +91,7 @@ def progress_callback(total_count, parsed_count, is_new): assert format is None assert prev_parsed_count < MockQRPartParser.TOTAL - 1 krux.camera.sensor.run.assert_called_with(0) - ctx.wdt.feed.assert_called() + krux.camera.wdt.feed.assert_called() def test_capture_qr_code_loop_skips_bad_histogram(mocker): @@ -107,8 +103,7 @@ def test_capture_qr_code_loop_skips_bad_histogram(mocker): import krux from krux.camera import Camera - ctx = mock.MagicMock(wdt=mock.MagicMock()) - c = Camera(ctx) + c = Camera() prev_parsed_count = -1 @@ -131,7 +126,7 @@ def progress_callback(total_count, parsed_count, is_new): assert format == MockQRPartParser.FORMAT assert prev_parsed_count == MockQRPartParser.TOTAL - 1 krux.camera.sensor.run.assert_called_with(0) - ctx.wdt.feed.assert_called() + krux.camera.wdt.feed.assert_called() def test_capture_qr_code_loop_skips_missing_qrcode(mocker): @@ -143,8 +138,7 @@ def test_capture_qr_code_loop_skips_missing_qrcode(mocker): import krux from krux.camera import Camera - ctx = mock.MagicMock(wdt=mock.MagicMock()) - c = Camera(ctx) + c = Camera() prev_parsed_count = -1 @@ -167,7 +161,7 @@ def progress_callback(total_count, parsed_count, is_new): assert format == MockQRPartParser.FORMAT assert prev_parsed_count == MockQRPartParser.TOTAL - 1 krux.camera.sensor.run.assert_called_with(0) - ctx.wdt.feed.assert_called() + krux.camera.wdt.feed.assert_called() def test_capture_qr_code_loop_skips_duplicate_qrcode(mocker): @@ -179,8 +173,7 @@ def test_capture_qr_code_loop_skips_duplicate_qrcode(mocker): import krux from krux.camera import Camera - ctx = mock.MagicMock(wdt=mock.MagicMock()) - c = Camera(ctx) + c = Camera() prev_parsed_count = -1 @@ -203,4 +196,4 @@ def progress_callback(total_count, parsed_count, is_new): assert format == MockQRPartParser.FORMAT assert prev_parsed_count == MockQRPartParser.TOTAL - 1 krux.camera.sensor.run.assert_called_with(0) - ctx.wdt.feed.assert_called() + krux.camera.wdt.feed.assert_called() diff --git a/tests/test_input.py b/tests/test_input.py index c6e1bdcd6..2a2b95ba1 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -14,8 +14,7 @@ def test_init(mocker): from krux.input import Input import board - ctx = mock.MagicMock(wdt=mock.MagicMock()) - input = Input(ctx) + input = Input() assert isinstance(input, Input) krux.input.fm.register.assert_has_calls( @@ -47,10 +46,10 @@ def test_init(mocker): def test_wait_for_button_blocks_until_enter_released(mocker): mock_modules(mocker) + import krux from krux.input import Input - ctx = mock.MagicMock(wdt=mock.MagicMock()) - input = Input(ctx) + input = Input() mocker.patch.object(input.enter, "value", new=lambda: RELEASED) mocker.patch.object(input.page, "value", new=lambda: RELEASED) @@ -69,7 +68,7 @@ def release(): assert btn == BUTTON_ENTER assert input.entropy > 0 - ctx.wdt.feed.assert_called() + krux.input.wdt.feed.assert_called() # def test_wait_for_button_blocks_until_page_released(mocker): @@ -97,10 +96,10 @@ def release(): def test_wait_for_button_waits_for_existing_press_to_release(mocker): mock_modules(mocker) + import krux from krux.input import Input - ctx = mock.MagicMock(wdt=mock.MagicMock()) - input = Input(ctx) + input = Input() mocker.patch.object(input.enter, "value", new=lambda: PRESSED) def release(): @@ -120,19 +119,19 @@ def release(): assert btn == BUTTON_ENTER assert input.entropy > 0 - ctx.wdt.feed.assert_called() + krux.input.wdt.feed.assert_called() def test_wait_for_button_returns_when_nonblocking(mocker): mock_modules(mocker) + import krux from krux.input import Input - ctx = mock.MagicMock(wdt=mock.MagicMock()) - input = Input(ctx) + input = Input() mocker.patch.object(input.enter, "value", new=lambda: RELEASED) mocker.patch.object(input.page, "value", new=lambda: RELEASED) btn = input.wait_for_button(False) assert btn is None - ctx.wdt.feed.assert_called() + krux.input.wdt.feed.assert_called()