Compare commits

..

No commits in common. "3a55cad86fd41bd6009e0a903546ba81ccf597aa" and "449812f1dfca7e5c404c8c14f1a2191646adf034" have entirely different histories.

5 changed files with 11 additions and 113 deletions

View File

@ -214,15 +214,10 @@ def write_new_config_to_filename(config: Message, filename: str, inject: bool =
with open(filename, 'rb') as file:
existing_binary = file.read()
binary = replace_config_in_binary(bytearray(existing_binary), config_binary)
with open(filename, 'wb') as file:
file.write(binary)
else:
binary = serialize_config_with_footer(config)
with open(filename, 'wb') as file:
if filename[-4:] == '.uf2':
file.write(convert_binary_to_uf2(pad_config_to_storage_size(binary),
start=USER_CONFIG_BINARY_LOCATION))
else:
file.write(binary)

View File

@ -10,7 +10,7 @@ Button {
margin: 0 1;
}
EditScreen, MessageScreen, SaveAsScreen {
EditScreen, MessageScreen {
align: center middle;
}
@ -30,7 +30,7 @@ EditScreen Label {
visibility: hidden;
}
#edit-dialog, #save-as-dialog {
#edit-dialog {
padding: 0 1;
grid-rows: 1fr 1fr 1fr 1fr;
width: 50%;

View File

@ -16,7 +16,7 @@ from textual.app import App, ComposeResult
from textual.containers import Container, Grid, Horizontal
from textual.logging import TextualHandler
from textual.screen import ModalScreen
from textual.validation import Length, Number
from textual.validation import Number
from textual.widgets import Button, Footer, Header, Input, Label, Pretty, Select, TextArea, Tree
from textual.widgets.tree import TreeNode
@ -125,61 +125,11 @@ class MessageScreen(ModalScreen):
self.app.pop_screen()
class SaveAsScreen(ModalScreen):
"""Present the option of saving the configuration as a new file."""
def __init__(self, config, *args, **kwargs):
"""Initialize a filename argument to be populated."""
self.config = config
super().__init__(*args, **kwargs)
def compose(self) -> ComposeResult:
"""Build the pop-up window prompting for the new filename to save the configuration as."""
self.filename_field = Input(value=None, id='field-input', validators=[Length(minimum=1)])
yield Grid(
Container(Label("Filename (.uf2 or .bin) to write to:", id='field-name'), id='field-name-container'),
Container(self.filename_field, id='input-field-container'),
Container(Pretty('', id='input-errors', classes='hidden'), id='error-container'),
Horizontal(Container(Button("Cancel", id='cancel-button'), id='cancel-button-container'),
Container(Button("Confirm", id='confirm-button'), id='confirm-button-container'),
id='button-container'),
id='save-as-dialog',
)
@on(Input.Changed)
def show_invalid_reasons(self, event: Input.Changed) -> None:
"""Update the UI to show why validation failed."""
if event.validation_result:
error_field = self.query_one(Pretty)
save_button = self.query_one('#confirm-button', Button)
if not event.validation_result.is_valid:
error_field.update(event.validation_result.failure_descriptions)
error_field.classes = ''
save_button.disabled = True
else:
error_field.update('')
error_field.classes = 'hidden'
save_button.disabled = False
def on_button_pressed(self, event: Button.Pressed) -> None:
"""Process the button actions."""
if event.button.id == 'confirm-button':
logger.debug("calling _save")
self._save()
self.app.pop_screen()
def _save(self):
"""Save the configuration to the specified file."""
write_new_config_to_filename(self.config, self.filename_field.value, inject=False)
self.notify(f"Saved to {self.filename_field.value}.", title="Configuration Saved")
class ConfigEditor(App):
"""Display the GP2040-CE configuration as a tree."""
BINDINGS = [
('a', 'save_as', "Save As..."),
('n', 'add_node', "Add Node"),
('a', 'add_node', "Add Node"),
('s', 'save', "Save Config"),
('q', 'quit', "Quit"),
('?', 'about', "About"),
@ -288,10 +238,6 @@ class ConfigEditor(App):
self.notify(f"Saved to {self.config_filename}.",
title="Configuration Saved")
def action_save_as(self) -> None:
"""Present a new dialog to save the configuration as a new standalone file."""
self.push_screen(SaveAsScreen(self.config))
def action_quit(self) -> None:
"""Quit the application."""
self.exit()
@ -463,9 +409,8 @@ def edit_config():
group = parser.add_mutually_exclusive_group(required=True)
group.add_argument('--usb', action='store_true', help="retrieve the config from a RP2040 board connected over USB "
"and in BOOTSEL mode")
group.add_argument('--filename', help=".bin of a GP2040-CE's whole board dump if --whole-board is specified, or a"
".bin file of a GP2040-CE board's config + footer or entire storage section; "
"if creating a new config, it can also be written in .uf2 format")
group.add_argument('--filename', help=".bin file of a GP2040-CE board's config + footer or entire storage section, "
"or of a GP2040-CE's whole board dump if --whole-board is specified")
args, _ = parser.parse_known_args()
if args.usb:

View File

@ -15,7 +15,7 @@ from gp2040ce_bintools.builder import (FirmwareLengthError, combine_firmware_and
concatenate_firmware_and_storage_files, get_gp2040ce_from_usb,
pad_binary_up_to_board_config, pad_binary_up_to_user_config,
replace_config_in_binary, write_new_config_to_filename, write_new_config_to_usb)
from gp2040ce_bintools.storage import (STORAGE_SIZE, get_board_storage_section, get_config, get_config_footer,
from gp2040ce_bintools.storage import (get_board_storage_section, get_config, get_config_footer,
get_user_storage_section, serialize_config_with_footer)
HERE = os.path.dirname(os.path.abspath(__file__))
@ -272,23 +272,6 @@ def test_write_new_config_to_config_bin(firmware_binary, tmp_path):
assert len(config_dump) == config_size + 12
@with_pb2s
def test_write_new_config_to_config_uf2(firmware_binary, tmp_path):
"""Test that the config can be written to a file."""
tmp_file = os.path.join(tmp_path, 'config.uf2')
config_pb2 = get_config_pb2()
config = config_pb2.Config()
config.boardVersion = 'v0.7.5-COOL'
write_new_config_to_filename(config, tmp_file)
# read new file
with open(tmp_file, 'rb') as file:
config_dump = file.read()
# the current implementation of UF2 writing does it in 256 blocks, so each 256 byte block of
# binary is 512 bytes in the UF2
assert len(config_dump) == STORAGE_SIZE * 2
@with_pb2s
def test_write_new_config_to_usb(config_binary):
"""Test that the config can be written to USB at the proper alignment."""

View File

@ -183,7 +183,7 @@ async def test_add_node_to_repeated():
tree.root.expand_all()
await pilot.wait_for_scheduled_animations()
tree.select_node(altpinmappings_node)
await pilot.press('n')
await pilot.press('a')
newpinmappings_node = altpinmappings_node.children[0]
newpinmappings_node.expand()
await pilot.wait_for_scheduled_animations()
@ -216,28 +216,3 @@ async def test_save(config_binary, tmp_path):
config = get_config_from_file(new_filename)
assert config.boardVersion == 'v0.7.5-bss-wuz-here'
@pytest.mark.asyncio
@with_pb2s
async def test_save_as(config_binary, tmp_path):
"""Test that we can save to a new file."""
filename = os.path.join(tmp_path, 'config-original.bin')
with open(filename, 'wb') as file:
file.write(config_binary)
original_config = get_config(config_binary)
app = ConfigEditor(config_filename=filename)
async with app.run_test() as pilot:
await pilot.press('a')
await pilot.wait_for_scheduled_animations()
await pilot.click('Input#field-input')
await pilot.wait_for_scheduled_animations()
await pilot.press('/', 't', 'm', 'p', '/', 'g', 'p', 't', 'e', 's', 't')
await pilot.wait_for_scheduled_animations()
await pilot.click('Button#confirm-button')
with open('/tmp/gptest', 'rb') as new_file:
test_config_binary = new_file.read()
test_config = get_config(test_config_binary)
assert original_config == test_config