firestarter 3.2.7b1__tar.gz → 3.2.7b3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {firestarter-3.2.7b1/firestarter.egg-info → firestarter-3.2.7b3}/PKG-INFO +1 -1
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/_version.py +3 -3
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/channel.py +8 -15
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/cli_handlers.py +45 -29
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/eprom_operations.py +41 -8
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/exceptions.py +3 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/firmware.py +85 -14
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/fw_release_gate.py +80 -4
- {firestarter-3.2.7b1 → firestarter-3.2.7b3/firestarter.egg-info}/PKG-INFO +1 -1
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter.egg-info/scm_version.json +2 -2
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/__snapshots__/test_characterization.ambr +5 -8
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_characterization.py +33 -16
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_cli_handlers.py +8 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_eprom_operations.py +68 -2
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_firmware_install.py +7 -5
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_fw_port_targeting_and_blind_install.py +9 -2
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_fw_release_gate.py +249 -1
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_fw_update_dead_endpoint.py +2 -2
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_fw_update_path_gate.py +8 -1
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_py32_channel_gating.py +51 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_py32_dfu.py +7 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/LICENSE +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/MANIFEST.in +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/README.md +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/__init__.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/address_parser.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/avr_tool.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/chip_resolver.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/chip_test.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/codec.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/compare.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/config.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/constants.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/data/chip_database.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/data/pinouts.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/database.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/diagnostic_report.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/dip24_2816_erase_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/eprom_info.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/erase_support.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/flash4_erase_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/frame_parser.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/hardware.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/ic_layout.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/jp5_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/jumper_table.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/lock_status.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/log_capture.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/logging_utils.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/main.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/messages.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/page_size_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/protection_readability.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/py32_dfu.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/sdp_capability.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/sdp_honesty.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/serial_comm.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/submit.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/timestamps.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/transport_counters.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/utils.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/vpp_display.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter/write_blank_guard.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter.egg-info/SOURCES.txt +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter.egg-info/dependency_links.txt +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter.egg-info/entry_points.txt +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter.egg-info/requires.txt +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter.egg-info/scm_file_list.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/firestarter.egg-info/top_level.txt +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/pyproject.toml +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/setup.cfg +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/__init__.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/conftest.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fake_chip.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/part_number_delta.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/report_shapes.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/at28c256-full-all-ok-sdp.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/attr01-status-axis-transport-fault.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/gh20-at28c256-fail.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/gh23-w27e257-fail.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/gh28-m27c512-fail.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/gh47-sst27sf512-pass.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/m27c512-full-all-ok.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/m27c512-full-blank-check-bad.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/m27c512-full-canonical-name.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/m27c512-full-comma-joined-name.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/m27c512-full-runs-1.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/prune03-synthesized-fingerprint-match.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/sst27sf512-full-all-ok.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/sst27sf512-six-step-readback-gated.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/sst27sf512-six-step.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/synthetic-arm4-empty-results.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/synthetic-arm4-no-ok.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/uv-slot-write-pass.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/reports/w27e257-full-all-ok.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/shape_ids.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/fixtures/synthetic_nonzero_chip_id.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/golden/build_db_part_specific_constants.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/golden/chip_database_field_inventory.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/golden/wire_dict.json +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/plan_corpus.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_address_parser.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_b15_page_size_corroboration.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_blast_radius_invariance.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_boot_block_hint.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_budget_failure_render.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_bug_characterization.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_build_db_constant_census.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_build_db_inclusion.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_build_db_interpret_timing.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_build_db_pinout_fork.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_build_db_sram_non_volatile.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_canonical_part_number.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_chip_database_field_inventory.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_chip_name_resolution.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_chip_resolver.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_chip_test.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_chip_test_blank_check_order.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_chip_test_cycle.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_chip_test_sdp_leg.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_chip_test_timing.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_chip_test_uv_slot_write.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_click_group_gate_hook.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_cobs.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_codec.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_codec_format_message.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_compare.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_config.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_config_band_refusal.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_connect_cost_harness.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_consistency_check.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_coverage_floor_v18.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_database_conversion.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_datasheet_overrides.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_decoder.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_derive_plan_no_drop_sweep.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_derive_plan_structural_sentinel.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_dev_group_channel_gating.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_dev_test_cmd.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_dev_tools_channel_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_devtest_firmware_error_propagation.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_dfu_opcode_anchors.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_diagnostic_report.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_dip24_2816_erase_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_endpoint_constants.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_eprom_database.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_eprom_info.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_erase_blank_step_nonregression.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_erase_flag_invariants.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_error_code_seam.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_even_block.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_extra_chips_supplement.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_flash4_erase_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_fw_list_failure_vs_empty.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_fw_version_guard.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_fwguard.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_hardware.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_hw_revision_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_ic_layout.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_info_vpp_and_erase.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_jp5_gate.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_jumper_table.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_lock_status_class_partition.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_lock_status_cli.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_lock_status_wire.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_log_capture.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_logging_utils.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_matrix_artifact.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_page_size_alignment_refusal.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_page_size_invariants.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_page_size_write_refusal.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_parse_devtest_issue.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_probe_spurious_setup_ack.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_programming_vcc_census.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_protect_flags_doc_measurements.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_protection_resolution.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_protection_status_catalog.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_protection_table_citations.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_protocol_not_implemented.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_protocol_not_implemented_production_path.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_provenance.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_pulse_us_override.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_py32_flash_map_host.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_py32_pyusb_absent.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_pyusb_api_surface.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_sdp_capability.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_sdp_db_invariant.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_sdp_honesty.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_serial_characterization.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_serial_comm.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_session_lease.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_setup_refusals.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_submit.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_timestamps.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_transport_counters.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_utils.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_uv_mask.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_validate_family_cmd.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_validate_oracle.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_validated_parts_regression_surface.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_vcc_margin_rail.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_vpp_decode_table.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_vpp_rail_classification.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_wire_dict_equivalence.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_write_blank_guard.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_write_blank_guard_pinning.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_write_guard_port_pinning.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_write_progress.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_write_response_budget.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_write_skip_erase_0x0d.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_write_skip_sdp_unlock.py +0 -0
- {firestarter-3.2.7b1 → firestarter-3.2.7b3}/tests/test_write_verify.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firestarter
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.7b3
|
|
4
4
|
Summary: EPROM programmer software for the Relatively Universal ROM Programmer
|
|
5
5
|
Author-email: Henrik Olsson <henols@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/henols/firestarter_app
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '3.2.
|
|
22
|
-
__version_tuple__ = version_tuple = (3, 2, 7, '
|
|
21
|
+
__version__ = version = '3.2.7b3'
|
|
22
|
+
__version_tuple__ = version_tuple = (3, 2, 7, 'b3')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g85b7152'
|
|
@@ -59,23 +59,16 @@ def is_prerelease_build() -> bool:
|
|
|
59
59
|
"""True when this build came off `beta` (its own version is a pre-release).
|
|
60
60
|
|
|
61
61
|
Fails **closed**: an unparseable version is treated as stable, so a gated
|
|
62
|
-
feature stays hidden rather than leaking.
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
feature stays hidden rather than leaking. `_maybe_auto_route_to_pre` uses
|
|
63
|
+
this function, so it treats an unparseable version the same way. In
|
|
64
|
+
practice `__version__` is a literal in the package and is always valid
|
|
65
|
+
PEP 440; dev versions (`2.0.7_dev`) parse as pre-releases and therefore
|
|
66
|
+
keep gated features enabled while working from a checkout.
|
|
67
67
|
"""
|
|
68
|
-
|
|
69
|
-
|
|
68
|
+
import firestarter as _pkg
|
|
69
|
+
from firestarter.fw_release_gate import is_prerelease_version
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
try:
|
|
74
|
-
return bool(Version(_pkg.__version__).is_prerelease)
|
|
75
|
-
except InvalidVersion:
|
|
76
|
-
return False
|
|
77
|
-
except ImportError: # pragma: no cover — packaging is a hard dependency
|
|
78
|
-
return False
|
|
71
|
+
return is_prerelease_version(_pkg.__version__) is True
|
|
79
72
|
|
|
80
73
|
|
|
81
74
|
def is_board_available(board: str | None) -> bool:
|
|
@@ -37,6 +37,7 @@ from firestarter.channel import (
|
|
|
37
37
|
available_boards,
|
|
38
38
|
dev_command_gate_message,
|
|
39
39
|
is_dev_tools_enabled,
|
|
40
|
+
is_prerelease_build,
|
|
40
41
|
)
|
|
41
42
|
from firestarter.chip_resolver import (
|
|
42
43
|
describe_ambiguity,
|
|
@@ -186,6 +187,27 @@ def _complete_eprom(
|
|
|
186
187
|
_ALL_BOARDS: tuple[str, ...] = ("uno", "uno328pb", "leonardo", "py32f071")
|
|
187
188
|
_BOARD_CHOICES: list[str] = available_boards(_ALL_BOARDS)
|
|
188
189
|
_PY32_ENABLED: bool = "py32f071" in _BOARD_CHOICES
|
|
190
|
+
# A stable build never offers pre-release firmware: `fw --pre` is refused and
|
|
191
|
+
# `fw --list` shows stable releases only. fw_release_gate refuses the install.
|
|
192
|
+
_PRE_CHANNEL_ENABLED: bool = is_prerelease_build()
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _reject_gated_option(name: str, given: bool, enabled: bool) -> None:
|
|
196
|
+
"""The one refusal for a CLI option that this build's channel does not offer.
|
|
197
|
+
|
|
198
|
+
It reads like Click's own unknown-option error, because on this build the
|
|
199
|
+
option does not exist.
|
|
200
|
+
"""
|
|
201
|
+
if given and not enabled:
|
|
202
|
+
raise click.UsageError(f"no such option: {name}")
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def _reject_pre_only_option(name: str, given: bool) -> None:
|
|
206
|
+
"""Refuse a pre-release-only CLI option on a stable build.
|
|
207
|
+
|
|
208
|
+
Reads `_PRE_CHANNEL_ENABLED` at CALL time, like `_reject_py32_only_option`.
|
|
209
|
+
"""
|
|
210
|
+
_reject_gated_option(name, given, _PRE_CHANNEL_ENABLED)
|
|
189
211
|
|
|
190
212
|
|
|
191
213
|
def _reject_py32_only_option(name: str, given: bool) -> None:
|
|
@@ -204,8 +226,7 @@ def _reject_py32_only_option(name: str, given: bool) -> None:
|
|
|
204
226
|
default -- which is what makes it monkeypatchable while the Click surface
|
|
205
227
|
stays frozen at import time.
|
|
206
228
|
"""
|
|
207
|
-
|
|
208
|
-
raise click.UsageError(f"no such option: {name}")
|
|
229
|
+
_reject_gated_option(name, given, _PY32_ENABLED)
|
|
209
230
|
|
|
210
231
|
|
|
211
232
|
def map_typed_errors(f: Callable[..., Any]) -> Callable[..., Any]:
|
|
@@ -322,29 +343,15 @@ def _maybe_auto_route_to_pre(args: object) -> None:
|
|
|
322
343
|
or getattr(args, "stable", False)
|
|
323
344
|
):
|
|
324
345
|
return
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
"Beta app detected — defaulting to --pre. "
|
|
335
|
-
"Use --firmware-version X.Y.Z to pin a stable version."
|
|
336
|
-
)
|
|
337
|
-
except InvalidVersion:
|
|
338
|
-
# Deliberate fail-OPEN, and deliberately the opposite polarity to
|
|
339
|
-
# fw_release_gate, which fails CLOSED on the same unreadable input.
|
|
340
|
-
# The asymmetry is intended: the worst case here is that the wrong
|
|
341
|
-
# channel is picked, which the gate then judges on its merits; the
|
|
342
|
-
# worst case there is a board flashed with firmware this host cannot
|
|
343
|
-
# drive, recoverable only through the bootloader. Do not "align"
|
|
344
|
-
# these two without re-reading both worst cases.
|
|
345
|
-
pass
|
|
346
|
-
except ImportError:
|
|
347
|
-
pass
|
|
346
|
+
# An unreadable version is not a pre-release here: the worst case is the
|
|
347
|
+
# wrong channel, which fw_release_gate then judges and refuses (it fails
|
|
348
|
+
# CLOSED on the same input). Do not "align" the two polarities.
|
|
349
|
+
if is_prerelease_build():
|
|
350
|
+
args.pre = True # type: ignore[attr-defined]
|
|
351
|
+
helper_logger.info(
|
|
352
|
+
"Beta app detected — defaulting to --pre. "
|
|
353
|
+
"Use --firmware-version X.Y.Z to pin a stable version."
|
|
354
|
+
)
|
|
348
355
|
|
|
349
356
|
|
|
350
357
|
def _build_op_flags(
|
|
@@ -1764,6 +1771,7 @@ def _maybe_auto_route_to_pre_click(
|
|
|
1764
1771
|
@click.option(
|
|
1765
1772
|
"--pre",
|
|
1766
1773
|
is_flag=True,
|
|
1774
|
+
hidden=not _PRE_CHANNEL_ENABLED,
|
|
1767
1775
|
help="Fetch latest pre-release firmware (mirrors pip install --pre).",
|
|
1768
1776
|
)
|
|
1769
1777
|
@click.option(
|
|
@@ -1772,7 +1780,11 @@ def _maybe_auto_route_to_pre_click(
|
|
|
1772
1780
|
type=_FirmwareVersionType(),
|
|
1773
1781
|
default=None,
|
|
1774
1782
|
metavar="VERSION",
|
|
1775
|
-
help=
|
|
1783
|
+
help=(
|
|
1784
|
+
"Pin exact firmware version (e.g. 3.1.0, 3.1.0b2, 3.1.0rc1)."
|
|
1785
|
+
if _PRE_CHANNEL_ENABLED
|
|
1786
|
+
else "Pin exact firmware version (e.g. 3.1.0)."
|
|
1787
|
+
),
|
|
1776
1788
|
)
|
|
1777
1789
|
@click.option(
|
|
1778
1790
|
"--stable",
|
|
@@ -1861,11 +1873,15 @@ def fw(
|
|
|
1861
1873
|
) -> None:
|
|
1862
1874
|
"""Firmware version.
|
|
1863
1875
|
|
|
1864
|
-
|
|
1865
|
-
|
|
1876
|
+
The release channel options are mutually exclusive; passing more than one
|
|
1877
|
+
is refused before anything is installed.
|
|
1866
1878
|
"""
|
|
1867
1879
|
app: AppContext = ctx.obj
|
|
1868
1880
|
|
|
1881
|
+
# Before the mutex check, so a stable build never names --pre in an error
|
|
1882
|
+
# other than this one.
|
|
1883
|
+
_reject_pre_only_option("--pre", pre)
|
|
1884
|
+
|
|
1869
1885
|
# 3-way --pre / --firmware-version / --stable mutex enforced once,
|
|
1870
1886
|
# post-parse, with a deterministic error message that doesn't depend on
|
|
1871
1887
|
# the user's option ordering. Raises click.UsageError → exit-2, matching
|
|
@@ -1914,7 +1930,7 @@ def fw(
|
|
|
1914
1930
|
channel_filter: Literal["all", "pre", "stable"]
|
|
1915
1931
|
if pre:
|
|
1916
1932
|
channel_filter = "pre"
|
|
1917
|
-
elif stable:
|
|
1933
|
+
elif stable or not _PRE_CHANNEL_ENABLED:
|
|
1918
1934
|
channel_filter = "stable"
|
|
1919
1935
|
else:
|
|
1920
1936
|
channel_filter = "all"
|
|
@@ -16,7 +16,7 @@ import statistics
|
|
|
16
16
|
import time
|
|
17
17
|
from contextlib import contextmanager
|
|
18
18
|
from pathlib import Path
|
|
19
|
-
from typing import Callable, Dict, Tuple # noqa: UP035
|
|
19
|
+
from typing import BinaryIO, Callable, Dict, Tuple # noqa: UP035
|
|
20
20
|
|
|
21
21
|
import tqdm
|
|
22
22
|
from tqdm.contrib.logging import logging_redirect_tqdm
|
|
@@ -362,6 +362,25 @@ def _blank_expected_bytes(_offset: int, length: int) -> bytes:
|
|
|
362
362
|
return b"\xff" * length
|
|
363
363
|
|
|
364
364
|
|
|
365
|
+
def _open_default_read_file(eprom_name: str) -> tuple[str, BinaryIO]:
|
|
366
|
+
"""Create the output file `read` uses when the user gives none:
|
|
367
|
+
`<NAME>.bin`, or `<NAME>-<n>.bin` with the lowest free `n` when that
|
|
368
|
+
file exists, so a second read never overwrites the first.
|
|
369
|
+
|
|
370
|
+
Mode 'xb' claims the name and creates the file in one step, so two reads
|
|
371
|
+
that start together never get the same name.
|
|
372
|
+
"""
|
|
373
|
+
base = eprom_name.upper()
|
|
374
|
+
path = f"{base}.bin"
|
|
375
|
+
n = 1
|
|
376
|
+
while True:
|
|
377
|
+
try:
|
|
378
|
+
return path, open(path, "xb")
|
|
379
|
+
except FileExistsError:
|
|
380
|
+
path = f"{base}-{n}.bin"
|
|
381
|
+
n += 1
|
|
382
|
+
|
|
383
|
+
|
|
365
384
|
def _write_blank_guard_refusal_message(
|
|
366
385
|
eprom_name: str, region_start: int, result: CompareResult
|
|
367
386
|
) -> str:
|
|
@@ -1298,14 +1317,20 @@ class EpromOperator:
|
|
|
1298
1317
|
if not cmd_data:
|
|
1299
1318
|
return False
|
|
1300
1319
|
|
|
1301
|
-
actual_output_file = output_file or
|
|
1302
|
-
|
|
1303
|
-
f"Reading EPROM {eprom_name.upper()}, saving to {actual_output_file}"
|
|
1304
|
-
)
|
|
1305
|
-
start_time = time.time()
|
|
1306
|
-
|
|
1320
|
+
actual_output_file = output_file or ""
|
|
1321
|
+
is_ok = False
|
|
1307
1322
|
try:
|
|
1308
|
-
|
|
1323
|
+
if output_file:
|
|
1324
|
+
file_handle = open(output_file, "wb")
|
|
1325
|
+
else:
|
|
1326
|
+
actual_output_file, file_handle = _open_default_read_file(
|
|
1327
|
+
eprom_name
|
|
1328
|
+
)
|
|
1329
|
+
logger.info(
|
|
1330
|
+
f"Reading EPROM {eprom_name.upper()}, saving to {actual_output_file}"
|
|
1331
|
+
)
|
|
1332
|
+
start_time = time.time()
|
|
1333
|
+
with file_handle:
|
|
1309
1334
|
|
|
1310
1335
|
def _write_to_file(address, data_chunk):
|
|
1311
1336
|
file_handle.seek(address)
|
|
@@ -1326,6 +1351,14 @@ class EpromOperator:
|
|
|
1326
1351
|
except IOError as e: # noqa: UP024
|
|
1327
1352
|
logger.error(f"File I/O error with {actual_output_file}: {e}")
|
|
1328
1353
|
return False
|
|
1354
|
+
finally:
|
|
1355
|
+
# A failed read with a generated name leaves no file behind,
|
|
1356
|
+
# so the next read takes that name again.
|
|
1357
|
+
if not output_file and actual_output_file and not is_ok:
|
|
1358
|
+
try:
|
|
1359
|
+
os.remove(actual_output_file)
|
|
1360
|
+
except OSError:
|
|
1361
|
+
pass
|
|
1329
1362
|
|
|
1330
1363
|
def consistency_check_eprom(
|
|
1331
1364
|
self,
|
|
@@ -181,6 +181,9 @@ class FirmwareReleaseRefusedError(FirmwareOperationError):
|
|
|
181
181
|
waives it; `--force` does not, because `--force` means "reinstall the same
|
|
182
182
|
version" and is also a wire flag (FLAG_FORCE).
|
|
183
183
|
|
|
184
|
+
Also raised when this CLI is a stable release and the firmware release is a
|
|
185
|
+
pre-release. Nothing waives that refusal.
|
|
186
|
+
|
|
184
187
|
A FirmwareOperationError, so cli_handlers.map_typed_errors renders it
|
|
185
188
|
verbatim through the existing FirmwareOperationError arm. No new arm.
|
|
186
189
|
"""
|
|
@@ -39,7 +39,12 @@ from firestarter.exceptions import (
|
|
|
39
39
|
ProgrammerNotFoundError,
|
|
40
40
|
SerialError,
|
|
41
41
|
)
|
|
42
|
-
from firestarter.fw_release_gate import
|
|
42
|
+
from firestarter.fw_release_gate import (
|
|
43
|
+
feature_version,
|
|
44
|
+
is_prerelease_version,
|
|
45
|
+
matches_stable_cli,
|
|
46
|
+
require_installable_release,
|
|
47
|
+
)
|
|
43
48
|
from firestarter.serial_comm import SerialCommunicator
|
|
44
49
|
from firestarter.timestamps import iso_text_to_cet
|
|
45
50
|
|
|
@@ -331,15 +336,21 @@ class FirmwareManager:
|
|
|
331
336
|
channel: Literal["stable", "pre", "pinned"] = "stable",
|
|
332
337
|
version: str | None = None,
|
|
333
338
|
board: str = "uno",
|
|
339
|
+
app_version: str | None = None,
|
|
334
340
|
) -> Tuple[str | None, str | None]: # noqa: UP006
|
|
335
341
|
"""Router: returns (resolved_version, download_url) or (None, None) on failure.
|
|
336
342
|
|
|
337
|
-
channel='stable' →
|
|
343
|
+
channel='stable' → for a stable app_version, the highest stable release on
|
|
344
|
+
its feature version (a stable CLI 3.2.x runs 3.2.y only);
|
|
345
|
+
otherwise delegates to fetch_latest_release_info.
|
|
338
346
|
channel='pre' → paginates /releases, takes the highest non-draft release by
|
|
339
347
|
PEP 440, stable or pre-release; falls back to stable if none.
|
|
340
348
|
channel='pinned' → fetches /releases/tags/{version} directly.
|
|
341
349
|
""" # noqa: E501
|
|
342
350
|
if channel == "stable":
|
|
351
|
+
app_feature = feature_version(app_version)
|
|
352
|
+
if is_prerelease_version(app_version) is False and app_feature:
|
|
353
|
+
return self._fetch_matching_stable_release_info(app_feature, board)
|
|
343
354
|
return self.fetch_latest_release_info(board=board)
|
|
344
355
|
|
|
345
356
|
elif channel == "pinned":
|
|
@@ -413,6 +424,44 @@ class FirmwareManager:
|
|
|
413
424
|
)
|
|
414
425
|
return None, None
|
|
415
426
|
|
|
427
|
+
def _fetch_matching_stable_release_info(
|
|
428
|
+
self, app_feature: tuple[int, int], board: str
|
|
429
|
+
) -> Tuple[str | None, str | None]: # noqa: UP006
|
|
430
|
+
"""The highest non-draft stable release whose feature version is app_feature."""
|
|
431
|
+
line = f"{app_feature[0]}.{app_feature[1]}.x"
|
|
432
|
+
try:
|
|
433
|
+
all_releases = self._fetch_all_releases()
|
|
434
|
+
except requests.RequestException as e:
|
|
435
|
+
logger.error(f"Failed to fetch firmware releases: {e}")
|
|
436
|
+
return None, None
|
|
437
|
+
|
|
438
|
+
candidates = []
|
|
439
|
+
for r in all_releases:
|
|
440
|
+
if r.get("draft"):
|
|
441
|
+
continue
|
|
442
|
+
try:
|
|
443
|
+
tag_version = Version(r.get("tag_name", ""))
|
|
444
|
+
except InvalidVersion:
|
|
445
|
+
continue
|
|
446
|
+
if tag_version.is_prerelease:
|
|
447
|
+
continue
|
|
448
|
+
if (tag_version.major, tag_version.minor) == app_feature:
|
|
449
|
+
candidates.append((tag_version, r))
|
|
450
|
+
|
|
451
|
+
if not candidates:
|
|
452
|
+
logger.error(f"No stable firmware release {line} was found.")
|
|
453
|
+
return None, None
|
|
454
|
+
|
|
455
|
+
_, picked = max(candidates, key=lambda t: t[0])
|
|
456
|
+
download_url = _pick_asset(picked.get("assets", []), board)
|
|
457
|
+
if not download_url:
|
|
458
|
+
logger.error(
|
|
459
|
+
f"Release {picked.get('tag_name')!r} has no asset "
|
|
460
|
+
f"{_asset_label(board)} for board {board!r}."
|
|
461
|
+
)
|
|
462
|
+
return None, None
|
|
463
|
+
return picked.get("tag_name"), download_url
|
|
464
|
+
|
|
416
465
|
def list_releases(
|
|
417
466
|
self,
|
|
418
467
|
channel_filter: Literal["all", "pre", "stable"] = "all",
|
|
@@ -847,8 +896,17 @@ class FirmwareManager:
|
|
|
847
896
|
self._hint_dfu_board()
|
|
848
897
|
return False
|
|
849
898
|
|
|
899
|
+
if app_version is None:
|
|
900
|
+
# Dereferenced at call time, not bound at import, so a test can
|
|
901
|
+
# monkeypatch firestarter.__version__ without reloading this module.
|
|
902
|
+
import firestarter as _pkg
|
|
903
|
+
|
|
904
|
+
app_version = _pkg.__version__
|
|
850
905
|
latest_version, download_url = self.fetch_release_info(
|
|
851
|
-
channel=channel,
|
|
906
|
+
channel=channel,
|
|
907
|
+
version=pinned_version,
|
|
908
|
+
board=board_to_use,
|
|
909
|
+
app_version=app_version,
|
|
852
910
|
)
|
|
853
911
|
|
|
854
912
|
# Guard the release tag where it is BORN, not on each branch that
|
|
@@ -866,12 +924,9 @@ class FirmwareManager:
|
|
|
866
924
|
# onto the pre channel, which is the channel most likely to be ahead of
|
|
867
925
|
# it. That raises how often this refusal fires. Do not "fix" the auto
|
|
868
926
|
# route to avoid the refusal -- the refusal is the point.
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
import firestarter as _pkg
|
|
873
|
-
|
|
874
|
-
app_version = _pkg.__version__
|
|
927
|
+
#
|
|
928
|
+
# The same call refuses a pre-release on a stable CLI. That covers a
|
|
929
|
+
# library caller that asks a stable CLI for channel="pre".
|
|
875
930
|
require_installable_release(
|
|
876
931
|
app_version,
|
|
877
932
|
latest_version,
|
|
@@ -917,8 +972,22 @@ class FirmwareManager:
|
|
|
917
972
|
"only guard."
|
|
918
973
|
)
|
|
919
974
|
|
|
975
|
+
# A stable CLI runs a stable release on its own feature version only. A
|
|
976
|
+
# board on anything else is never up to date, even when its version is
|
|
977
|
+
# higher: a stable 3.2.6 CLI moves a 3.2.3b1 board to 3.2.2.
|
|
978
|
+
mismatched = bool(
|
|
979
|
+
current_version
|
|
980
|
+
and is_prerelease_version(app_version) is False
|
|
981
|
+
and not matches_stable_cli(app_version, current_version)
|
|
982
|
+
)
|
|
920
983
|
is_up_to_date = False
|
|
921
|
-
if
|
|
984
|
+
if mismatched:
|
|
985
|
+
logger.warning(
|
|
986
|
+
f"Firmware {current_version} does not match CLI {app_version}. "
|
|
987
|
+
"A stable CLI uses a stable firmware release with the same "
|
|
988
|
+
"major.minor version."
|
|
989
|
+
)
|
|
990
|
+
elif current_version and latest_version:
|
|
922
991
|
is_up_to_date = self._compare_versions(current_version, latest_version)
|
|
923
992
|
|
|
924
993
|
if is_up_to_date and not force_install:
|
|
@@ -946,10 +1015,12 @@ class FirmwareManager:
|
|
|
946
1015
|
elif (
|
|
947
1016
|
not is_up_to_date and current_version and latest_version
|
|
948
1017
|
): # No flags, but update available
|
|
949
|
-
|
|
950
|
-
f"
|
|
951
|
-
|
|
952
|
-
|
|
1018
|
+
prompt = (
|
|
1019
|
+
f"Install firmware {latest_version} for {board_to_use} (current: {current_version})?" # noqa: E501
|
|
1020
|
+
if mismatched
|
|
1021
|
+
else f"New firmware {latest_version} available for {board_to_use} (current: {current_version}). Update now?" # noqa: E501
|
|
1022
|
+
)
|
|
1023
|
+
if Confirm.ask(prompt, default=False):
|
|
953
1024
|
should_install_now = True
|
|
954
1025
|
else:
|
|
955
1026
|
logger.info("Firmware update cancelled by user.")
|
|
@@ -16,6 +16,16 @@ The rule is the FEATURE version, (major, minor). The patch number is ignored,
|
|
|
16
16
|
and so is the pre-release suffix: 3.1.0b2 and 3.1.0b5 are one feature version.
|
|
17
17
|
A release is refused when its feature version is ABOVE this CLI's.
|
|
18
18
|
|
|
19
|
+
A stable CLI also refuses every pre-release firmware. A stable CLI is a final
|
|
20
|
+
PEP 440 release; a pre-release firmware is a b/rc/dev tag. This rule ignores the
|
|
21
|
+
feature version, and nothing waives it: a stable user gets stable firmware only.
|
|
22
|
+
A pre-release CLI (a b/rc tag, or a dev checkout) installs either.
|
|
23
|
+
|
|
24
|
+
A stable CLI also refuses a release BELOW its feature version. A stable CLI
|
|
25
|
+
3.2.x runs stable firmware 3.2.y only (`matches_stable_cli`). Nothing waives
|
|
26
|
+
this refusal either. `firmware.fetch_release_info` picks the highest matching
|
|
27
|
+
stable release for a stable CLI, so `fw --install` installs a matching release.
|
|
28
|
+
|
|
19
29
|
The reverse pairing -- a CLI newer than the firmware -- is NOT gated here.
|
|
20
30
|
`_maybe_auto_route_to_pre` in cli_handlers.py steers channel selection away from
|
|
21
31
|
it. The two checks cover opposite directions and neither replaces the other.
|
|
@@ -23,7 +33,7 @@ it. The two checks cover opposite directions and neither replaces the other.
|
|
|
23
33
|
Polarity: fail closed. A false pass flashes a board this host cannot drive, and
|
|
24
34
|
the board must then be recovered through the bootloader. A false refusal only
|
|
25
35
|
makes the install unavailable, and `firestarter fw --list` still enumerates
|
|
26
|
-
|
|
36
|
+
the releases. A version that cannot be read on either side is refused exactly
|
|
27
37
|
like a version that is too new.
|
|
28
38
|
|
|
29
39
|
One deliberate exception: a release version of None is NOT a refusal. With no
|
|
@@ -31,7 +41,8 @@ release there is nothing to flash, and `manage_firmware_update` already reports
|
|
|
31
41
|
the failed fetch. Turning a GitHub outage into a version-mismatch refusal would
|
|
32
42
|
be a lie. This is the one place the gate looks fail-open and is not.
|
|
33
43
|
|
|
34
|
-
Escape: `--allow-newer-firmware`. Never an
|
|
44
|
+
Escape for the feature-version refusal: `--allow-newer-firmware`. Never an
|
|
45
|
+
environment variable. The pre-release and older-line refusals have no escape.
|
|
35
46
|
|
|
36
47
|
Unlike the pre-serial gates, the authoritative call runs AFTER the release
|
|
37
48
|
metadata fetch, because the release tag is only known then. It still runs before
|
|
@@ -75,6 +86,23 @@ _UNREADABLE_FORMAT = (
|
|
|
75
86
|
"--allow-newer-firmware. This pairing is not tested."
|
|
76
87
|
)
|
|
77
88
|
|
|
89
|
+
_PRERELEASE_ON_STABLE_FORMAT = (
|
|
90
|
+
"Firmware {release} is a pre-release. This CLI is {app}, a stable release. "
|
|
91
|
+
"A stable CLI installs stable firmware only. "
|
|
92
|
+
"Refusing to install firmware {release}.\n"
|
|
93
|
+
"Run 'firestarter fw --install' to install the latest stable firmware.\n"
|
|
94
|
+
"To use pre-release firmware, install a pre-release CLI. "
|
|
95
|
+
"Run 'pip install --upgrade --pre firestarter'."
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
_OLDER_LINE_ON_STABLE_FORMAT = (
|
|
99
|
+
"Firmware {release} is feature version {release_feature}. This CLI is {app}, "
|
|
100
|
+
"a stable release. A stable CLI installs stable firmware {app_feature}.x only. "
|
|
101
|
+
"Refusing to install firmware {release}.\n"
|
|
102
|
+
"Run 'firestarter fw --install' to install the latest stable firmware "
|
|
103
|
+
"{app_feature}.x."
|
|
104
|
+
)
|
|
105
|
+
|
|
78
106
|
_UNREADABLE_TEXT = "an unreadable version"
|
|
79
107
|
|
|
80
108
|
|
|
@@ -94,6 +122,31 @@ def feature_version(raw: str | None) -> tuple[int, int] | None:
|
|
|
94
122
|
return (parsed.major, parsed.minor)
|
|
95
123
|
|
|
96
124
|
|
|
125
|
+
def is_prerelease_version(raw: str | None) -> bool | None:
|
|
126
|
+
"""True for a PEP 440 pre-release (b, rc or dev), False for a final release.
|
|
127
|
+
|
|
128
|
+
None when the version cannot be read. The caller decides what that means;
|
|
129
|
+
the feature-version check already refuses an unreadable version.
|
|
130
|
+
"""
|
|
131
|
+
if not raw:
|
|
132
|
+
return None
|
|
133
|
+
try:
|
|
134
|
+
return Version(raw).is_prerelease
|
|
135
|
+
except InvalidVersion:
|
|
136
|
+
return None
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def matches_stable_cli(app_version: str | None, fw_version: str | None) -> bool:
|
|
140
|
+
"""True when fw_version is a stable release on app_version's feature version.
|
|
141
|
+
|
|
142
|
+
The firmware a stable CLI runs. False when either version cannot be read.
|
|
143
|
+
"""
|
|
144
|
+
if is_prerelease_version(fw_version) is not False:
|
|
145
|
+
return False
|
|
146
|
+
app = feature_version(app_version)
|
|
147
|
+
return app is not None and feature_version(fw_version) == app
|
|
148
|
+
|
|
149
|
+
|
|
97
150
|
def is_refused(app_version: str | None, release_version: str | None) -> bool:
|
|
98
151
|
"""True when this CLI must not flash this release.
|
|
99
152
|
|
|
@@ -117,9 +170,32 @@ def require_installable_release(
|
|
|
117
170
|
) -> None:
|
|
118
171
|
"""Raise FirmwareReleaseRefusedError when this CLI must not flash this release.
|
|
119
172
|
|
|
120
|
-
Returns None on pass. `allow_newer` waives the refusal; it
|
|
121
|
-
so a library caller that does not ask for the waiver does
|
|
173
|
+
Returns None on pass. `allow_newer` waives the feature-version refusal; it
|
|
174
|
+
defaults to False so a library caller that does not ask for the waiver does
|
|
175
|
+
not get it. Nothing waives the pre-release or older-line refusal on a
|
|
176
|
+
stable CLI.
|
|
177
|
+
|
|
178
|
+
An unreadable version on either side is not judged by the stable-CLI rules.
|
|
179
|
+
The feature-version check refuses it.
|
|
122
180
|
"""
|
|
181
|
+
if is_prerelease_version(app_version) is False:
|
|
182
|
+
if is_prerelease_version(release_version) is True:
|
|
183
|
+
raise FirmwareReleaseRefusedError(
|
|
184
|
+
_PRERELEASE_ON_STABLE_FORMAT.format(
|
|
185
|
+
app=app_version, release=release_version
|
|
186
|
+
)
|
|
187
|
+
)
|
|
188
|
+
app = feature_version(app_version)
|
|
189
|
+
release = feature_version(release_version)
|
|
190
|
+
if app and release and release < app:
|
|
191
|
+
raise FirmwareReleaseRefusedError(
|
|
192
|
+
_OLDER_LINE_ON_STABLE_FORMAT.format(
|
|
193
|
+
app=app_version,
|
|
194
|
+
release=release_version,
|
|
195
|
+
app_feature=f"{app[0]}.{app[1]}",
|
|
196
|
+
release_feature=f"{release[0]}.{release[1]}",
|
|
197
|
+
)
|
|
198
|
+
)
|
|
123
199
|
if allow_newer or not is_refused(app_version, release_version):
|
|
124
200
|
return
|
|
125
201
|
app = feature_version(app_version)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: firestarter
|
|
3
|
-
Version: 3.2.
|
|
3
|
+
Version: 3.2.7b3
|
|
4
4
|
Summary: EPROM programmer software for the Relatively Universal ROM Programmer
|
|
5
5
|
Author-email: Henrik Olsson <henols@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/henols/firestarter_app
|
|
@@ -217,8 +217,8 @@
|
|
|
217
217
|
|
|
218
218
|
Firmware version.
|
|
219
219
|
|
|
220
|
-
|
|
221
|
-
|
|
220
|
+
The release channel options are mutually exclusive; passing more than one is
|
|
221
|
+
refused before anything is installed.
|
|
222
222
|
|
|
223
223
|
Options:
|
|
224
224
|
-i, --install Try to install the latest firmware.
|
|
@@ -257,15 +257,12 @@
|
|
|
257
257
|
|
|
258
258
|
Firmware version.
|
|
259
259
|
|
|
260
|
-
|
|
261
|
-
|
|
260
|
+
The release channel options are mutually exclusive; passing more than one is
|
|
261
|
+
refused before anything is installed.
|
|
262
262
|
|
|
263
263
|
Options:
|
|
264
264
|
-i, --install Try to install the latest firmware.
|
|
265
|
-
--
|
|
266
|
-
pip install --pre).
|
|
267
|
-
--firmware-version VERSION Pin exact firmware version (e.g. 3.1.0,
|
|
268
|
-
3.1.0b2, 3.1.0rc1).
|
|
265
|
+
--firmware-version VERSION Pin exact firmware version (e.g. 3.1.0).
|
|
269
266
|
--stable Explicitly select stable channel. With
|
|
270
267
|
--list, filters to stable releases only.
|
|
271
268
|
--list List available firmware releases for the
|