firestarter 3.2.1b1__tar.gz → 3.2.2__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.1b1/firestarter.egg-info → firestarter-3.2.2}/PKG-INFO +2 -1
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/_version.py +3 -3
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/chip_resolver.py +44 -1
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/cli_handlers.py +41 -11
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/database.py +140 -33
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/diagnostic_report.py +4 -4
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/eprom_info.py +35 -7
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/eprom_operations.py +6 -6
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/exceptions.py +6 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/firmware.py +3 -2
- firestarter-3.2.2/firestarter/timestamps.py +83 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2/firestarter.egg-info}/PKG-INFO +2 -1
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter.egg-info/SOURCES.txt +4 -9
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter.egg-info/requires.txt +1 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter.egg-info/scm_file_list.json +4 -9
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter.egg-info/scm_version.json +2 -2
- {firestarter-3.2.1b1 → firestarter-3.2.2}/pyproject.toml +1 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/__snapshots__/test_characterization.ambr +29 -28
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/conftest.py +35 -0
- firestarter-3.2.1b1/tests/golden/wire_dict_baseline.json → firestarter-3.2.2/tests/golden/wire_dict.json +189 -137
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/plan_corpus.py +11 -1
- firestarter-3.2.2/tests/test_chip_name_resolution.py +199 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_chip_resolver.py +2 -1
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_derive_plan_no_drop_sweep.py +14 -22
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_derive_plan_structural_sentinel.py +20 -34
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_devtest_firmware_error_propagation.py +11 -4
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_erase_flag_invariants.py +20 -30
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_firmware_install.py +4 -2
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_hardware.py +10 -2
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_page_size_invariants.py +22 -44
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_probe_spurious_setup_ack.py +3 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_protocol_not_implemented_production_path.py +4 -1
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_sdp_db_invariant.py +22 -62
- firestarter-3.2.2/tests/test_timestamps.py +87 -0
- firestarter-3.2.2/tests/test_wire_dict_equivalence.py +177 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_write_blank_guard_pinning.py +10 -6
- firestarter-3.2.1b1/tests/golden/wire_dict_expected_deltas_149.json +0 -65
- firestarter-3.2.1b1/tests/golden/wire_dict_expected_deltas_153.json +0 -265
- firestarter-3.2.1b1/tests/golden/wire_dict_expected_deltas_182.json +0 -219
- firestarter-3.2.1b1/tests/golden/wire_dict_expected_deltas_194.json +0 -86
- firestarter-3.2.1b1/tests/golden/wire_dict_expected_deltas_197.json +0 -20
- firestarter-3.2.1b1/tests/golden/wire_dict_expected_deltas_198.json +0 -17
- firestarter-3.2.1b1/tests/golden/wire_dict_expected_deltas_199.json +0 -14
- firestarter-3.2.1b1/tests/golden/wire_dict_expected_deltas_dip24_2816.json +0 -91
- firestarter-3.2.1b1/tests/test_wire_dict_equivalence.py +0 -821
- {firestarter-3.2.1b1 → firestarter-3.2.2}/LICENSE +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/MANIFEST.in +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/README.md +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/__init__.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/address_parser.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/avr_tool.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/channel.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/chip_test.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/codec.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/compare.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/config.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/constants.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/data/chip_database.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/data/pinouts.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/dip24_2816_erase_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/erase_support.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/flash4_erase_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/frame_parser.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/fw_release_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/hardware.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/ic_layout.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/jp5_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/jumper_table.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/lock_status.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/log_capture.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/logging_utils.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/main.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/messages.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/page_size_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/protection_readability.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/py32_dfu.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/sdp_capability.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/sdp_honesty.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/serial_comm.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/submit.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/transport_counters.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/utils.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/vpp_display.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter/write_blank_guard.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter.egg-info/dependency_links.txt +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter.egg-info/entry_points.txt +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/firestarter.egg-info/top_level.txt +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/setup.cfg +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/__init__.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fake_chip.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/part_number_delta.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/report_shapes.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/at28c256-full-all-ok-sdp.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/attr01-status-axis-transport-fault.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/gh20-at28c256-fail.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/gh23-w27e257-fail.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/gh28-m27c512-fail.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/gh47-sst27sf512-pass.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/m27c512-full-all-ok.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/m27c512-full-blank-check-bad.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/m27c512-full-canonical-name.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/m27c512-full-comma-joined-name.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/m27c512-full-runs-1.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/prune03-synthesized-fingerprint-match.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/sst27sf512-full-all-ok.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/sst27sf512-six-step-readback-gated.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/sst27sf512-six-step.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/synthetic-arm4-empty-results.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/synthetic-arm4-no-ok.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/uv-slot-write-pass.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/reports/w27e257-full-all-ok.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/shape_ids.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/fixtures/synthetic_nonzero_chip_id.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/golden/build_db_part_specific_constants.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/golden/chip_database_field_inventory.json +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_address_parser.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_b15_page_size_corroboration.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_blast_radius_invariance.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_boot_block_hint.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_budget_failure_render.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_bug_characterization.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_build_db_constant_census.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_build_db_inclusion.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_build_db_interpret_timing.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_build_db_pinout_fork.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_canonical_part_number.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_characterization.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_chip_database_field_inventory.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_chip_test.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_chip_test_blank_check_order.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_chip_test_cycle.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_chip_test_sdp_leg.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_chip_test_timing.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_chip_test_uv_slot_write.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_cli_handlers.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_click_group_gate_hook.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_cobs.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_codec.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_codec_format_message.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_compare.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_config.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_config_band_refusal.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_connect_cost_harness.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_consistency_check.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_coverage_floor_v18.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_database_conversion.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_datasheet_overrides.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_decoder.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_dev_group_channel_gating.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_dev_test_cmd.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_dev_tools_channel_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_dfu_opcode_anchors.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_diagnostic_report.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_dip24_2816_erase_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_endpoint_constants.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_eprom_database.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_eprom_info.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_eprom_operations.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_erase_blank_step_nonregression.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_error_code_seam.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_even_block.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_extra_chips_supplement.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_flash4_erase_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_fw_list_failure_vs_empty.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_fw_port_targeting_and_blind_install.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_fw_release_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_fw_update_dead_endpoint.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_fw_update_path_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_fw_version_guard.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_fwguard.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_hw_revision_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_ic_layout.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_info_vpp_and_erase.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_jp5_gate.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_jumper_table.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_lock_status_class_partition.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_lock_status_cli.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_lock_status_wire.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_log_capture.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_logging_utils.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_matrix_artifact.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_page_size_alignment_refusal.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_page_size_write_refusal.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_parse_devtest_issue.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_programming_vcc_census.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_protect_flags_doc_measurements.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_protection_resolution.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_protection_status_catalog.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_protection_table_citations.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_protocol_not_implemented.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_provenance.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_pulse_us_override.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_py32_channel_gating.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_py32_dfu.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_py32_flash_map_host.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_py32_pyusb_absent.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_pyusb_api_surface.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_sdp_capability.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_sdp_honesty.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_serial_characterization.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_serial_comm.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_session_lease.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_setup_refusals.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_submit.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_transport_counters.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_utils.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_uv_mask.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_validate_family_cmd.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_validate_oracle.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_validated_parts_regression_surface.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_vcc_margin_rail.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_vpp_decode_table.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_vpp_rail_classification.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_write_blank_guard.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_write_guard_port_pinning.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_write_progress.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_write_response_budget.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_write_skip_erase_0x0d.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/tests/test_write_skip_sdp_unlock.py +0 -0
- {firestarter-3.2.1b1 → firestarter-3.2.2}/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.2
|
|
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
|
|
@@ -37,6 +37,7 @@ Requires-Dist: syrupy<7,>=5.0; extra == "test"
|
|
|
37
37
|
Requires-Dist: ruff>=0.15.14; extra == "test"
|
|
38
38
|
Requires-Dist: mypy<3,>=2.1.0; extra == "test"
|
|
39
39
|
Requires-Dist: pytest-cov>=7.1.0; extra == "test"
|
|
40
|
+
Requires-Dist: pytest-xdist>=3.6; extra == "test"
|
|
40
41
|
Requires-Dist: types-pyserial>=3.5.0.20260519; extra == "test"
|
|
41
42
|
Dynamic: license-file
|
|
42
43
|
|
|
@@ -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,
|
|
21
|
+
__version__ = version = '3.2.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (3, 2, 2)
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'ga7d562c'
|
|
@@ -10,11 +10,50 @@ Single chokepoint between CLI dispatch and the EPROM database lookup/conversion.
|
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
12
|
from firestarter.database import EpromDatabase
|
|
13
|
-
from firestarter.exceptions import
|
|
13
|
+
from firestarter.exceptions import (
|
|
14
|
+
AmbiguousChipNameError,
|
|
15
|
+
ChipNotFoundError,
|
|
16
|
+
ChipNotImplementedError,
|
|
17
|
+
)
|
|
14
18
|
|
|
15
19
|
_NON_ZERO_WIRE_FIELDS = ("memory-size", "pin-count", "vpp_mv")
|
|
16
20
|
|
|
17
21
|
|
|
22
|
+
def _qualified_choices(name: str, db: EpromDatabase) -> list[str]:
|
|
23
|
+
"""The row `name` selects first, then each other row it selects that
|
|
24
|
+
programs differently, all as qualified names. Empty when one row, or rows
|
|
25
|
+
that program the same, are selected."""
|
|
26
|
+
alternatives = db.ambiguous_alternatives(name)
|
|
27
|
+
if not alternatives:
|
|
28
|
+
return []
|
|
29
|
+
raw_config, manufacturer = db.get_eprom_config(name)
|
|
30
|
+
return [db.qualified_chip_name(name, raw_config, manufacturer), *alternatives]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def refuse_if_ambiguous(name: str, db: EpromDatabase) -> None:
|
|
34
|
+
"""Refuse a name that selects chips that program differently: guessing
|
|
35
|
+
one would drive the wrong VPP, algorithm or timing."""
|
|
36
|
+
choices = _qualified_choices(name, db)
|
|
37
|
+
if choices:
|
|
38
|
+
listed = ", ".join(f"'{c}'" for c in choices)
|
|
39
|
+
raise AmbiguousChipNameError(
|
|
40
|
+
f"'{name}' names chips that program differently. Name one: {listed}."
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def describe_ambiguity(name: str, db: EpromDatabase) -> str | None:
|
|
45
|
+
"""For display commands, which use the first row: the other rows `name`
|
|
46
|
+
selects, or None."""
|
|
47
|
+
choices = _qualified_choices(name, db)
|
|
48
|
+
if not choices:
|
|
49
|
+
return None
|
|
50
|
+
others = ", ".join(f"'{c}'" for c in choices[1:])
|
|
51
|
+
return (
|
|
52
|
+
f"'{name}' also names chips that program differently: {others}. "
|
|
53
|
+
f"This shows '{choices[0]}'."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
|
|
18
57
|
def resolve_chip(name: str, db: EpromDatabase | None = None) -> dict[str, Any]:
|
|
19
58
|
"""Resolve a chip name to its programmer-config dict.
|
|
20
59
|
|
|
@@ -26,6 +65,9 @@ def resolve_chip(name: str, db: EpromDatabase | None = None) -> dict[str, Any]:
|
|
|
26
65
|
conversion (``convert_to_programmer`` returns ``{}``) are treated as the
|
|
27
66
|
not-found condition.
|
|
28
67
|
|
|
68
|
+
Raises ``AmbiguousChipNameError`` (a ``ChipNotFoundError``) when the name
|
|
69
|
+
selects rows that program differently; the message names each of them.
|
|
70
|
+
|
|
29
71
|
Raises ``ChipNotImplementedError`` when the chip exists in the database but
|
|
30
72
|
its ``support_status`` is not ``"supported"`` (covers protocol-not-implemented,
|
|
31
73
|
adapter-required, and vpp-exceeds-max). This guard fires BEFORE any wire dict
|
|
@@ -49,6 +91,7 @@ def resolve_chip(name: str, db: EpromDatabase | None = None) -> dict[str, Any]:
|
|
|
49
91
|
# have a support_status, so raise ChipNotFoundError immediately.
|
|
50
92
|
if raw_config is None:
|
|
51
93
|
raise ChipNotFoundError(name)
|
|
94
|
+
refuse_if_ambiguous(name, db)
|
|
52
95
|
|
|
53
96
|
# Support-status guard: refuse every program-capable operation
|
|
54
97
|
# for non-supported chips BEFORE convert_to_programmer builds any wire dict.
|
|
@@ -4,7 +4,6 @@ This module is the production CLI surface; main.py re-exports `cli` as `main`
|
|
|
4
4
|
for the console-script entry point.
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
|
-
import datetime
|
|
8
7
|
import functools
|
|
9
8
|
import hashlib
|
|
10
9
|
import json
|
|
@@ -39,7 +38,11 @@ from firestarter.channel import (
|
|
|
39
38
|
dev_command_gate_message,
|
|
40
39
|
is_dev_tools_enabled,
|
|
41
40
|
)
|
|
42
|
-
from firestarter.chip_resolver import
|
|
41
|
+
from firestarter.chip_resolver import (
|
|
42
|
+
describe_ambiguity,
|
|
43
|
+
refuse_if_ambiguous,
|
|
44
|
+
resolve_chip,
|
|
45
|
+
)
|
|
43
46
|
from firestarter.chip_test import (
|
|
44
47
|
OP_ID,
|
|
45
48
|
SDP_HOLD_NOT_RUN,
|
|
@@ -106,6 +109,7 @@ from firestarter.protection_readability import (
|
|
|
106
109
|
protection_gate_for_entry,
|
|
107
110
|
)
|
|
108
111
|
from firestarter.sdp_capability import SDP_PROTOCOL_ID, sdp_capability
|
|
112
|
+
from firestarter.timestamps import iso_cet
|
|
109
113
|
|
|
110
114
|
logger = logging.getLogger("Firestarter")
|
|
111
115
|
|
|
@@ -160,10 +164,17 @@ def _complete_eprom(
|
|
|
160
164
|
semantics at main.py:39-45.
|
|
161
165
|
""" # noqa: E501
|
|
162
166
|
db = EpromDatabase()
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
qualified = db.names_needing_qualifier()
|
|
168
|
+
# A row whose name also names chips that program differently is offered
|
|
169
|
+
# by the qualified name that selects it.
|
|
170
|
+
names = (
|
|
171
|
+
qualified.get((e["manufacturer"], e["name"]), e["name"])
|
|
165
172
|
for e in db.get_eproms()
|
|
166
|
-
|
|
173
|
+
)
|
|
174
|
+
return [
|
|
175
|
+
click.shell_completion.CompletionItem(name)
|
|
176
|
+
for name in names
|
|
177
|
+
if name.lower().startswith(incomplete.lower())
|
|
167
178
|
]
|
|
168
179
|
|
|
169
180
|
|
|
@@ -486,7 +497,11 @@ def _list_cmd(app: AppContext) -> None:
|
|
|
486
497
|
"""List all EPROMs in the database."""
|
|
487
498
|
eprom_data_list = app.db.get_eproms()
|
|
488
499
|
if eprom_data_list:
|
|
489
|
-
print_eprom_list_table(
|
|
500
|
+
print_eprom_list_table(
|
|
501
|
+
eprom_data_list,
|
|
502
|
+
app.eprom_presenter.spec_builder,
|
|
503
|
+
app.db.names_needing_qualifier(),
|
|
504
|
+
)
|
|
490
505
|
sys.exit(0)
|
|
491
506
|
sys.exit(1)
|
|
492
507
|
|
|
@@ -504,6 +519,9 @@ def info(app: AppContext, eprom: str, config: bool, adapter: bool) -> None:
|
|
|
504
519
|
logger.error(f"EPROM '{eprom}' not found in database.")
|
|
505
520
|
sys.exit(1)
|
|
506
521
|
|
|
522
|
+
ambiguity = describe_ambiguity(eprom, app.db)
|
|
523
|
+
if ambiguity:
|
|
524
|
+
logger.warning(ambiguity)
|
|
507
525
|
eprom_data_for_programmer = app.db.convert_to_programmer(eprom_details)
|
|
508
526
|
raw_config_data, manufacturer = app.db.get_eprom_config(eprom)
|
|
509
527
|
|
|
@@ -534,7 +552,11 @@ def search(app: AppContext, text: str) -> None:
|
|
|
534
552
|
"""Search for EPROMs in the database."""
|
|
535
553
|
search_results = app.db.search_eprom(text)
|
|
536
554
|
if search_results:
|
|
537
|
-
print_eprom_list_table(
|
|
555
|
+
print_eprom_list_table(
|
|
556
|
+
search_results,
|
|
557
|
+
app.eprom_presenter.spec_builder,
|
|
558
|
+
app.db.names_needing_qualifier(),
|
|
559
|
+
)
|
|
538
560
|
sys.exit(0)
|
|
539
561
|
sys.exit(1)
|
|
540
562
|
|
|
@@ -1505,7 +1527,9 @@ def chip_id(app: AppContext, eprom: str, force: bool) -> None:
|
|
|
1505
1527
|
for ic in found_eproms_for_detected_id
|
|
1506
1528
|
]
|
|
1507
1529
|
print_eprom_list_table(
|
|
1508
|
-
mapped_found_eproms,
|
|
1530
|
+
mapped_found_eproms,
|
|
1531
|
+
app.eprom_presenter.spec_builder,
|
|
1532
|
+
app.db.names_needing_qualifier(),
|
|
1509
1533
|
)
|
|
1510
1534
|
else:
|
|
1511
1535
|
logger.warning(
|
|
@@ -1908,10 +1932,10 @@ def fw(
|
|
|
1908
1932
|
|
|
1909
1933
|
print(_json.dumps(releases, indent=2))
|
|
1910
1934
|
else:
|
|
1911
|
-
print(f"{'Version':<12} {'Channel':<14} {'Published':<
|
|
1935
|
+
print(f"{'Version':<12} {'Channel':<14} {'Published':<25} Asset URL")
|
|
1912
1936
|
for r in releases:
|
|
1913
1937
|
print(
|
|
1914
|
-
f"{r['version']:<12} {r['channel']:<14} {r['published']:<
|
|
1938
|
+
f"{r['version']:<12} {r['channel']:<14} {r['published']:<25} {r['asset_url']}" # noqa: E501
|
|
1915
1939
|
)
|
|
1916
1940
|
if not releases:
|
|
1917
1941
|
print(f"No releases found for board {board}.")
|
|
@@ -2638,7 +2662,7 @@ def _write_artifact(
|
|
|
2638
2662
|
out_path.mkdir(parents=True, exist_ok=True)
|
|
2639
2663
|
|
|
2640
2664
|
artifact: Dict[str, Any] = { # noqa: UP006
|
|
2641
|
-
"generated":
|
|
2665
|
+
"generated": iso_cet(),
|
|
2642
2666
|
"harness_version": "71",
|
|
2643
2667
|
"cells": cells,
|
|
2644
2668
|
}
|
|
@@ -3058,6 +3082,8 @@ def _canonical_part_number(part_number: str | None, raw_token: str) -> str | Non
|
|
|
3058
3082
|
def _strip_paren(s: str) -> str:
|
|
3059
3083
|
return re.sub(r"\([^)]*\)", "", s).strip().lower()
|
|
3060
3084
|
|
|
3085
|
+
# A manufacturer qualifier (PART@MANUFACTURER) is not part of the name.
|
|
3086
|
+
raw_token = raw_token.partition("@")[0].strip()
|
|
3061
3087
|
query = raw_token.lower()
|
|
3062
3088
|
if query == part_number.lower():
|
|
3063
3089
|
return part_number
|
|
@@ -3166,6 +3192,10 @@ def dev_test(app: "AppContext", chip: str, fast: bool, submit: bool) -> None:
|
|
|
3166
3192
|
# still runs the full community-validation sweep below.
|
|
3167
3193
|
if not app.db.get_eprom(chip):
|
|
3168
3194
|
raise ChipNotFoundError(f"{chip}: not found in database")
|
|
3195
|
+
# A name that selects chips that program differently is refused here,
|
|
3196
|
+
# not step by step: every step would be SKIPPED and the report would
|
|
3197
|
+
# name no chip.
|
|
3198
|
+
refuse_if_ambiguous(chip, app.db)
|
|
3169
3199
|
|
|
3170
3200
|
# The chip must be known to be in the DB (see above) before its
|
|
3171
3201
|
# electrical type can be read, so the UV-scope resolution happens here,
|
|
@@ -14,6 +14,7 @@ return the same instance.
|
|
|
14
14
|
import json
|
|
15
15
|
import logging
|
|
16
16
|
import os
|
|
17
|
+
import re
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
from typing import Any
|
|
19
20
|
|
|
@@ -145,6 +146,11 @@ def _read_config_file(filename: str) -> dict:
|
|
|
145
146
|
return {}
|
|
146
147
|
|
|
147
148
|
|
|
149
|
+
def _strip_mode(name: str) -> str:
|
|
150
|
+
"""'DS1245AB(RW)' -> 'ds1245ab': the name without its mode annotation."""
|
|
151
|
+
return re.sub(r"\([^)]*\)", "", name).strip().lower()
|
|
152
|
+
|
|
153
|
+
|
|
148
154
|
class EpromDatabase:
|
|
149
155
|
"""
|
|
150
156
|
Manages the EPROM and pin map database for the Firestarter application.
|
|
@@ -157,6 +163,7 @@ class EpromDatabase:
|
|
|
157
163
|
def __init__(self, skip_local_override: bool = False):
|
|
158
164
|
self.proms: dict[str, Any] = {}
|
|
159
165
|
self.pin_maps: dict[str, Any] = {}
|
|
166
|
+
self._names_needing_qualifier: dict[tuple[str, str], str] | None = None
|
|
160
167
|
self._initialize_database_core(skip_local_override=skip_local_override)
|
|
161
168
|
logger.debug("EpromDatabase initialized.")
|
|
162
169
|
|
|
@@ -426,46 +433,146 @@ class EpromDatabase:
|
|
|
426
433
|
for ic_config in ics
|
|
427
434
|
]
|
|
428
435
|
|
|
436
|
+
def find_chip_configs(self, chip_name: str) -> list[tuple[dict, str]]:
|
|
437
|
+
"""Return every database row `chip_name` selects, as
|
|
438
|
+
(config_dict, manufacturer) pairs in database order.
|
|
439
|
+
|
|
440
|
+
A name matches a row's `part_number` directly, or one of its
|
|
441
|
+
comma-separated aliases. Upstream infoic.xml encodes chip aliases as
|
|
442
|
+
comma-separated lists (e.g. "AT28C256,AT28C256E,AT28HC256"), which
|
|
443
|
+
build_db.py preserves in the part_number field.
|
|
444
|
+
|
|
445
|
+
infoic.xml also annotates many names with a parenthetical mode, such
|
|
446
|
+
as "(RW)", "(TEST)" or "(RW3.3V)" (e.g. "DS1245AB(RW),DS1245Y(RW)"). A
|
|
447
|
+
name matches an annotated alias with the annotation stripped, but only
|
|
448
|
+
when no row matches the name as typed: "DS1220(TEST)" selects the
|
|
449
|
+
(TEST) row, never the (RW) row that "DS1220" also matches.
|
|
450
|
+
|
|
451
|
+
The same part number can name rows of different manufacturers, which
|
|
452
|
+
program differently. `PART@MANUFACTURER` selects one manufacturer's
|
|
453
|
+
rows; see `resolve_manufacturer` for how the manufacturer matches.
|
|
454
|
+
"""
|
|
455
|
+
name, at, wanted = chip_name.partition("@")
|
|
456
|
+
manufacturer_wanted = self.resolve_manufacturer(wanted) if at else None
|
|
457
|
+
if at and manufacturer_wanted is None:
|
|
458
|
+
return []
|
|
459
|
+
query = name.strip().lower()
|
|
460
|
+
query_stripped = _strip_mode(name)
|
|
461
|
+
exact: list[tuple[dict, str]] = []
|
|
462
|
+
stripped: list[tuple[dict, str]] = []
|
|
463
|
+
for manufacturer, ics in self.proms.items():
|
|
464
|
+
if manufacturer_wanted is not None and manufacturer != manufacturer_wanted:
|
|
465
|
+
continue
|
|
466
|
+
for ic_config in ics:
|
|
467
|
+
part_number = ic_config.get("part_number", "")
|
|
468
|
+
aliases = part_number.split(",")
|
|
469
|
+
if query == part_number.lower() or query in (
|
|
470
|
+
a.strip().lower() for a in aliases
|
|
471
|
+
):
|
|
472
|
+
exact.append((ic_config, manufacturer))
|
|
473
|
+
elif query_stripped and query_stripped in (
|
|
474
|
+
_strip_mode(a) for a in aliases
|
|
475
|
+
):
|
|
476
|
+
stripped.append((ic_config, manufacturer))
|
|
477
|
+
return exact or stripped
|
|
478
|
+
|
|
429
479
|
def get_eprom_config(self, chip_name: str):
|
|
430
480
|
"""
|
|
431
481
|
Retrieves the raw configuration data for a specific EPROM by its name.
|
|
432
482
|
Returns (config_dict, manufacturer_str) or (None, None).
|
|
433
483
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
chip aliases as comma-separated lists (e.g.,
|
|
437
|
-
"AT28C256,AT28C256E,AT28HC256"), which build_db.py preserves in
|
|
438
|
-
the part_number field. Without alias-aware lookup, queries like
|
|
439
|
-
`firestarter info AT28C256` failed despite the chip being in the DB.
|
|
440
|
-
|
|
441
|
-
Also handles infoic.xml's parenthetical mode annotations: many
|
|
442
|
-
chip names carry "(RW)", "(TEST)", "(RW3.3V)" etc. (e.g.,
|
|
443
|
-
"DS1245AB(RW),DS1245Y(RW)"). A plain query like "DS1245AB"
|
|
444
|
-
is normalized to match against the paren-stripped alias.
|
|
484
|
+
The first row `find_chip_configs` selects; see there for how a name
|
|
485
|
+
matches.
|
|
445
486
|
"""
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
487
|
+
matches = self.find_chip_configs(chip_name)
|
|
488
|
+
return matches[0] if matches else (None, None)
|
|
489
|
+
|
|
490
|
+
def resolve_manufacturer(self, wanted: str) -> str | None:
|
|
491
|
+
"""The manufacturer `wanted` names: an exact name, ignoring case, else
|
|
492
|
+
the one manufacturer it is a prefix of. None when it names none, or is
|
|
493
|
+
a prefix of more than one."""
|
|
494
|
+
wanted = wanted.strip().lower()
|
|
495
|
+
if not wanted:
|
|
496
|
+
return None
|
|
497
|
+
names = list(self.proms)
|
|
498
|
+
exact = [m for m in names if m.lower() == wanted]
|
|
499
|
+
if exact:
|
|
500
|
+
return exact[0]
|
|
501
|
+
prefixed = [m for m in names if m.lower().startswith(wanted)]
|
|
502
|
+
return prefixed[0] if len(prefixed) == 1 else None
|
|
503
|
+
|
|
504
|
+
def qualified_chip_name(
|
|
505
|
+
self, chip_name: str, ic_config: dict, manufacturer: str
|
|
506
|
+
) -> str:
|
|
507
|
+
"""The name that selects this one row: the row's alias that `chip_name`
|
|
508
|
+
matched, with its mode annotation, then `@` and the manufacturer.
|
|
509
|
+
|
|
510
|
+
The manufacturer is its first word when that word names it alone, so
|
|
511
|
+
the name needs no shell quoting ('27C128@MICROCHIP', not
|
|
512
|
+
'27C128@MICROCHIP memory').
|
|
513
|
+
"""
|
|
514
|
+
name = chip_name.partition("@")[0].strip()
|
|
515
|
+
aliases = [a.strip() for a in ic_config.get("part_number", "").split(",")]
|
|
516
|
+
alias = next(
|
|
517
|
+
(a for a in aliases if a.lower() == name.lower()),
|
|
518
|
+
next(
|
|
519
|
+
(a for a in aliases if _strip_mode(a) == _strip_mode(name)),
|
|
520
|
+
aliases[0],
|
|
521
|
+
),
|
|
522
|
+
)
|
|
523
|
+
first_word = manufacturer.split()[0] if manufacturer.split() else manufacturer
|
|
524
|
+
short = (
|
|
525
|
+
first_word
|
|
526
|
+
if self.resolve_manufacturer(first_word) == manufacturer
|
|
527
|
+
else manufacturer
|
|
528
|
+
)
|
|
529
|
+
return f"{alias}@{short}"
|
|
530
|
+
|
|
531
|
+
def names_needing_qualifier(self) -> dict[tuple[str, str], str]:
|
|
532
|
+
"""Every row with an alias that, typed as shown, also selects rows that
|
|
533
|
+
program differently, keyed (manufacturer, part_number), with the
|
|
534
|
+
qualified name that selects it. Such a row can only be named with
|
|
535
|
+
`@MANUFACTURER`."""
|
|
536
|
+
if self._names_needing_qualifier is None:
|
|
537
|
+
by_name: dict[str, list[tuple[dict, str]]] = {}
|
|
538
|
+
for manufacturer, ics in self.proms.items():
|
|
539
|
+
for ic_config in ics:
|
|
540
|
+
for alias in ic_config.get("part_number", "").split(","):
|
|
541
|
+
by_name.setdefault(alias.strip().lower(), []).append(
|
|
542
|
+
(ic_config, manufacturer)
|
|
543
|
+
)
|
|
544
|
+
needing: dict[tuple[str, str], str] = {}
|
|
545
|
+
for name, rows in by_name.items():
|
|
546
|
+
if len(rows) < 2:
|
|
547
|
+
continue
|
|
548
|
+
wires = [
|
|
549
|
+
self.convert_to_programmer(self._map_data(ic, m)) for ic, m in rows
|
|
550
|
+
]
|
|
551
|
+
if all(w == wires[0] for w in wires[1:]):
|
|
552
|
+
continue
|
|
553
|
+
for ic, m in rows:
|
|
554
|
+
needing.setdefault(
|
|
555
|
+
(m, ic.get("part_number", "")),
|
|
556
|
+
self.qualified_chip_name(name, ic, m),
|
|
557
|
+
)
|
|
558
|
+
self._names_needing_qualifier = needing
|
|
559
|
+
return self._names_needing_qualifier
|
|
451
560
|
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
return ic_config, manufacturer
|
|
468
|
-
return None, None
|
|
561
|
+
def ambiguous_alternatives(self, chip_name: str) -> list[str]:
|
|
562
|
+
"""Name the other rows `chip_name` selects that program differently
|
|
563
|
+
from the one `get_eprom_config` uses, each as a qualified name that
|
|
564
|
+
selects that row alone. Empty when the choice cannot matter.
|
|
565
|
+
"""
|
|
566
|
+
matches = self.find_chip_configs(chip_name)
|
|
567
|
+
if len(matches) < 2:
|
|
568
|
+
return []
|
|
569
|
+
chosen = self.convert_to_programmer(self._map_data(*matches[0]))
|
|
570
|
+
return [
|
|
571
|
+
self.qualified_chip_name(chip_name, ic_config, manufacturer)
|
|
572
|
+
for ic_config, manufacturer in matches[1:]
|
|
573
|
+
if self.convert_to_programmer(self._map_data(ic_config, manufacturer))
|
|
574
|
+
!= chosen
|
|
575
|
+
]
|
|
469
576
|
|
|
470
577
|
def get_eprom(self, chip_name: str):
|
|
471
578
|
"""
|
|
@@ -23,7 +23,6 @@ human-input field gates it.
|
|
|
23
23
|
|
|
24
24
|
from __future__ import annotations
|
|
25
25
|
|
|
26
|
-
import datetime
|
|
27
26
|
import hashlib
|
|
28
27
|
import json
|
|
29
28
|
from dataclasses import dataclass, field
|
|
@@ -45,6 +44,7 @@ from firestarter.chip_test import (
|
|
|
45
44
|
repeat_policy_tag,
|
|
46
45
|
resolve_error_name,
|
|
47
46
|
)
|
|
47
|
+
from firestarter.timestamps import iso_cet
|
|
48
48
|
|
|
49
49
|
# ---------------------------------------------------------------------------
|
|
50
50
|
# Module constants -- single sources of truth
|
|
@@ -774,8 +774,8 @@ class DiagnosticReport:
|
|
|
774
774
|
over dataclass fields, so this field's absence from that list is the
|
|
775
775
|
whole exclusion mechanism."""
|
|
776
776
|
|
|
777
|
-
def
|
|
778
|
-
return
|
|
777
|
+
def _now(self) -> str:
|
|
778
|
+
return iso_cet()
|
|
779
779
|
|
|
780
780
|
def _auto_capture_dict(self) -> dict[str, Any]:
|
|
781
781
|
ac = self.auto_capture
|
|
@@ -1065,7 +1065,7 @@ class DiagnosticReport:
|
|
|
1065
1065
|
"""
|
|
1066
1066
|
return {
|
|
1067
1067
|
"schema_version": SCHEMA_VERSION,
|
|
1068
|
-
"generated": self.
|
|
1068
|
+
"generated": self._now(),
|
|
1069
1069
|
"elapsed": self.elapsed,
|
|
1070
1070
|
"auto_capture": self._auto_capture_dict(),
|
|
1071
1071
|
"transport_health": self._transport_dict(),
|
|
@@ -394,9 +394,17 @@ class EpromConsolePresenter:
|
|
|
394
394
|
|
|
395
395
|
|
|
396
396
|
# --- Helper function for printing EPROM list (for testing/CLI) ---
|
|
397
|
-
def print_eprom_list_table(
|
|
397
|
+
def print_eprom_list_table(
|
|
398
|
+
eproms_data: list,
|
|
399
|
+
spec_builder: EpromSpecBuilder,
|
|
400
|
+
qualified_names: dict[tuple[str, str], str] | None = None,
|
|
401
|
+
):
|
|
398
402
|
"""Prints a list of EPROM data in a table format. For CLI/testing.
|
|
399
403
|
|
|
404
|
+
A row in `qualified_names` (keyed (manufacturer, name)) can only be named
|
|
405
|
+
with `@MANUFACTURER`; its Name cell ends in '*' and a line under the table
|
|
406
|
+
gives an example.
|
|
407
|
+
|
|
400
408
|
Column layout:
|
|
401
409
|
- Name: dynamic width clamped to [13, 20]; names longer than 20 chars are
|
|
402
410
|
truncated with a trailing ellipsis ('…') that counts toward the 20-char cap.
|
|
@@ -413,19 +421,26 @@ def print_eprom_list_table(eproms_data: list, spec_builder: EpromSpecBuilder):
|
|
|
413
421
|
# Compute the widest rendered name (including any [!] suffix) across all rows,
|
|
414
422
|
# then clamp to the [13, 20] range. Names that would exceed 20 chars are
|
|
415
423
|
# truncated to 19 chars + '…' (ellipsis counts toward the 20-char cap).
|
|
416
|
-
|
|
424
|
+
qualified_names = qualified_names or {}
|
|
425
|
+
|
|
426
|
+
def _render_name(raw_name: str, has_bus_config: bool, marked: bool) -> str:
|
|
417
427
|
"""Return the name string as it will be rendered in the table cell."""
|
|
418
428
|
name = raw_name
|
|
419
429
|
if not has_bus_config:
|
|
420
430
|
name = (name[:11] + "[!]") if len(name) > 11 else f"{name}[!]"
|
|
421
|
-
if
|
|
422
|
-
|
|
423
|
-
|
|
431
|
+
cap = 19 if marked else 20
|
|
432
|
+
if len(name) > cap:
|
|
433
|
+
name = name[: cap - 1] + "…" # ellipsis counts toward the cap
|
|
434
|
+
return name + "*" if marked else name
|
|
424
435
|
|
|
425
|
-
|
|
426
|
-
|
|
436
|
+
marked_rows = [
|
|
437
|
+
(ic.get("manufacturer", ""), ic.get("name", "")) in qualified_names
|
|
427
438
|
for ic in eproms_data
|
|
428
439
|
]
|
|
440
|
+
rendered_names = [
|
|
441
|
+
_render_name(ic.get("name", ""), bool(ic.get("bus-config")), marked)
|
|
442
|
+
for ic, marked in zip(eproms_data, marked_rows)
|
|
443
|
+
]
|
|
429
444
|
name_w = max(13, min(20, max((len(n) for n in rendered_names), default=13)))
|
|
430
445
|
|
|
431
446
|
divider = f"+{'':-<{name_w + 1}}+{'':-<18}+{'':-<6}+{'':-<12}+{'':-<13}+{'':-<6}+"
|
|
@@ -462,6 +477,19 @@ def print_eprom_list_table(eproms_data: list, spec_builder: EpromSpecBuilder):
|
|
|
462
477
|
f"| {name: <{name_w}}| {ic.get('manufacturer', ''): <17}|{ic.get('pin-count', 0): >5} | {chip_id_str: <11}| {type_str_display: <12}| {vpp_str: <5}|" # noqa: E501
|
|
463
478
|
)
|
|
464
479
|
logger.info(divider)
|
|
480
|
+
example = next(
|
|
481
|
+
(
|
|
482
|
+
qualified_names[(ic.get("manufacturer", ""), ic.get("name", ""))]
|
|
483
|
+
for ic, marked in zip(eproms_data, marked_rows)
|
|
484
|
+
if marked
|
|
485
|
+
),
|
|
486
|
+
None,
|
|
487
|
+
)
|
|
488
|
+
if example:
|
|
489
|
+
logger.info(
|
|
490
|
+
"* This name also selects chips that program differently. "
|
|
491
|
+
f"Add @MANUFACTURER to choose one, e.g. {example}"
|
|
492
|
+
)
|
|
465
493
|
|
|
466
494
|
|
|
467
495
|
# Standalone test function
|
|
@@ -15,7 +15,6 @@ import shutil
|
|
|
15
15
|
import statistics
|
|
16
16
|
import time
|
|
17
17
|
from contextlib import contextmanager
|
|
18
|
-
from datetime import datetime
|
|
19
18
|
from pathlib import Path
|
|
20
19
|
from typing import Callable, Dict, Tuple # noqa: UP035
|
|
21
20
|
|
|
@@ -75,6 +74,7 @@ from firestarter.serial_comm import (
|
|
|
75
74
|
WRITE_BUDGET_MAX_S,
|
|
76
75
|
SerialCommunicator,
|
|
77
76
|
)
|
|
77
|
+
from firestarter.timestamps import iso_cet_basic
|
|
78
78
|
from firestarter.utils import extract_hex_to_decimal
|
|
79
79
|
from firestarter.write_blank_guard import (
|
|
80
80
|
incomplete_refusal_text,
|
|
@@ -1383,7 +1383,7 @@ class EpromOperator:
|
|
|
1383
1383
|
# path is to use the actual board name -- but the unit-test
|
|
1384
1384
|
# surface doesn't have a serial connection, so we fall back).
|
|
1385
1385
|
if output_dir is None:
|
|
1386
|
-
timestamp =
|
|
1386
|
+
timestamp = iso_cet_basic()
|
|
1387
1387
|
output_dir = str(
|
|
1388
1388
|
Path(DEFAULT_RUN_OUTPUT_DIR)
|
|
1389
1389
|
/ f"consistency-check-{eprom_name}-unknown-board-{timestamp}"
|
|
@@ -1560,7 +1560,7 @@ class EpromOperator:
|
|
|
1560
1560
|
source_sha = hashlib.sha256(Path(source_image_path).read_bytes()).hexdigest()
|
|
1561
1561
|
|
|
1562
1562
|
if output_dir is None:
|
|
1563
|
-
timestamp =
|
|
1563
|
+
timestamp = iso_cet_basic()
|
|
1564
1564
|
output_dir = str(
|
|
1565
1565
|
Path(DEFAULT_RUN_OUTPUT_DIR)
|
|
1566
1566
|
/ f"write-cycle-{eprom_name}-unknown-board-{timestamp}"
|
|
@@ -1678,7 +1678,7 @@ class EpromOperator:
|
|
|
1678
1678
|
from firestarter.serial_comm import FaultInjectingSerialCommunicator
|
|
1679
1679
|
|
|
1680
1680
|
if output_dir is None:
|
|
1681
|
-
timestamp =
|
|
1681
|
+
timestamp = iso_cet_basic()
|
|
1682
1682
|
output_dir = f"fault-inject-{eprom_name}-{direction}-{timestamp}"
|
|
1683
1683
|
output_path = Path(output_dir)
|
|
1684
1684
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -1945,7 +1945,7 @@ class EpromOperator:
|
|
|
1945
1945
|
return False
|
|
1946
1946
|
|
|
1947
1947
|
if output_dir is None:
|
|
1948
|
-
timestamp =
|
|
1948
|
+
timestamp = iso_cet_basic()
|
|
1949
1949
|
output_dir = f"fault-inject-latency-{fault_form}-{timestamp}"
|
|
1950
1950
|
output_path = Path(output_dir)
|
|
1951
1951
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -2149,7 +2149,7 @@ class EpromOperator:
|
|
|
2149
2149
|
return False
|
|
2150
2150
|
|
|
2151
2151
|
if output_dir is None:
|
|
2152
|
-
timestamp =
|
|
2152
|
+
timestamp = iso_cet_basic()
|
|
2153
2153
|
output_dir = f"connect-cost-{timestamp}"
|
|
2154
2154
|
output_path = Path(output_dir)
|
|
2155
2155
|
output_path.mkdir(parents=True, exist_ok=True)
|
|
@@ -195,3 +195,9 @@ class ChipNotFoundError(Exception):
|
|
|
195
195
|
"""
|
|
196
196
|
|
|
197
197
|
pass
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class AmbiguousChipNameError(ChipNotFoundError):
|
|
201
|
+
"""Raised when a chip name selects database rows that program differently
|
|
202
|
+
(VPP, algorithm, chip ID or pulse width), so no one chip is named. The
|
|
203
|
+
message lists a qualified name for each of them."""
|
|
@@ -41,6 +41,7 @@ from firestarter.exceptions import (
|
|
|
41
41
|
)
|
|
42
42
|
from firestarter.fw_release_gate import require_installable_release
|
|
43
43
|
from firestarter.serial_comm import SerialCommunicator
|
|
44
|
+
from firestarter.timestamps import iso_text_to_cet
|
|
44
45
|
|
|
45
46
|
logger = logging.getLogger("Firmware")
|
|
46
47
|
|
|
@@ -62,7 +63,7 @@ class ReleaseInfo(TypedDict):
|
|
|
62
63
|
version: str # tag_name from GitHub API
|
|
63
64
|
tag: str # raw tag_name (same as version for firmware releases)
|
|
64
65
|
channel: str # "stable" or "prerelease"
|
|
65
|
-
published: str # ISO
|
|
66
|
+
published: str # published_at as ISO 8601 in CET
|
|
66
67
|
asset_url: str # browser_download_url for the board-matching .hex asset
|
|
67
68
|
|
|
68
69
|
|
|
@@ -468,7 +469,7 @@ class FirmwareManager:
|
|
|
468
469
|
version=tag,
|
|
469
470
|
tag=tag,
|
|
470
471
|
channel="prerelease" if is_pre else "stable",
|
|
471
|
-
published=r.get("published_at") or "",
|
|
472
|
+
published=iso_text_to_cet(r.get("published_at") or ""),
|
|
472
473
|
asset_url=asset_url,
|
|
473
474
|
)
|
|
474
475
|
)
|