# Phase 4a - done, bug with "File not found (bl, app, f_test) fixed

This commit is contained in:
Dmitry Akimov 2026-07-06 11:53:48 +03:00
parent 657b3a2e63
commit 3279022829
6 changed files with 108 additions and 112 deletions

View file

@ -1,81 +0,0 @@
# Gate 4a
## Case 1
```bash
2026-07-06 10:07:16,955 DEBUG spsdk.mboot.protocol.bulk_protocol IN [1016]: 03, 00, 0C, 00, A0, 00, 00, 02, 00, 00, 00, 00, 11, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00
2026-07-06 10:07:16,955 DEBUG spsdk.mboot.mcuboot RX-PACKET: Tag=GenericResponse, Status=Success, Cmd=ConfigureMemory
2026-07-06 10:07:16,955 INFO spsdk.mboot.mcuboot CMD: Status: 0 (0x0) Success.
2026-07-06 10:07:16,956 INFO spsdk.mboot.mcuboot CMD: FlashEraseAll(mem_id=9)
2026-07-06 10:07:16,956 DEBUG spsdk.mboot.mcuboot TX-PACKET: Tag=FlashEraseAll, Flags=0x00, P[0]=0x00000009
2026-07-06 10:07:16,956 DEBUG spsdk.mboot.protocol.bulk_protocol OUT[12]: 01, 00, 08, 00, 01, 00, 00, 01, 09, 00, 00, 00
2026-07-06 10:07:16,956 DEBUG libusbsio.hidapi.dev HID device 3185030394352 writing[12]: 0x010008000100000109000000
2026-07-06 10:07:16,957 DEBUG libusbsio.hidapi.dev HID device 3185030394352 wrote 1016 bytes
2026-07-06 10:07:21,297 DEBUG libusbsio.hidapi.dev HID device 3185030394352 read[-1]: None
2026-07-06 10:07:21,297 DEBUG spsdk.utils.interfaces.device.usb_device Cannot read from HID device, error=-1
2026-07-06 10:07:21,297 DEBUG spsdk.mboot.mcuboot RX-PACKET: No Response, Timeout Error !
2026-07-06 10:07:21,298 DEBUG spsdk.mboot.mcuboot RX-PACKET: Tag=0x01, Flags=0x00 [14, 27, 00, 00]
2026-07-06 10:07:21,298 INFO spsdk.mboot.mcuboot CMD: Status: 10004 (0x2714) No response packet from target device.
2026-07-06 10:07:21,299 INFO libusbsio Loading SIO library: C:\Development\IMXRT\tft_manufacture_test\tools\production\.venv\Lib\site-packages\libusbsio\bin\x64\libusbsio.dll
2026-07-06 10:07:21,347 INFO libusbsio HID enumeration[3185065344192]: initialized
2026-07-06 10:07:21,347 DEBUG libusbsio HID enumeration[3185065344192]: device #0: USB Receiver
2026-07-06 10:07:21,347 DEBUG libusbsio HID enumeration[3185065344192]: device #1: USB Receiver
2026-07-06 10:07:21,347 DEBUG libusbsio HID enumeration[3185065344192]: device #2: USB Receiver
2026-07-06 10:07:21,347 DEBUG libusbsio HID enumeration[3185065344192]: device #3: USB Receiver
2026-07-06 10:07:21,347 DEBUG libusbsio HID enumeration[3185065344192]: device #4: USB Receiver
2026-07-06 10:07:21,348 DEBUG libusbsio HID enumeration[3185065344192]: device #5: USB Receiver
2026-07-06 10:07:21,348 DEBUG libusbsio HID enumeration[3185065344192]: device #6: HIDI2C Device
2026-07-06 10:07:21,348 DEBUG libusbsio HID enumeration[3185065344192]: device #7: USB Receiver
2026-07-06 10:07:21,348 DEBUG libusbsio HID enumeration[3185065344192]: device #8: HIDI2C Device
2026-07-06 10:07:21,348 DEBUG libusbsio HID enumeration[3185065344192]: device #9: USB Receiver
2026-07-06 10:07:21,348 DEBUG libusbsio HID enumeration[3185065344192]: device #10: HIDI2C Device
2026-07-06 10:07:21,348 INFO libusbsio HID enumeration[3185065344192]: finished, total 11 devices
2026-07-06 10:07:21,348 INFO libusbsio SIO library unloaded
2026-07-06 10:07:21,348 INFO spsdk.mboot.mcuboot Closing: identifier='usb', device=USB COMPOSITE DEVICE (0x15A2, 0x0073)path=b'\\\\?\\hid#vid_15a2&pid_0073#7&100c291f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}' sn=''
2026-07-06 10:07:21,348 DEBUG spsdk.utils.interfaces.device.usb_device Closing the Interface: USB COMPOSITE DEVICE (0x15A2, 0x0073)path=b'\\\\?\\hid#vid_15a2&pid_0073#7&100c291f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}' sn=''
2026-07-06 10:07:21,348 INFO libusbsio.hidapi.dev HID device 0x2e5929af1f0 closed
2026-07-06 10:07:21,348 DEBUG spsdk.utils.interfaces.device.usb_device Closing the Interface: USB COMPOSITE DEVICE (0x15A2, 0x0073)path=b'\\\\?\\hid#vid_15a2&pid_0073#7&100c291f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}' sn=''
2026-07-06 10:07:21,349 ERROR app.flasher erase_chip: flash_erase_all вернул False (устройство пропало с шины)
2026-07-06 10:07:21,351 INFO libusbsio SIO library unloaded
```
## Case 2
```bash
2026-07-06 11:11:37,016 DEBUG spsdk.mboot.protocol.bulk_protocol IN [1016]: 03, 00, 0C, 00, A0, 00, 00, 02, 00, 00, 00, 00, 11, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00, 00
2026-07-06 11:11:37,016 DEBUG spsdk.mboot.mcuboot RX-PACKET: Tag=GenericResponse, Status=Success, Cmd=ConfigureMemory
2026-07-06 11:11:37,017 INFO spsdk.mboot.mcuboot CMD: Status: 0 (0x0) Success.
2026-07-06 11:11:37,018 INFO spsdk.mboot.mcuboot CMD: FlashEraseRegion(address=0x60000000, length=143360, mem_id=0)
2026-07-06 11:11:37,018 DEBUG spsdk.mboot.mcuboot TX-PACKET: Tag=FlashEraseRegion, Flags=0x00, P[0]=0x60000000, P[1]=0x00023000, P[2]=0x00000000
2026-07-06 11:11:37,018 DEBUG spsdk.mboot.protocol.bulk_protocol OUT[20]: 01, 00, 10, 00, 02, 00, 00, 03, 00, 00, 00, 60, 00, 30, 02, 00, 00, 00, 00, 00
2026-07-06 11:11:37,018 DEBUG libusbsio.hidapi.dev HID device 2020961928848 writing[20]: 0x0100100002000003000000600030020000000000
2026-07-06 11:11:37,018 DEBUG libusbsio.hidapi.dev HID device 2020961928848 wrote 1016 bytes
2026-07-06 11:11:37,301 DEBUG libusbsio.hidapi.dev HID device 2020961928848 read[-1]: None
2026-07-06 11:11:37,301 DEBUG spsdk.utils.interfaces.device.usb_device Cannot read from HID device, error=-1
2026-07-06 11:11:37,301 DEBUG spsdk.mboot.mcuboot RX-PACKET: No Response, Timeout Error !
2026-07-06 11:11:37,301 DEBUG spsdk.mboot.mcuboot RX-PACKET: Tag=0x02, Flags=0x00 [14, 27, 00, 00]
2026-07-06 11:11:37,301 INFO spsdk.mboot.mcuboot CMD: Status: 10004 (0x2714) No response packet from target device.
2026-07-06 11:11:37,301 INFO libusbsio Loading SIO library: C:\Development\IMXRT\tft_manufacture_test\tools\production\.venv\Lib\site-packages\libusbsio\bin\x64\libusbsio.dll
2026-07-06 11:11:37,349 INFO libusbsio HID enumeration[2020961011232]: initialized
2026-07-06 11:11:37,349 DEBUG libusbsio HID enumeration[2020961011232]: device #0: USB Receiver
2026-07-06 11:11:37,349 DEBUG libusbsio HID enumeration[2020961011232]: device #1: USB Receiver
2026-07-06 11:11:37,349 DEBUG libusbsio HID enumeration[2020961011232]: device #2: USB Receiver
2026-07-06 11:11:37,349 DEBUG libusbsio HID enumeration[2020961011232]: device #3: USB Receiver
2026-07-06 11:11:37,349 DEBUG libusbsio HID enumeration[2020961011232]: device #4: USB Receiver
2026-07-06 11:11:37,349 DEBUG libusbsio HID enumeration[2020961011232]: device #5: USB Receiver
2026-07-06 11:11:37,349 DEBUG libusbsio HID enumeration[2020961011232]: device #6: HIDI2C Device
2026-07-06 11:11:37,349 DEBUG libusbsio HID enumeration[2020961011232]: device #7: USB Receiver
2026-07-06 11:11:37,350 DEBUG libusbsio HID enumeration[2020961011232]: device #8: HIDI2C Device
2026-07-06 11:11:37,350 DEBUG libusbsio HID enumeration[2020961011232]: device #9: USB Receiver
2026-07-06 11:11:37,350 DEBUG libusbsio HID enumeration[2020961011232]: device #10: HIDI2C Device
2026-07-06 11:11:37,350 INFO libusbsio HID enumeration[2020961011232]: finished, total 11 devices
2026-07-06 11:11:37,350 INFO libusbsio SIO library unloaded
2026-07-06 11:11:37,350 INFO spsdk.mboot.mcuboot Closing: identifier='usb', device=USB COMPOSITE DEVICE (0x15A2, 0x0073)path=b'\\\\?\\hid#vid_15a2&pid_0073#7&100c291f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}' sn=''
2026-07-06 11:11:37,350 DEBUG spsdk.utils.interfaces.device.usb_device Closing the Interface: USB COMPOSITE DEVICE (0x15A2, 0x0073)path=b'\\\\?\\hid#vid_15a2&pid_0073#7&100c291f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}' sn=''
2026-07-06 11:11:37,350 INFO libusbsio.hidapi.dev HID device 0x1d68ab7c690 closed
2026-07-06 11:11:37,350 DEBUG spsdk.utils.interfaces.device.usb_device Closing the Interface: USB COMPOSITE DEVICE (0x15A2, 0x0073)path=b'\\\\?\\hid#vid_15a2&pid_0073#7&100c291f&0&0000#{4d1e55b2-f16f-11cf-88cb-001111000030}' sn=''
2026-07-06 11:11:37,350 ERROR app.flasher flash: flash_erase_region вернул False (устройство пропало с шины)
2026-07-06 11:11:37,354 INFO libusbsio SIO library unloaded
```

View file

@ -64,16 +64,21 @@ class ServiceApp(App):
""" """
После прошивки: После прошивки:
- firmware_test + успех PostFlashScreen (промпт смены BootMode) - firmware_test + успех PostFlashScreen (промпт смены BootMode)
- всё остальное (production/custom, либо ошибка) WaitingScreen - production/custom + успех WaitingScreen
- target=None маркер потери соединения (см. ConnectionWatcherMixin) - target=None обрыв USB (watcher в простое ИЛИ backend во время
WaitingScreen с явной причиной возврата активной операции, см. models.FlashResult, Фаза 4a вариант 2)
WaitingScreen с причиной (конкретный текст, если есть, иначе
общий fallback)
Логическая ошибка (плата на месте) сюда не долетает вовсе
FlashScreen в этом случае не покидает себя (см. _do_flash/_do_erase).
""" """
if event.preset is not None: if event.preset is not None:
self._last_flash_preset = event.preset self._last_flash_preset = event.preset
if event.target is None and not event.success: if event.target is None and not event.success:
self.switch_screen( WaitingScreen(
WaitingScreen(disconnect_reason="Соединение с платой потеряно") disconnect_reason=event.error_message or "Соединение с платой потеряно"
) )
return return

View file

@ -40,7 +40,7 @@ from pathlib import Path
from typing import Awaitable, Callable, Optional from typing import Awaitable, Callable, Optional
from . import flash_backend from . import flash_backend
from .models import FcbVariant, FlashProgress, FlashTarget from .models import FcbVariant, FlashProgress, FlashResult, FlashTarget
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -159,13 +159,14 @@ class Flasher:
async_progress_cb: Optional[ProgressCallback], async_progress_cb: Optional[ProgressCallback],
sync_progress_cb: Optional[flash_backend.ProgressCallback], sync_progress_cb: Optional[flash_backend.ProgressCallback],
**kwargs: object, **kwargs: object,
) -> bool: ) -> FlashResult:
"""Выполнить flash_backend.flash()/erase_chip() в потоке. """Выполнить flash_backend.flash()/erase_chip() в потоке.
flash_backend поднимает FlashBackendError (включая ConnectionLostError, flash_backend поднимает FlashBackendError (включая ConnectionLostError,
см. Фазу 4) вместо возврата False здесь это конвертируется обратно см. Фазу 4) вместо возврата False здесь это конвертируется обратно
в контракт Flasher (bool + событие phase="error"), который был у в контракт Flasher (FlashResult + событие phase="error"). connection_lost
subprocess-версии (ненулевой returncode). транслируется из exc.connection_lost (Фаза 4a, вариант 2) экран
различает физический обрыв от логической ошибки без парсинга текста.
Отдельный except Exception safety net (Фаза 4, согласовано): Отдельный except Exception safety net (Фаза 4, согласовано):
любое непредвиденное исключение из worker-потока (не только любое непредвиденное исключение из worker-потока (не только
@ -176,7 +177,7 @@ class Flasher:
""" """
try: try:
await asyncio.to_thread(func, *args, progress_cb=sync_progress_cb, **kwargs) await asyncio.to_thread(func, *args, progress_cb=sync_progress_cb, **kwargs)
return True return FlashResult(ok=True)
except flash_backend.FlashBackendError as exc: except flash_backend.FlashBackendError as exc:
logger.error("%s: %s", getattr(func, "__name__", func), exc) logger.error("%s: %s", getattr(func, "__name__", func), exc)
if async_progress_cb is not None: if async_progress_cb is not None:
@ -185,7 +186,7 @@ class Flasher:
phase="error", percent=0, message=_format_error_message(exc) phase="error", percent=0, message=_format_error_message(exc)
) )
) )
return False return FlashResult(ok=False, connection_lost=exc.connection_lost)
except Exception as exc: # noqa: BLE001 — safety net, см. docstring except Exception as exc: # noqa: BLE001 — safety net, см. docstring
logger.exception( logger.exception(
"%s: непредвиденная ошибка", getattr(func, "__name__", func) "%s: непредвиденная ошибка", getattr(func, "__name__", func)
@ -198,19 +199,19 @@ class Flasher:
message=f"Непредвиденная ошибка: {exc}", message=f"Непредвиденная ошибка: {exc}",
) )
) )
return False return FlashResult(ok=False, connection_lost=False)
# ── Erase ──────────────────────────────────────────────────────────── # ── Erase ────────────────────────────────────────────────────────────
async def erase_chip( async def erase_chip(
self, self,
progress_cb: Optional[ProgressCallback] = None, progress_cb: Optional[ProgressCallback] = None,
) -> bool: ) -> FlashResult:
""" """
Chip erase Flash через USB SDP (flash_backend.erase_chip). Chip erase Flash через USB SDP (flash_backend.erase_chip).
Занимает ~30 с для W25Q128. FCB будет стёрт. Занимает ~30 с для W25Q128. FCB будет стёрт.
:return: True при успехе. :return: FlashResult(ok, connection_lost) см. models.FlashResult.
""" """
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
sync_cb = _make_sync_progress_cb(progress_cb, loop) sync_cb = _make_sync_progress_cb(progress_cb, loop)
@ -229,7 +230,7 @@ class Flasher:
bin_path: Optional[Path] = None, bin_path: Optional[Path] = None,
use_dcd: bool = False, use_dcd: bool = False,
fcb_variant: FcbVariant = FcbVariant.W25Q128, fcb_variant: FcbVariant = FcbVariant.W25Q128,
) -> bool: ) -> FlashResult:
""" """
Запустить прошивку через flash_backend. Запустить прошивку через flash_backend.
@ -242,7 +243,7 @@ class Flasher:
при сборке HAB-образа через HabImage. при сборке HAB-образа через HabImage.
:param fcb_variant: Только для CUSTOM какой явный FCB-блоб (dcd/*_fdcb.bin) :param fcb_variant: Только для CUSTOM какой явный FCB-блоб (dcd/*_fdcb.bin)
записать в Flash[0x60000000] вместо auto-config. записать в Flash[0x60000000] вместо auto-config.
:return: True при успехе. :return: FlashResult(ok, connection_lost) см. models.FlashResult.
""" """
loop = asyncio.get_running_loop() loop = asyncio.get_running_loop()
sync_cb = _make_sync_progress_cb(progress_cb, loop) sync_cb = _make_sync_progress_cb(progress_cb, loop)
@ -261,20 +262,20 @@ class Flasher:
) )
elif target == FlashTarget.PRODUCTION: elif target == FlashTarget.PRODUCTION:
ok = await self._run_flash_op( result = await self._run_flash_op(
flash_backend.flash, flash_backend.flash,
_firmware_hab_path("bootloader"), _firmware_hab_path("bootloader"),
async_progress_cb=progress_cb, async_progress_cb=progress_cb,
sync_progress_cb=sync_cb, sync_progress_cb=sync_cb,
) )
if ok: if result.ok:
ok = await self._run_flash_op( result = await self._run_flash_op(
flash_backend.flash, flash_backend.flash,
_firmware_hab_path("app"), _firmware_hab_path("app"),
async_progress_cb=progress_cb, async_progress_cb=progress_cb,
sync_progress_cb=sync_cb, sync_progress_cb=sync_cb,
) )
return ok return result
elif target == FlashTarget.CUSTOM: elif target == FlashTarget.CUSTOM:
if bin_path is None: if bin_path is None:
@ -283,7 +284,7 @@ class Flasher:
bin_path, use_dcd, fcb_variant, progress_cb, sync_cb bin_path, use_dcd, fcb_variant, progress_cb, sync_cb
) )
return False return FlashResult(ok=False)
async def _flash_custom( async def _flash_custom(
self, self,
@ -292,7 +293,7 @@ class Flasher:
fcb_variant: FcbVariant, fcb_variant: FcbVariant,
progress_cb: Optional[ProgressCallback], progress_cb: Optional[ProgressCallback],
sync_cb: Optional[flash_backend.ProgressCallback], sync_cb: Optional[flash_backend.ProgressCallback],
) -> bool: ) -> FlashResult:
""" """
Прошить «сырой» (не-HAB) кастомный бинарник из custom_binaries/. Прошить «сырой» (не-HAB) кастомный бинарник из custom_binaries/.
@ -311,7 +312,9 @@ class Flasher:
raw_bin_path, use_dcd, progress_cb, sync_cb raw_bin_path, use_dcd, progress_cb, sync_cb
) )
if hab_bin is None: if hab_bin is None:
return False # Ошибка сборки HAB — чисто локальная операция (HabImage,
# временный файл), к USB-соединению отношения не имеет.
return FlashResult(ok=False)
fcb_path = flash_backend.fcb_blob_path(fcb_variant.fcb_filename) fcb_path = flash_backend.fcb_blob_path(fcb_variant.fcb_filename)
try: try:

View file

@ -135,3 +135,22 @@ class FlashProgress:
# | "reset" | "hab_build" | "done" | "error" # | "reset" | "hab_build" | "done" | "error"
percent: int # 0..100 percent: int # 0..100
message: str message: str
@dataclass(frozen=True)
class FlashResult:
"""Результат Flasher.flash()/erase_chip() (Фаза 4a, вариант 2).
Раньше публичный API Flasher() возвращал голый bool экран не мог
отличить физический обрыв USB от логической ошибки (файл не найден,
битый custom-бинарь, ошибка сборки HAB) и одинаково уводил оператора
на WaitingScreen в обоих случаях, хотя при логической ошибке плата
остаётся на месте и разумнее остаться на FlashScreen.
connection_lost транслируется из FlashBackendError.connection_lost
(см. flash_backend.py) без парсинга текста сообщения тот же принцип,
что и там.
"""
ok: bool
connection_lost: bool = False

View file

@ -51,6 +51,18 @@ class FlashScreen(Screen, ConnectionWatcherMixin):
4 отчёта). Во время самой прошивки/erase мониторинг приостановлен 4 отчёта). Во время самой прошивки/erase мониторинг приостановлен
обрыв в этом случае обнаруживает сам flash_backend (SPSDKConnectionError обрыв в этом случае обнаруживает сам flash_backend (SPSDKConnectionError
ConnectionLostError, см. Фазу 4) и репортит через #flash-log. ConnectionLostError, см. Фазу 4) и репортит через #flash-log.
FlashResult (Фаза 4a, вариант 2) различает физический обрыв от
логической ошибки без парсинга текста см. models.FlashResult:
- connection_lost=True уходим на WaitingScreen (тот же маркер
target=None, что и watcher-детект в простое), текст ошибки
прокидывается через FlashDone.error_message, чтобы не терять
конкретику ради общего "Соединение с платой потеряно".
- connection_lost=False плата на месте, остаёмся на FlashScreen.
Раньше (до варианта 2) любая ошибка уводила на WaitingScreen,
который почти мгновенно переоткрывал FlashScreen заново (плата
на шине видна) и уничтожал #flash-log раньше, чем оператор
успевал прочитать (см. Гейт 4a, п.0 отчёта).
""" """
BINDINGS = [ BINDINGS = [
@ -63,17 +75,24 @@ class FlashScreen(Screen, ConnectionWatcherMixin):
success: bool, success: bool,
target: Optional[FlashTarget] = None, target: Optional[FlashTarget] = None,
preset: Optional[FlashPreset] = None, preset: Optional[FlashPreset] = None,
error_message: Optional[str] = None,
) -> None: ) -> None:
super().__init__() super().__init__()
self.success = success self.success = success
self.target = target self.target = target
self.preset = preset self.preset = preset
# Заполняется только при connection_lost=True — конкретный текст
# обрыва вместо общего "Соединение с платой потеряно" в app.py.
# При логической ошибке экран никуда не уходит (см. _do_flash),
# и это поле не используется.
self.error_message = error_message
def __init__(self, preset: Optional[FlashPreset] = None, **kwargs) -> None: def __init__(self, preset: Optional[FlashPreset] = None, **kwargs) -> None:
super().__init__(**kwargs) super().__init__(**kwargs)
self._flasher = Flasher() self._flasher = Flasher()
self._flashing = False self._flashing = False
self._preset = preset or FlashPreset() self._preset = preset or FlashPreset()
self._last_error_message: Optional[str] = None
def compose(self) -> ComposeResult: def compose(self) -> ComposeResult:
with AppFrame(id="flash-frame"): with AppFrame(id="flash-frame"):
@ -217,10 +236,11 @@ class FlashScreen(Screen, ConnectionWatcherMixin):
async def _do_flash( async def _do_flash(
self, target: FlashTarget, bin_path: Optional[Path], preset: FlashPreset self, target: FlashTarget, bin_path: Optional[Path], preset: FlashPreset
) -> None: ) -> None:
self._last_error_message = None
self._set_busy(True) self._set_busy(True)
self._show_progress(True) self._show_progress(True)
self._log(f"▶ Прошивка: {target.value}") self._log(f"▶ Прошивка: {target.value}")
ok = await self._flasher.flash( result = await self._flasher.flash(
target=target, target=target,
bin_path=bin_path, bin_path=bin_path,
use_dcd=preset.use_dcd, use_dcd=preset.use_dcd,
@ -228,19 +248,47 @@ class FlashScreen(Screen, ConnectionWatcherMixin):
progress_cb=self._on_progress, progress_cb=self._on_progress,
) )
self._set_busy(False) self._set_busy(False)
self._finish_progress(ok) self._finish_progress(result.ok)
self._log("✅ Готово" if ok else "❌ Ошибка") self._log("✅ Готово" if result.ok else "❌ Ошибка")
self.post_message(self.FlashDone(success=ok, target=target, preset=preset))
if result.ok:
self.post_message(
self.FlashDone(success=True, target=target, preset=preset)
)
elif result.connection_lost:
# Обрыв во время активной прошивки — watcher в это время приглушён
# (см. _check_sdp_present), обрыв обнаружил сам backend. Тот же
# маркер target=None, что и у watcher-детекта в простое (Гейт 4a).
self.post_message(
self.FlashDone(
success=False,
target=None,
preset=preset,
error_message=self._last_error_message,
)
)
# else: логическая ошибка, плата на месте (вариант 2, Гейт 4a) —
# остаёмся на FlashScreen. Сообщение уже в #flash-log (_on_progress),
# кнопки уже разблокированы (_set_busy(False) выше).
@work(exclusive=True, thread=False) @work(exclusive=True, thread=False)
async def _do_erase(self) -> None: async def _do_erase(self) -> None:
self._last_error_message = None
self._set_busy(True) self._set_busy(True)
self._show_progress(True) self._show_progress(True)
self._log("⚠ Очистка памяти (~30 с)...") self._log("⚠ Очистка памяти (~30 с)...")
ok = await self._flasher.erase_chip(progress_cb=self._on_progress) result = await self._flasher.erase_chip(progress_cb=self._on_progress)
self._set_busy(False) self._set_busy(False)
self._finish_progress(ok) self._finish_progress(result.ok)
self._log("✅ Очистка памяти завершена" if ok else "❌ Очистка памяти: ошибка") self._log(
"✅ Очистка памяти завершена" if result.ok else "❌ Очистка памяти: ошибка"
)
if result.connection_lost:
self.post_message(
self.FlashDone(
success=False, target=None, error_message=self._last_error_message
)
)
# ── Вспомогательные ─────────────────────────────────────────────────────── # ── Вспомогательные ───────────────────────────────────────────────────────
@ -273,6 +321,8 @@ class FlashScreen(Screen, ConnectionWatcherMixin):
bar = self.query_one("#flash-progress-bar", ProgressBar) bar = self.query_one("#flash-progress-bar", ProgressBar)
bar.update(total=100, progress=progress.percent) bar.update(total=100, progress=progress.percent)
self._log(progress.message) self._log(progress.message)
if progress.phase == "error":
self._last_error_message = progress.message
def _show_progress(self, visible: bool) -> None: def _show_progress(self, visible: bool) -> None:
"""Показать/скрыть прогресс-бар. Скрыт в простое — без анимации.""" """Показать/скрыть прогресс-бар. Скрыт в простое — без анимации."""

View file

@ -449,4 +449,4 @@ service-tui-vX.Y.Z-<os>/
документация, `.env.example`). документация, `.env.example`).
**Старт (после подтверждения Р7 и, желательно, измерения О1):** **Старт (после подтверждения Р7 и, желательно, измерения О1):**
фаза 0 — diff `pyproject.toml` + `spike_hab.py` + `spike_flash.py`. фаза 0 — diff `pyproject.toml` + `spike_hab.py` + `spike_flash.py`.