๐Ÿ”ง PiDebugger v2.1

๐Ÿ“ Pi Zero W + ๐Ÿ”ฒ HyperPixel 2.1โ€ Round 480ร—480 = ๐ŸŽฏ Multi-Target ARM Debugger

Outil portable basรฉ sur Raspberry Pi Zero W et รฉcran HyperPixel 2.1โ€ Round (480ร—480 @ 60fps) pour le debug, monitoring et installation de plateformes SoC ARM Marvell/Globalscale.

๐ŸŽฏ Targets Supportรฉs

Emoji Target SoC CPU RAM Rรฉseau
โ˜• ESPRESSObin V7 Armada 3720 2ร—Cortex-A53 @ 1.0GHz 1-2GB DDR4 3ร—GbE
๐Ÿš€ ESPRESSObin Ultra Armada 3720 2ร—Cortex-A53 @ 1.2GHz 1GB DDR4 1WAN+4LAN PoE
๐Ÿซ MOCHAbin Armada 7040 4ร—Cortex-A72 @ 1.4GHz 2-8GB DDR4 10G SFP+ 4ร—GbE
๐Ÿ”Œ Sheeva64 Armada 3720 2ร—Cortex-A53 @ 1.2GHz 1GB DDR4 2ร—GbE

โœจ Fonctionnalitรฉs principales

Emoji Fonction Description
๐Ÿ• Horloge ร‰cran de veille avec heure/date et status
๐Ÿ“Š Dashboard Menu principal avec 9 boutons circulaires
๐Ÿ’ป Terminal UART Moniteur sรฉrie temps rรฉel colorรฉ avec parser ATF
๐Ÿš€ Boot Analyzer Timeline circulaire + Hardware info + ATF stages
๐Ÿ”‹ Power Control Sรฉlection target + alimentation USB + graphiques V/A
๐Ÿ“ File Browser Explorateur USB Mass Storage + export logs
๐ŸŒ Network Status/WiFi/TFTP avec authentification WPA2/WPA3
๐Ÿ“ค XMODEM Transfert fichiers via UART (CRC-16, 128B packets)
๐Ÿ›ก๏ธ UEFI Shell 6 commandes interactives
โš™๏ธ Settings 4 thรจmes + sons + gestures + stats systรจme

๐Ÿท๏ธ Lรฉgende des emojis

๐Ÿ“Š Status

  • โœ… OK / Succรจs
  • โŒ Erreur
  • โš ๏ธ Warning
  • ๐ŸŸข Actif / ON
  • ๐Ÿ”ด Inactif / OFF
  • ๐ŸŸก Standby
  • ๐Ÿ”„ En cours

๐Ÿ”ฒ Hardware

  • ๐Ÿ“ Raspberry Pi (Master)
  • โ˜• ESPRESSObin V7
  • ๐Ÿš€ ESPRESSObin Ultra
  • ๐Ÿซ MOCHAbin
  • ๐Ÿ”Œ Sheeva64
  • โšก CPU / Power
  • ๐Ÿ’พ RAM / Storage
  • ๐Ÿ”Œ USB
  • ๐Ÿ“ก Serial/UART
  • ๐ŸŒ Network
  • ๐Ÿ”‹ Power
  • ๐Ÿง  SoC/Chip

๐Ÿš€ Boot Stages

  • ๐Ÿ”’ BootROM
  • ๐Ÿ”‘ WTMI
  • ๐Ÿ›ก๏ธ ATF (ARM Trusted Firmware)
  • ๐Ÿ“ฆ SPL (Secondary Program Loader)
  • ๐Ÿฅพ U-Boot
  • ๐Ÿง Linux Kernel
  • ๐Ÿ‘ค Login

๐Ÿ”— USB & Connexions

  • ๐Ÿ”— USB Gadget (composite device)
  • ๐Ÿ“Ÿ TTY Serial (/dev/ttyGS0)
  • ๐Ÿ“ก UART liaison sรฉrie
  • ๐Ÿ“ฅ RX (rรฉception donnรฉes)
  • ๐Ÿ“ค TX (envoi donnรฉes)
  • ๐Ÿ“ถ Signal WiFi
  • ๐ŸŽฏ Target (cible SoC)

๐ŸŽฌ Actions

  • โ–ถ๏ธ Play / Run
  • โธ๏ธ Pause / Stop
  • ๐Ÿ”„ Restart / Loading
  • โ—€๏ธ Back (retour)
  • ๐Ÿงน Clear (effacer)
  • ๐Ÿ” Search (recherche)

๐Ÿ—๏ธ Architecture du projet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿ”ฒ HyperPixel 2.1" Round Touch โ”‚
โ”‚ 480ร—480 @ 60fps โ€ข Capacitif โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๐Ÿ“ Raspberry Pi Zero W (MASTER) โ”‚
โ”‚ WiFi 802.11n โ€ข Bluetooth 4.1 โ€ข USB OTG Gadget โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ UART + USB Power โ”‚
โ”‚ GPIO14/15 (TX/RX) + USB 5V Control โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๐ŸŽฏ TARGET (1 de 4) โ”‚
โ”‚ โ˜• ESPRESSObin V7 โ”‚ ๐Ÿš€ ESPRESSObin Ultra โ”‚
โ”‚ Armada 3720 โ”‚ Armada 3720 + PoE โ”‚
โ”‚ 2ร—A53 @ 1.0GHz โ”‚ 2ร—A53 @ 1.2GHz โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๐Ÿซ MOCHAbin โ”‚ ๐Ÿ”Œ Sheeva64 โ”‚
โ”‚ Armada 7040 โ”‚ Armada 3720 โ”‚
โ”‚ 4ร—A72 @ 1.4GHz โ”‚ 2ร—A53 @ 1.2GHz โ”‚
โ”‚ 10G SFP+ โ”‚ Wall Plug Form Factor โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Sรฉquences de Boot par Target

Armada 3720 (ESPRESSObin/Ultra/Sheeva64):

1
๐Ÿ”’ BootROM โ†’ ๐Ÿ”‘ WTMI (CM3) โ†’ ๐Ÿ›ก๏ธ ATF (BL1/BL2/BL31) โ†’ ๐Ÿ“ฆ SPL โ†’ ๐Ÿฅพ U-Boot โ†’ ๐Ÿง Linux

Armada 7040 (MOCHAbin):

1
๐Ÿ”’ AP806 BootROM โ†’ ๐Ÿ›ก๏ธ ATF (BL1/BL2/BL31 PSCI/SCMI) โ†’ ๐Ÿ“ฆ SPL โ†’ ๐Ÿฅพ U-Boot โ†’ ๐Ÿง Linux

๐Ÿ“Š Mini-Dashboards de Status

Lโ€™interface affiche en permanence lโ€™รฉtat du systรจme via deux mini-dashboards :

๐Ÿ“ MASTER (Pi Zero W)

Emoji Indicateur Description
๐Ÿ”— Gadget USB Gadget composite actif
๐Ÿ“Ÿ TTY Serial /dev/ttyGS0 disponible
๐Ÿ’ฟ Storage Mass Storage 512MB montรฉ
๐Ÿ“ถ WiFi Connexion rรฉseau active
๐ŸŒก๏ธ Temp Tempรฉrature CPU

๐ŸŽฏ TARGET (SoC Cible)

Emoji Indicateur Description
๐Ÿ”‹ Power Alimentation USB/externe
๐Ÿ”Œ USB Connexion USB dรฉtectรฉe
๐Ÿ“ก Serial Liaison UART active
๐Ÿ”„/โœ…/๐ŸŸก State Booting / Ready / Idle

๐Ÿ“ Affichage par รฉcran

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
โ”Œโ”€ Dashboard โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ ๐Ÿ“ MASTER โ”‚ โ”‚ ๐ŸŽฏ TARGET โ”‚ โ”‚
โ”‚ โ”‚ ๐Ÿ”—๐ŸŸข ๐Ÿ“Ÿ๐ŸŸข ๐Ÿ’ฟ๐ŸŸขโ”‚ โ”‚ ๐Ÿ”‹๐ŸŸข ๐Ÿ”Œ๐ŸŸข ๐Ÿ“ก๐ŸŸขโ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ โ”‚
โ”‚ ๐Ÿ”ง PiDebugger โ”‚
โ”‚ [๐Ÿ•] [๐Ÿ’ป] [๐Ÿš€] [๐Ÿ”‹] [๐Ÿ“] [โš™๏ธ] โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€ Serial Monitor โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ ๐Ÿ’ป Serial Monitor โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚ โ”‚ ๐Ÿ”‹๐ŸŸข ๐Ÿ”—๐ŸŸข ๐Ÿ“Ÿ๐ŸŸข ๐Ÿ“ก๐ŸŸข [โ–ถ๏ธ RUN] โ”‚โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”โ”‚
โ”‚ โ”‚ ๐Ÿฅพ U-Boot 2024.01 โ”‚โ”‚
โ”‚ โ”‚ โ˜• ESPRESSOBin Board โ”‚โ”‚
โ”‚ โ”‚ โšก Armada 3720 @ 1000 MHz โ”‚โ”‚
โ”‚ โ”‚ ... โ”‚โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜โ”‚
โ”‚ ๐Ÿ“ฅ 1,234 bytes | โ„น๏ธ 42 lines โ”‚
โ”‚ [๐Ÿงน CLR] [โ—€๏ธ BACK] โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

โ”Œโ”€ Clock โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ”‚
โ”‚ 14:32 โ”‚
โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚
โ”‚ โ”‚ ๐Ÿ• โ”‚ โ”‚
โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚
โ”‚ 08 dรฉc. 2025 โ”‚
โ”‚ โ”‚
โ”‚ ๐Ÿ“๐ŸŸข ๐ŸŽฏ๐Ÿ”ด ๐Ÿ“ก๐Ÿ”ด โ”‚
โ”‚ โ–ถ๏ธ Touch to continue โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐ŸŽฏ Targets ARM Supportรฉs

PiDebugger v2.1 supporte 4 plateformes ARM Marvell/Globalscale avec sรฉquences de boot spรฉcifiques.

โ˜• ESPRESSObin V7

Le board de rรฉfรฉrence pour le dรฉveloppement Marvell Armada.

Caractรฉristique Valeur
SoC Marvell Armada 3720 (88F3720)
CPU 2ร— ARM Cortex-A53 @ 1.0 GHz
RAM 1-2 GB DDR4 @ 800 MHz
Storage microSD, eMMC (option), SPI NOR 4MB
Network 3ร— Gigabit Ethernet (Topaz 6341 Switch)
USB 1ร— USB 3.0, 1ร— USB 2.0, micro-USB console
Power 5V/2A micro-USB ou 12V DC jack
UART 115200 8N1 via micro-USB (FTDI)

Sรฉquence de boot:

1
๐Ÿ”’ BootROM โ†’ ๐Ÿ”‘ WTMI (CM3) โ†’ ๐Ÿ›ก๏ธ ATF BL1 โ†’ BL2 โ†’ BL31 โ†’ ๐Ÿ“ฆ SPL โ†’ ๐Ÿฅพ U-Boot โ†’ ๐Ÿง Linux

Device Tree: armada-3720-espressobin-v7.dts

๐Ÿš€ ESPRESSObin Ultra

Version amรฉliorรฉe avec PoE et plus de ports rรฉseau.

Caractรฉristique Valeur
SoC Marvell Armada 3720 (88F3720)
CPU 2ร— ARM Cortex-A53 @ 1.2 GHz
RAM 1 GB DDR4
Storage 8GB eMMC, microSD, SPI NOR, M.2 2280
Network 1ร— WAN PoE 30W + 4ร— LAN Gigabit (Topaz 6341)
WiFi 802.11ac 2ร—2 dual-band + BLE 4.2
USB 1ร— USB 3.0, 1ร— USB 2.0
Power 12V/2A DC ou PoE 30W
Form Factor CPE Gateway avec boรฎtier

Sรฉquence de boot:

1
๐Ÿ”’ BootROM โ†’ ๐Ÿ”‘ WTMI โ†’ ๐Ÿ›ก๏ธ ATF โ†’ ๐Ÿ“ฆ SPL โ†’ ๐Ÿฅพ U-Boot โ†’ ๐Ÿง Linux/OpenWrt

Device Tree: armada-3720-espressobin-ultra.dts

๐Ÿซ MOCHAbin

Board haute performance avec 10G SFP+ et quad-core A72.

Caractรฉristique Valeur
SoC Marvell Armada 7040 (88F7040)
CPU 4ร— ARM Cortex-A72 @ 1.4 GHz
RAM 2-8 GB DDR4 ECC
Storage 16GB eMMC, SPI NOR 4MB, M.2 SATA, SATA 7+15
Network 1ร— 10G SFP+, 1ร— 1G SFP, 4ร— GbE (Topaz 88E6141), 1ร— WAN PoE
WiFi 802.11ax WiFi 6 (option) + 5G LTE (option)
USB 2ร— USB 3.0 (via hub)
Power 12V/3A DC
Expansion Mini-PCIe, M.2 2280, M.2 2250, MikroBus

Sรฉquence de boot:

1
๐Ÿ”’ AP806 BootROM โ†’ ๐Ÿ›ก๏ธ ATF BL1 (AP init) โ†’ BL2 (CP110) โ†’ BL31 (PSCI/SCMI) โ†’ ๐Ÿ“ฆ SPL โ†’ ๐Ÿฅพ U-Boot โ†’ ๐Ÿง Linux

Device Tree: armada-7040-mochabin.dts

Note: Le 7040 nโ€™a pas de WTMI (Cortex-M3) contrairement au 3720.

๐Ÿ”Œ Sheeva64

Format โ€œplug computerโ€ compact avec alimentation intรฉgrรฉe.

Caractรฉristique Valeur
SoC Marvell Armada 3720 (88F3720)
CPU 2ร— ARM Cortex-A53 @ 1.2 GHz
RAM 1 GB DDR4
Storage 4GB eMMC, microSD
Network 2ร— Gigabit Ethernet
USB 2ร— USB 2.0 Type-A
Power Wall plug intรฉgrรฉ (110-240V AC)
Console mini-USB UART
WiFi 802.11ac + BLE 4.2 (option)
Form Factor 110 ร— 70 ร— 49mm plug

Sรฉquence de boot:

1
๐Ÿ”’ BootROM โ†’ ๐Ÿ”‘ WTMI โ†’ ๐Ÿ›ก๏ธ ATF โ†’ ๐Ÿ“ฆ SPL โ†’ ๐Ÿฅพ U-Boot โ†’ ๐Ÿง Ubuntu 18.04

Hรฉritage: Successeur 64-bit du SheevaPlug original (Kirkwood ARMv5).

๐Ÿ”„ Comparatif des Targets

Feature โ˜• ESPRESSObin ๐Ÿš€ Ultra ๐Ÿซ MOCHAbin ๐Ÿ”Œ Sheeva64
Cores 2ร—A53 2ร—A53 4ร—A72 2ร—A53
Freq 1.0 GHz 1.2 GHz 1.4 GHz 1.2 GHz
RAM max 2 GB 1 GB 8 GB 1 GB
10G โŒ โŒ โœ… SFP+ โŒ
PoE โŒ โœ… 30W โœ… โŒ
WiFi โŒ โœ… ac โœ… ax Option
WTMI โœ… โœ… โŒ โœ…
Prix ~$50 ~$120 ~$160 ~$90

๐Ÿ”Œ Connexion UART vers Target

1
2
3
4
5
6
7
Pi Zero W (Master)          Target (ESPRESSObin/etc)
โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
GPIO 14 (TXD) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บ RXD (micro-USB ou header)
GPIO 15 (RXD) โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ TXD
GND โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ GND

Paramรจtres: 115200 8N1 (pas de flow control)

โšก Contrรดle Power USB

Le Pi Zero W peut contrรดler lโ€™alimentation du target via :

  1. Relay USB : Module relay contrรดlรฉ par GPIO
  2. USB Hub programmable : Hub avec contrรดle power par port
  3. INA219 : Mesure V/A en temps rรฉel (I2C)
1
2
3
4
5
6
7
# Exemple contrรดle GPIO relay
import RPi.GPIO as GPIO
RELAY_PIN = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(RELAY_PIN, GPIO.OUT)
GPIO.output(RELAY_PIN, GPIO.HIGH) # Power ON
GPIO.output(RELAY_PIN, GPIO.LOW) # Power OFF

๐Ÿ“ฆ Phase 1 : Prรฉparation du systรจme

๐Ÿ“Œ 1.1 ๐Ÿ›’ Matรฉriel requis

Emoji Composant Modรจle
๐Ÿ“ SBC Raspberry Pi Zero W (avec header GPIO)
๐Ÿ”ฒ ร‰cran Pimoroni HyperPixel 2.1โ€ Round Touch
๐Ÿ’พ Stockage Carte microSD 16 Go+ (classe 10)
๐Ÿ”Œ Cรขble USB data (micro-USB โ†’ USB-A)
๐Ÿ”‹ Batterie LiPo 3.7V + module charge (optionnel)

๐Ÿ“Œ 1.2 ๐Ÿ’ฟ Installation Raspberry Pi OS Lite

1
2
3
4
5
6
7
8
# Sur machine hรดte - tรฉlรฉcharger et flasher
wget https://downloads.raspberrypi.com/raspios_lite_armhf/images/raspios_lite_armhf-2024-11-19/2024-11-19-raspios-bookworm-armhf-lite.img.xz

# Flasher avec dd ou balenaEtcher
xzcat 2024-11-19-raspios-bookworm-armhf-lite.img.xz | sudo dd of=/dev/sdX bs=4M status=progress

# Monter la partition boot
sudo mount /dev/sdX1 /mnt/boot

๐Ÿ“Œ 1.3 Configuration headless initiale

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# Activer SSH
sudo touch /mnt/boot/ssh

# Configuration WiFi
cat << 'EOF' | sudo tee /mnt/boot/wpa_supplicant.conf
country=FR
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
ssid="VOTRE_SSID"
psk="VOTRE_PASSWORD"
key_mgmt=WPA-PSK
}
EOF

# Dรฉmonter
sudo umount /mnt/boot

๐Ÿญ Phase 1.5 : Factory Boot Mode

Le PiDebugger dรฉmarre avec une sรฉquence de boot complรจte simulant lโ€™initialisation de tous les composants.

๐Ÿ“‹ Sรฉquence Factory Boot

1
2
3
4
5
6
7
8
9
10
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Phase โ”‚ Durรฉe โ”‚ Description โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ ๐Ÿ“ GPU INIT โ”‚ ~1.2s โ”‚ Logo Raspberry Pi, init BCM2835 โ”‚
โ”‚ โš™๏ธ BOOTLOADER โ”‚ ~0.7s โ”‚ config.txt, dtoverlay, kernel โ”‚
โ”‚ ๐Ÿง LINUX โ”‚ ~1.0s โ”‚ Kernel, dwc2, mmc, EXT4 โ”‚
โ”‚ โ–ถ๏ธ SYSTEMD โ”‚ ~0.5s โ”‚ Services, network, SSH โ”‚
โ”‚ ๐Ÿ”ง FACTORY โ”‚ ~3.0s โ”‚ PiDebugger init (voir dรฉtail) โ”‚
โ”‚ ๐Ÿš€ LAUNCH โ”‚ ~0.6s โ”‚ Transition vers UI โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ”ง Dรฉtail phase FACTORY

La phase Factory initialise tous les composants du PiDebugger :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
โ•‘ ๐Ÿ”ง PIDEBUGGER FACTORY MODE โ•‘
โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

โ–ถ๏ธ [INIT] Loading PiDebugger v1.0
๐Ÿง  [DISP] HyperPixel 2.1" detected
โœ… [DISP] Resolution: 480x480 @ 60Hz
โœ… [DISP] Touch: FT5406 OK

๐Ÿ”— [USB ] Configuring gadget...
๐Ÿ“Ÿ [USB ] ACM serial: /dev/ttyGS0
๐Ÿ’ฟ [USB ] Mass storage: 512MB
โœ… [USB ] Composite gadget active

๐Ÿ”‹ [PWR ] USB OTG power control init
โœ… [PWR ] VBUS detect: enabled
๐ŸŸก [PWR ] Target power: standby

โœ… [STOR] FAT32 PIDEBUGGER mounted
๐ŸŒ [NET ] WiFi: Connected
โœ… [NET ] IP: 192.168.1.42

๐Ÿ” [AUTO] Checking autorun.sh...
โ–ถ๏ธ [AUTO] Executing factory setup...
โ†’ Verifying firmware images
โœ… armbian/espressobin.img
โœ… uboot/flash-image-v7.bin
โ†’ Loading UI theme: LuCI Dark
โ†’ Calibrating touchscreen
โœ… Touch calibration OK
โ†’ Starting UI service...

๐ŸŽ‰ [READY] System initialized
๐Ÿš€ [READY] Launching interface...

๐Ÿ“Š Status progressifs pendant le boot

Les indicateurs Master sโ€™allument progressivement :

ร‰tape ๐Ÿ”— Gadget ๐Ÿ“Ÿ TTY ๐Ÿ’ฟ Storage ๐Ÿ“ถ WiFi
GPU ๐Ÿ”ด ๐Ÿ”ด ๐Ÿ”ด ๐Ÿ”ด
Kernel ๐ŸŸข ๐Ÿ”ด ๐Ÿ”ด ๐Ÿ”ด
Systemd ๐ŸŸข ๐ŸŸข ๐Ÿ”ด ๐Ÿ”ด
Factory ๐ŸŸข ๐ŸŸข ๐ŸŸข ๐Ÿ”ด
Ready ๐ŸŸข ๐ŸŸข ๐ŸŸข ๐ŸŸข

โญ๏ธ Bouton SKIP

Un bouton โ–ถ๏ธ SKIP permet de passer directement ร  lโ€™interface sans attendre la fin du boot.

๐Ÿ“Œ 1.4 Premier boot et mise ร  jour

1
2
3
4
5
6
7
8
9
10
11
# Connexion SSH (trouver l'IP via router ou nmap)
ssh pi@raspberrypi.local
# Password par dรฉfaut: raspberry

# Mise ร  jour systรจme
sudo apt update && sudo apt upgrade -y

# Installer les outils de base
sudo apt install -y git python3-pip python3-pygame python3-dev \
libsdl2-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-ttf-dev \
screen minicom picocom fonts-dejavu-core

๐Ÿ“‹ Phase 2 : Installation driver HyperPixel

๐Ÿ“Œ 2.1 Installation automatique Pimoroni

1
2
3
4
5
6
7
# Cloner et installer
git clone https://github.com/pimoroni/hyperpixel2r
cd hyperpixel2r
sudo ./install.sh

# Redรฉmarrer
sudo reboot

๐Ÿ“Œ 2.2 Configuration รฉcran rond

1
2
3
4
5
# Vรฉrifier que l'รฉcran fonctionne
dmesg | grep -i hyper

# Configurer la rotation si nรฉcessaire (dans /boot/config.txt)
# display_lcd_rotate=2 # 180 degrรฉs

๐Ÿ“Œ 2.3 Test tactile

1
2
3
4
5
# Installer evtest
sudo apt install -y evtest

# Tester le touch
sudo evtest /dev/input/event0

๐Ÿ“‹ Phase 3 : Structure du projet Python

๐Ÿ“Œ 3.1 Arborescence

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/home/pi/pidebugger/
โ”œโ”€โ”€ main.py # Point d'entrรฉe
โ”œโ”€โ”€ config.yaml # Configuration
โ”œโ”€โ”€ ui/
โ”‚ โ”œโ”€โ”€ __init__.py
โ”‚ โ”œโ”€โ”€ core.py # Moteur UI circulaire
โ”‚ โ”œโ”€โ”€ widgets.py # Widgets (jauges, boutons)
โ”‚ โ”œโ”€โ”€ screens/
โ”‚ โ”‚ โ”œโ”€โ”€ clock.py # ร‰cran horloge
โ”‚ โ”‚ โ”œโ”€โ”€ dashboard.py # Dashboard principal
โ”‚ โ”‚ โ”œโ”€โ”€ serial.py # Moniteur sรฉrie
โ”‚ โ”‚ โ”œโ”€โ”€ boot.py # Analyse boot
โ”‚ โ”‚ โ””โ”€โ”€ network.py # Config rรฉseau
โ”‚ โ””โ”€โ”€ themes/
โ”‚ โ””โ”€โ”€ luci.py # Thรจme style OpenWRT
โ”œโ”€โ”€ serial/
โ”‚ โ”œโ”€โ”€ __init__.py
โ”‚ โ”œโ”€โ”€ gadget.py # USB Gadget mode
โ”‚ โ”œโ”€โ”€ console.py # Console sรฉrie
โ”‚ โ””โ”€โ”€ parser.py # Parsers boot (uboot, uefi...)
โ”œโ”€โ”€ analyzers/
โ”‚ โ”œโ”€โ”€ __init__.py
โ”‚ โ”œโ”€โ”€ uboot.py # Analyseur U-Boot
โ”‚ โ”œโ”€โ”€ uefi.py # Analyseur UEFI
โ”‚ โ”œโ”€โ”€ spi.py # Analyseur SPI boot
โ”‚ โ””โ”€โ”€ armbian.py # Analyseur Armbian
โ””โ”€โ”€ assets/
โ”œโ”€โ”€ fonts/
โ””โ”€โ”€ icons/

๐Ÿ“Œ 3.2 Crรฉation du projet

1
2
3
4
5
6
7
mkdir -p /home/pi/pidebugger/{ui/screens,ui/themes,serial,analyzers,assets/fonts,assets/icons}
cd /home/pi/pidebugger

# Crรฉer environnement virtuel (optionnel mais recommandรฉ)
python3 -m venv venv
source venv/bin/activate
pip install pygame pyyaml pyserial

๐Ÿ“‹ Phase 4 : Moteur UI circulaire

๐Ÿ“Œ 4.1 Core UI (ui/core.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
#!/usr/bin/env python3
"""
Moteur d'interface circulaire pour HyperPixel 2.1" Round
480x480 pixels, masque circulaire
"""

import pygame
import math
from typing import Callable, Optional

# Constantes รฉcran
SCREEN_SIZE = 480
CENTER = (SCREEN_SIZE // 2, SCREEN_SIZE // 2)
RADIUS = SCREEN_SIZE // 2

# Couleurs thรจme LuCI
COLORS = {
'bg': (24, 26, 31),
'bg_light': (35, 39, 47),
'bg_dark': (18, 20, 26),
'primary': (0, 149, 218),
'secondary': (76, 175, 80),
'warning': (255, 152, 0),
'danger': (244, 67, 54),
'text': (224, 224, 224),
'text_dim': (128, 128, 128),
'border': (55, 60, 70),
'raspberry': (197, 26, 74),
'power': (255, 87, 34),
'spi': (255, 150, 255),
'uefi': (100, 255, 255),
'uboot': (100, 200, 255),
'kernel': (150, 255, 150),
}

# ๐Ÿท๏ธ Emojis pour l'interface
EMOJI = {
# Status
'on': '๐ŸŸข', 'off': '๐Ÿ”ด', 'standby': '๐ŸŸก', 'loading': '๐Ÿ”„',
'ok': 'โœ…', 'error': 'โŒ', 'warn': 'โš ๏ธ', 'info': 'โ„น๏ธ',

# Hardware
'raspberry': '๐Ÿ“', 'chip': '๐Ÿง ', 'cpu': 'โšก', 'ram': '๐Ÿ’พ',
'usb': '๐Ÿ”Œ', 'serial': '๐Ÿ“ก', 'network': '๐ŸŒ', 'storage': '๐Ÿ’ฟ',
'power': '๐Ÿ”‹', 'volt': 'โšก', 'temp': '๐ŸŒก๏ธ',

# USB Gadget
'gadget': '๐Ÿ”—', 'tty': '๐Ÿ“Ÿ', 'rx': '๐Ÿ“ฅ', 'tx': '๐Ÿ“ค', 'signal': '๐Ÿ“ถ',

# Actions
'play': 'โ–ถ๏ธ', 'pause': 'โธ๏ธ', 'stop': 'โน๏ธ', 'restart': '๐Ÿ”„',
'back': 'โ—€๏ธ', 'forward': 'โ–ถ๏ธ', 'settings': 'โš™๏ธ', 'tools': '๐Ÿ”ง',
'search': '๐Ÿ”', 'clear': '๐Ÿงน', 'save': '๐Ÿ’พ', 'load': '๐Ÿ“‚',

# Interface
'clock': '๐Ÿ•', 'dashboard': '๐Ÿ“Š', 'terminal': '๐Ÿ’ป', 'files': '๐Ÿ“',
'boot': '๐Ÿš€', 'timeline': '๐Ÿ“ˆ', 'config': 'โš™๏ธ', 'help': 'โ“',

# Platforms
'espressobin': 'โ˜•', 'mochabin': '๐Ÿซ', 'ultra': 'โšก', 'target': '๐ŸŽฏ',

# Boot stages
'bootrom': '๐Ÿ”’', 'wtmi': '๐Ÿ”‘', 'atf': '๐Ÿ›ก๏ธ', 'spl': '๐Ÿ“ฆ',
'uboot': '๐Ÿฅพ', 'kernel': '๐Ÿง', 'login': '๐Ÿ‘ค',

# Misc
'success': '๐ŸŽ‰', 'flash': 'โšก', 'link': '๐Ÿ”—',
}

# Couleurs par stage de boot
STAGE_COLORS = {
'bootrom': (200, 100, 100), # Rouge clair
'wtmi': (255, 150, 100), # Orange
'atf': (200, 255, 100), # Jaune-vert
'spl': (100, 200, 255), # Bleu clair
'uboot': (100, 150, 255), # Bleu
'kernel': (150, 100, 255), # Violet
'login': (100, 255, 150), # Vert clair
}


class CircularUI:
def __init__(self):
pygame.init()
pygame.mouse.set_visible(False)

self.screen = pygame.display.set_mode(
(SCREEN_SIZE, SCREEN_SIZE),
pygame.FULLSCREEN | pygame.HWSURFACE
)
self.clock = pygame.time.Clock()
self.running = True
self.current_screen = None
self.screens = {}

# Surface de masque circulaire
self.mask = self._create_circular_mask()

# Font
pygame.font.init()
self.fonts = {
'small': pygame.font.Font('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 14),
'medium': pygame.font.Font('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 18),
'large': pygame.font.Font('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 28),
'xlarge': pygame.font.Font('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf', 48),
}

def _create_circular_mask(self) -> pygame.Surface:
"""Crรฉe un masque circulaire pour cacher les coins."""
mask = pygame.Surface((SCREEN_SIZE, SCREEN_SIZE), pygame.SRCALPHA)
mask.fill((0, 0, 0, 255))
pygame.draw.circle(mask, (0, 0, 0, 0), CENTER, RADIUS)
return mask

def register_screen(self, name: str, screen_class):
"""Enregistre un รฉcran."""
self.screens[name] = screen_class(self)

def switch_screen(self, name: str):
"""Change d'รฉcran."""
if name in self.screens:
if self.current_screen:
self.current_screen.on_exit()
self.current_screen = self.screens[name]
self.current_screen.on_enter()

def draw_circular_progress(self, surface, center, radius, progress,
color, width=8, start_angle=-90):
"""Dessine un arc de progression circulaire."""
if progress <= 0:
return

rect = pygame.Rect(
center[0] - radius,
center[1] - radius,
radius * 2,
radius * 2
)

end_angle = start_angle + (360 * min(progress, 1.0))

# Convertir en radians pour pygame
start_rad = math.radians(start_angle)
end_rad = math.radians(end_angle)

pygame.draw.arc(surface, color, rect, -end_rad, -start_rad, width)

def draw_radial_menu(self, surface, items: list, selected: int = 0):
"""Dessine un menu radial."""
n_items = len(items)
if n_items == 0:
return

angle_step = 360 / n_items
menu_radius = RADIUS - 60

for i, item in enumerate(items):
angle = math.radians(-90 + i * angle_step)
x = CENTER[0] + int(menu_radius * math.cos(angle))
y = CENTER[1] + int(menu_radius * math.sin(angle))

# Cercle de l'item
color = COLORS['primary'] if i == selected else COLORS['bg_light']
pygame.draw.circle(surface, color, (x, y), 35)
pygame.draw.circle(surface, COLORS['border'], (x, y), 35, 2)

# Icรดne/texte
text = self.fonts['small'].render(item['label'][:4], True, COLORS['text'])
text_rect = text.get_rect(center=(x, y))
surface.blit(text, text_rect)

def run(self):
"""Boucle principale."""
while self.running:
# Events
for event in pygame.event.get():
if event.type == pygame.QUIT:
self.running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
self.running = False
elif event.type == pygame.MOUSEBUTTONDOWN:
if self.current_screen:
self.current_screen.on_touch(event.pos)

# Update
if self.current_screen:
self.current_screen.update()

# Draw
self.screen.fill(COLORS['bg'])

if self.current_screen:
self.current_screen.draw(self.screen)

# Appliquer masque circulaire
self.screen.blit(self.mask, (0, 0))

pygame.display.flip()
self.clock.tick(30)

pygame.quit()


class Screen:
"""Classe de base pour les รฉcrans."""

def __init__(self, ui: CircularUI):
self.ui = ui

def on_enter(self):
"""Appelรฉ quand l'รฉcran devient actif."""
pass

def on_exit(self):
"""Appelรฉ quand on quitte l'รฉcran."""
pass

def on_touch(self, pos: tuple):
"""Gรจre les รฉvรฉnements tactiles."""
pass

def update(self):
"""Met ร  jour la logique."""
pass

def draw(self, surface: pygame.Surface):
"""Dessine l'รฉcran."""
pass

๐Ÿ“Œ 4.2 ร‰cran Horloge (ui/screens/clock.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
#!/usr/bin/env python3
"""ร‰cran horloge - premier รฉcran du projet."""

import pygame
import math
from datetime import datetime
from ui.core import Screen, COLORS, CENTER, RADIUS


class ClockScreen(Screen):
def __init__(self, ui):
super().__init__(ui)
self.last_second = -1

def draw(self, surface):
now = datetime.now()

# Cercle extรฉrieur
pygame.draw.circle(surface, COLORS['border'], CENTER, RADIUS - 10, 2)

# Marqueurs des heures
for i in range(12):
angle = math.radians(-90 + i * 30)
inner_r = RADIUS - 30
outer_r = RADIUS - 15

x1 = CENTER[0] + int(inner_r * math.cos(angle))
y1 = CENTER[1] + int(inner_r * math.sin(angle))
x2 = CENTER[0] + int(outer_r * math.cos(angle))
y2 = CENTER[1] + int(outer_r * math.sin(angle))

width = 3 if i % 3 == 0 else 1
pygame.draw.line(surface, COLORS['text'], (x1, y1), (x2, y2), width)

# Aiguille des heures
hour_angle = math.radians(-90 + (now.hour % 12 + now.minute / 60) * 30)
hour_len = RADIUS * 0.5
hx = CENTER[0] + int(hour_len * math.cos(hour_angle))
hy = CENTER[1] + int(hour_len * math.sin(hour_angle))
pygame.draw.line(surface, COLORS['text'], CENTER, (hx, hy), 6)

# Aiguille des minutes
min_angle = math.radians(-90 + now.minute * 6)
min_len = RADIUS * 0.7
mx = CENTER[0] + int(min_len * math.cos(min_angle))
my = CENTER[1] + int(min_len * math.sin(min_angle))
pygame.draw.line(surface, COLORS['primary'], CENTER, (mx, my), 4)

# Aiguille des secondes
sec_angle = math.radians(-90 + now.second * 6)
sec_len = RADIUS * 0.8
sx = CENTER[0] + int(sec_len * math.cos(sec_angle))
sy = CENTER[1] + int(sec_len * math.sin(sec_angle))
pygame.draw.line(surface, COLORS['danger'], CENTER, (sx, sy), 2)

# Centre
pygame.draw.circle(surface, COLORS['text'], CENTER, 8)
pygame.draw.circle(surface, COLORS['bg'], CENTER, 4)

# Date
date_str = now.strftime('%d %b %Y')
date_text = self.ui.fonts['medium'].render(date_str, True, COLORS['text_dim'])
date_rect = date_text.get_rect(center=(CENTER[0], CENTER[1] + 80))
surface.blit(date_text, date_rect)

# Heure digitale
time_str = now.strftime('%H:%M')
time_text = self.ui.fonts['large'].render(time_str, True, COLORS['text'])
time_rect = time_text.get_rect(center=(CENTER[0], CENTER[1] - 80))
surface.blit(time_text, time_rect)

def on_touch(self, pos):
# Toucher pour passer au dashboard
self.ui.switch_screen('dashboard')

๐Ÿ“Œ 4.3 ร‰cran Dashboard avec Mini-Status (ui/screens/dashboard.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/env python3
"""Dashboard principal avec menu radial et mini-dashboards de status."""

import pygame
import math
from ui.core import Screen, COLORS, EMOJI, CENTER, RADIUS


class DashboardScreen(Screen):
"""Dashboard principal avec status Master/Target."""

# Menu items avec emojis
MENU_ITEMS = [
{'id': 'clock', 'emoji': EMOJI['clock'], 'label': 'TIME', 'color': COLORS['primary']},
{'id': 'serial', 'emoji': EMOJI['terminal'], 'label': 'UART', 'color': COLORS['secondary']},
{'id': 'boot', 'emoji': EMOJI['boot'], 'label': 'BOOT', 'color': COLORS['warning']},
{'id': 'power', 'emoji': EMOJI['power'], 'label': 'PWR', 'color': COLORS['power']},
{'id': 'files', 'emoji': EMOJI['files'], 'label': 'FILE', 'color': COLORS['uboot']},
{'id': 'conf', 'emoji': EMOJI['settings'], 'label': 'CONF', 'color': COLORS['spi']},
]

def __init__(self, ui):
super().__init__(ui)
self.selected = -1

# Status Master (Pi Zero)
self.master_status = {
'power': True,
'gadget': False,
'tty': False,
'storage': False,
'wifi': False,
'temp': 45,
}

# Status Target (SoC cible)
self.target_status = {
'power': False,
'usb': False,
'serial': False,
'platform': f"{EMOJI['search']} Detecting...",
'booting': False,
'ready': False,
}

def update_master_status(self, **kwargs):
"""Met ร  jour le status du Master."""
self.master_status.update(kwargs)

def update_target_status(self, **kwargs):
"""Met ร  jour le status de la cible."""
self.target_status.update(kwargs)

def draw(self, surface):
# Titre
title = f"{EMOJI['tools']} PiDebugger"
title_text = self.ui.fonts['large'].render(title, True, COLORS['primary'])
title_rect = title_text.get_rect(center=(CENTER[0], 45))
surface.blit(title_text, title_rect)

# Mini-dashboard Master
self._draw_master_status(surface, 45, 55)

# Mini-dashboard Target
self._draw_target_status(surface, RADIUS + 50, 55)

# Platform info
platform_text = self.ui.fonts['small'].render(
self.target_status['platform'], True, COLORS['text']
)
platform_rect = platform_text.get_rect(center=(CENTER[0], 118))
surface.blit(platform_text, platform_rect)

# Menu radial
self._draw_menu(surface)

# Footer
footer = f"{EMOJI['info']} Touch icon to select"
footer_text = self.ui.fonts['small'].render(footer, True, COLORS['text_dim'])
footer_rect = footer_text.get_rect(center=(CENTER[0], RADIUS * 2 - 22))
surface.blit(footer_text, footer_rect)

def _draw_master_status(self, surface, x, y):
"""Dessine le mini-dashboard Master."""
# Background
pygame.draw.rect(surface, COLORS['bg_light'], (x, y, 140, 44), border_radius=8)

# Titre
title = f"{EMOJI['raspberry']} MASTER"
title_text = self.ui.fonts['small'].render(title, True, COLORS['raspberry'])
surface.blit(title_text, (x + 10, y + 5))

# Status icons
ms = self.master_status
status_line = (
f"{EMOJI['gadget']}{EMOJI['on'] if ms['gadget'] else EMOJI['off']} "
f"{EMOJI['tty']}{EMOJI['on'] if ms['tty'] else EMOJI['off']} "
f"{EMOJI['storage']}{EMOJI['on'] if ms['storage'] else EMOJI['off']}"
)
status_text = self.ui.fonts['small'].render(status_line, True, COLORS['text'])
surface.blit(status_text, (x + 10, y + 25))

def _draw_target_status(self, surface, x, y):
"""Dessine le mini-dashboard Target."""
# Background
pygame.draw.rect(surface, COLORS['bg_light'], (x, y, 140, 44), border_radius=8)

# Titre
title = f"{EMOJI['target']} TARGET"
title_text = self.ui.fonts['small'].render(title, True, COLORS['primary'])
surface.blit(title_text, (x + 10, y + 5))

# Status icons
ts = self.target_status
status_line = (
f"{EMOJI['power']}{EMOJI['on'] if ts['power'] else EMOJI['off']} "
f"{EMOJI['usb']}{EMOJI['on'] if ts['usb'] else EMOJI['off']} "
f"{EMOJI['serial']}{EMOJI['on'] if ts['serial'] else EMOJI['off']}"
)
status_text = self.ui.fonts['small'].render(status_line, True, COLORS['text'])
surface.blit(status_text, (x + 10, y + 25))

def _draw_menu(self, surface):
"""Dessine le menu radial avec emojis."""
n_items = len(self.MENU_ITEMS)
menu_radius = RADIUS - 75

for i, item in enumerate(self.MENU_ITEMS):
angle = math.radians(-90 + i * (360 / n_items))
x = CENTER[0] + int(menu_radius * math.cos(angle))
y = CENTER[1] + 30 + int(menu_radius * math.sin(angle))

# Cercle bouton
pygame.draw.circle(surface, COLORS['bg_light'], (x, y), 36)
pygame.draw.circle(surface, COLORS['border'], (x, y), 36, 2)
pygame.draw.circle(surface, item['color'], (x, y), 36, 2)

# Emoji
emoji_text = self.ui.fonts['large'].render(item['emoji'], True, COLORS['text'])
emoji_rect = emoji_text.get_rect(center=(x, y - 2))
surface.blit(emoji_text, emoji_rect)

# Label
label_text = self.ui.fonts['small'].render(item['label'], True, COLORS['text_dim'])
label_rect = label_text.get_rect(center=(x, y + 16))
surface.blit(label_text, label_rect)

def on_touch(self, pos):
"""Gรจre le touch sur les boutons du menu."""
n_items = len(self.MENU_ITEMS)
menu_radius = RADIUS - 75

for i, item in enumerate(self.MENU_ITEMS):
angle = math.radians(-90 + i * (360 / n_items))
bx = CENTER[0] + int(menu_radius * math.cos(angle))
by = CENTER[1] + 30 + int(menu_radius * math.sin(angle))

# Distance au centre du bouton
dist = math.sqrt((pos[0] - bx) ** 2 + (pos[1] - by) ** 2)

if dist < 36:
self.ui.switch_screen(item['id'])
return

๐Ÿ“‹ Phase 5 : Mode USB Gadget

๐Ÿ“Œ 5.1 Configuration kernel

1
2
3
4
5
# ร‰diter /boot/config.txt
sudo nano /boot/config.txt

# Ajouter ร  la fin:
dtoverlay=dwc2
1
2
# ร‰diter /boot/cmdline.txt - ajouter aprรจs rootwait:
modules-load=dwc2,g_serial

๐Ÿ“Œ 5.2 Module USB Gadget (serial/gadget.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#!/usr/bin/env python3
"""
Gestion USB Gadget mode pour connexion sรฉrie vers SoC cible.
Le Pi Zero W devient un adaptateur USB-sรฉrie virtuel.
"""

import os
import subprocess
from pathlib import Path


class USBGadget:
GADGET_PATH = '/sys/kernel/config/usb_gadget/pidebugger'

def __init__(self):
self.configured = False

def setup(self, vendor_id='0x1d6b', product_id='0x0104',
serial='PiDebugger001', manufacturer='PiDebugger',
product='Debug Console'):
"""Configure le gadget USB composite (serial + mass storage)."""

if not os.path.exists('/sys/kernel/config/usb_gadget'):
subprocess.run(['modprobe', 'libcomposite'], check=True)

gadget = Path(self.GADGET_PATH)

# Crรฉer gadget
gadget.mkdir(exist_ok=True)

# IDs
(gadget / 'idVendor').write_text(vendor_id)
(gadget / 'idProduct').write_text(product_id)
(gadget / 'bcdDevice').write_text('0x0100')
(gadget / 'bcdUSB').write_text('0x0200')

# Strings
strings = gadget / 'strings/0x409'
strings.mkdir(parents=True, exist_ok=True)
(strings / 'serialnumber').write_text(serial)
(strings / 'manufacturer').write_text(manufacturer)
(strings / 'product').write_text(product)

# Configuration
config = gadget / 'configs/c.1'
config.mkdir(parents=True, exist_ok=True)
(config / 'MaxPower').write_text('250')

config_strings = config / 'strings/0x409'
config_strings.mkdir(parents=True, exist_ok=True)
(config_strings / 'configuration').write_text('Serial + Storage')

# Fonction ACM (serial)
acm = gadget / 'functions/acm.usb0'
acm.mkdir(exist_ok=True)

# Lier fonction ร  config
link = config / 'acm.usb0'
if not link.exists():
link.symlink_to(acm)

# Activer gadget
udc = list(Path('/sys/class/udc').iterdir())[0].name
(gadget / 'UDC').write_text(udc)

self.configured = True
return '/dev/ttyGS0'

def teardown(self):
"""Dรฉsactive le gadget USB."""
gadget = Path(self.GADGET_PATH)

if gadget.exists():
# Dรฉsactiver
try:
(gadget / 'UDC').write_text('')
except:
pass

# Supprimer liens
for link in (gadget / 'configs/c.1').glob('*.usb*'):
link.unlink()

# Supprimer dans l'ordre inverse
for d in ['configs/c.1/strings/0x409', 'configs/c.1',
'functions/acm.usb0', 'strings/0x409', '']:
try:
(gadget / d).rmdir()
except:
pass

self.configured = False


# Script standalone pour test
if __name__ == '__main__':
gadget = USBGadget()
try:
tty = gadget.setup()
print(f'Gadget configurรฉ: {tty}')
input('Appuyez sur Entrรฉe pour dรฉsactiver...')
finally:
gadget.teardown()
print('Gadget dรฉsactivรฉ')

๐Ÿ“Œ 5.3 Console sรฉrie (serial/console.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
#!/usr/bin/env python3
"""Console sรฉrie avec buffer circulaire et parsing temps rรฉel."""

import serial
import threading
from collections import deque
from typing import Callable, Optional
import re


class SerialConsole:
def __init__(self, port: str = '/dev/ttyGS0', baudrate: int = 115200):
self.port = port
self.baudrate = baudrate
self.serial: Optional[serial.Serial] = None
self.buffer = deque(maxlen=10000) # Buffer circulaire
self.lines = deque(maxlen=500) # Derniรจres lignes
self.running = False
self.thread: Optional[threading.Thread] = None
self.callbacks: list[Callable] = []

# Patterns de dรฉtection
self.patterns = {
'uboot_prompt': re.compile(r'^(=>|U-Boot>)\s*$'),
'uboot_start': re.compile(r'U-Boot \d+\.\d+'),
'linux_boot': re.compile(r'Linux version \d+\.\d+'),
'login_prompt': re.compile(r'login:\s*$'),
'uefi': re.compile(r'UEFI|TianoCore|EDK'),
'spi_boot': re.compile(r'SPI|BootROM|WTMI'),
}

self.detected_stage = None

def connect(self) -> bool:
"""Ouvre la connexion sรฉrie."""
try:
self.serial = serial.Serial(
self.port,
self.baudrate,
timeout=0.1,
xonxoff=False,
rtscts=False
)
return True
except Exception as e:
print(f'Erreur connexion: {e}')
return False

def disconnect(self):
"""Ferme la connexion."""
self.running = False
if self.thread:
self.thread.join(timeout=1)
if self.serial:
self.serial.close()
self.serial = None

def start_reading(self):
"""Dรฉmarre la lecture en arriรจre-plan."""
if not self.serial:
return

self.running = True
self.thread = threading.Thread(target=self._read_loop, daemon=True)
self.thread.start()

def _read_loop(self):
"""Boucle de lecture."""
line_buffer = ''

while self.running and self.serial:
try:
data = self.serial.read(256)
if data:
text = data.decode('utf-8', errors='replace')

for char in text:
self.buffer.append(char)

if char == '\n':
self.lines.append(line_buffer)
self._analyze_line(line_buffer)
line_buffer = ''
elif char != '\r':
line_buffer += char

# Notifier callbacks
for cb in self.callbacks:
cb(text)

except Exception as e:
print(f'Erreur lecture: {e}')

def _analyze_line(self, line: str):
"""Analyse une ligne pour dรฉtecter le stage de boot."""
for stage, pattern in self.patterns.items():
if pattern.search(line):
self.detected_stage = stage
break

def send(self, data: str):
"""Envoie des donnรฉes."""
if self.serial:
self.serial.write(data.encode('utf-8'))

def send_break(self):
"""Envoie un break (pour interrompre U-Boot)."""
if self.serial:
self.serial.send_break(duration=0.25)

def get_recent_lines(self, n: int = 20) -> list[str]:
"""Retourne les n derniรจres lignes."""
return list(self.lines)[-n:]

def add_callback(self, callback: Callable):
"""Ajoute un callback pour les donnรฉes reรงues."""
self.callbacks.append(callback)

๐Ÿ“‹ Phase 6 : Analyseurs de boot

๐Ÿ“Œ 6.1 Analyseur U-Boot (analyzers/uboot.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#!/usr/bin/env python3
"""Analyseur de sรฉquence U-Boot."""

import re
from dataclasses import dataclass
from typing import Optional
from enum import Enum


class UBootStage(Enum):
SPL = 'spl'
TPL = 'tpl'
MAIN = 'main'
PROMPT = 'prompt'
BOOT = 'boot'


@dataclass
class UBootInfo:
version: Optional[str] = None
board: Optional[str] = None
cpu: Optional[str] = None
dram: Optional[str] = None
stage: UBootStage = UBootStage.SPL
env_vars: dict = None
boot_device: Optional[str] = None

def __post_init__(self):
if self.env_vars is None:
self.env_vars = {}


class UBootAnalyzer:
def __init__(self):
self.info = UBootInfo()
self.lines = []

self.patterns = {
'version': re.compile(r'U-Boot (\d+\.\d+[^\s]*)'),
'board': re.compile(r'Board:\s*(.+)'),
'cpu': re.compile(r'CPU:\s*(.+)'),
'dram': re.compile(r'DRAM:\s*(.+)'),
'spl': re.compile(r'U-Boot SPL'),
'tpl': re.compile(r'U-Boot TPL'),
'prompt': re.compile(r'^(=>|[a-zA-Z0-9_-]+>)\s*$'),
'env': re.compile(r'^([a-zA-Z_][a-zA-Z0-9_]*)=(.*)$'),
'boot_dev': re.compile(r'Boot device:\s*(.+)'),
'loading': re.compile(r'Loading .+ from (\w+)'),
}

def feed(self, line: str) -> Optional[str]:
"""Analyse une ligne et retourne un รฉvรฉnement si dรฉtectรฉ."""
self.lines.append(line)

# Version
m = self.patterns['version'].search(line)
if m:
self.info.version = m.group(1)
return 'version_detected'

# Board
m = self.patterns['board'].search(line)
if m:
self.info.board = m.group(1).strip()
return 'board_detected'

# CPU
m = self.patterns['cpu'].search(line)
if m:
self.info.cpu = m.group(1).strip()
return 'cpu_detected'

# DRAM
m = self.patterns['dram'].search(line)
if m:
self.info.dram = m.group(1).strip()
return 'dram_detected'

# Stage detection
if self.patterns['spl'].search(line):
self.info.stage = UBootStage.SPL
return 'spl_start'

if self.patterns['tpl'].search(line):
self.info.stage = UBootStage.TPL
return 'tpl_start'

if self.patterns['prompt'].search(line):
self.info.stage = UBootStage.PROMPT
return 'prompt_ready'

# Boot device
m = self.patterns['boot_dev'].search(line)
if m:
self.info.boot_device = m.group(1).strip()
return 'boot_device'

return None

def get_summary(self) -> dict:
"""Retourne un rรฉsumรฉ de l'analyse."""
return {
'version': self.info.version,
'board': self.info.board,
'cpu': self.info.cpu,
'dram': self.info.dram,
'stage': self.info.stage.value,
'boot_device': self.info.boot_device,
}

def generate_commands(self) -> list[str]:
"""Gรฉnรจre des commandes U-Boot utiles pour le board dรฉtectรฉ."""
cmds = [
'version',
'bdinfo',
'printenv',
'mmc info',
'usb info',
]

if 'marvell' in (self.info.board or '').lower():
cmds.extend([
'bubt', # Burn boot image
'hw_info',
])

return cmds

๐Ÿ“‹ Phase 7 : Dashboard principal

๐Ÿ“Œ 7.1 ร‰cran Dashboard (ui/screens/dashboard.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#!/usr/bin/env python3
"""Dashboard principal style LuCI OpenWRT."""

import pygame
import math
from ui.core import Screen, COLORS, CENTER, RADIUS


class DashboardScreen(Screen):
def __init__(self, ui):
super().__init__(ui)

self.menu_items = [
{'id': 'clock', 'label': 'TIME', 'icon': 'โฐ'},
{'id': 'serial', 'label': 'UART', 'icon': '๐Ÿ“ก'},
{'id': 'boot', 'label': 'BOOT', 'icon': '๐Ÿ”ง'},
{'id': 'net', 'label': 'NET', 'icon': '๐ŸŒ'},
{'id': 'files', 'label': 'FILE', 'icon': '๐Ÿ“'},
{'id': 'settings', 'label': 'CONF', 'icon': 'โš™๏ธ'},
]

self.selected = 0
self.status = {
'usb': False,
'serial': False,
'target': 'Unknown',
}

def draw(self, surface):
# Titre central
title = self.ui.fonts['large'].render('PiDebugger', True, COLORS['primary'])
title_rect = title.get_rect(center=(CENTER[0], 60))
surface.blit(title, title_rect)

# Status bar
status_y = 100
status_color = COLORS['secondary'] if self.status['usb'] else COLORS['danger']
pygame.draw.circle(surface, status_color, (CENTER[0] - 60, status_y), 6)
usb_text = self.ui.fonts['small'].render('USB', True, COLORS['text_dim'])
surface.blit(usb_text, (CENTER[0] - 50, status_y - 8))

status_color = COLORS['secondary'] if self.status['serial'] else COLORS['danger']
pygame.draw.circle(surface, status_color, (CENTER[0] + 40, status_y), 6)
ser_text = self.ui.fonts['small'].render('UART', True, COLORS['text_dim'])
surface.blit(ser_text, (CENTER[0] + 50, status_y - 8))

# Target info
target_text = self.ui.fonts['medium'].render(
self.status['target'], True, COLORS['text']
)
target_rect = target_text.get_rect(center=(CENTER[0], 140))
surface.blit(target_text, target_rect)

# Menu radial
n_items = len(self.menu_items)
menu_radius = RADIUS - 80

for i, item in enumerate(self.menu_items):
angle = math.radians(-90 + i * (360 / n_items))
x = CENTER[0] + int(menu_radius * math.cos(angle))
y = CENTER[1] + int(menu_radius * math.sin(angle))

# Fond du bouton
btn_radius = 40
if i == self.selected:
pygame.draw.circle(surface, COLORS['primary'], (x, y), btn_radius)
pygame.draw.circle(surface, COLORS['text'], (x, y), btn_radius, 2)
else:
pygame.draw.circle(surface, COLORS['bg_light'], (x, y), btn_radius)
pygame.draw.circle(surface, COLORS['border'], (x, y), btn_radius, 2)

# Label
label = self.ui.fonts['small'].render(item['label'], True, COLORS['text'])
label_rect = label.get_rect(center=(x, y))
surface.blit(label, label_rect)

# Instructions
hint = self.ui.fonts['small'].render('Touch to select', True, COLORS['text_dim'])
hint_rect = hint.get_rect(center=(CENTER[0], RADIUS * 2 - 40))
surface.blit(hint, hint_rect)

def on_touch(self, pos):
# Dรฉtecter quel item est touchรฉ
n_items = len(self.menu_items)
menu_radius = RADIUS - 80

for i, item in enumerate(self.menu_items):
angle = math.radians(-90 + i * (360 / n_items))
x = CENTER[0] + int(menu_radius * math.cos(angle))
y = CENTER[1] + int(menu_radius * math.sin(angle))

# Distance au centre du bouton
dist = math.sqrt((pos[0] - x) ** 2 + (pos[1] - y) ** 2)

if dist < 45:
self.selected = i
# Navigation
screen_id = item['id']
if screen_id in self.ui.screens:
self.ui.switch_screen(screen_id)
break

def update_status(self, usb: bool = None, serial: bool = None, target: str = None):
if usb is not None:
self.status['usb'] = usb
if serial is not None:
self.status['serial'] = serial
if target is not None:
self.status['target'] = target

๐Ÿ“‹ Phase 8 : Point dโ€™entrรฉe

๐Ÿ“Œ 8.1 Main (main.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
#!/usr/bin/env python3
"""
PiDebugger - Outil portable de debug SoC
Point d'entrรฉe principal
"""

import sys
import signal
from ui.core import CircularUI
from ui.screens.clock import ClockScreen
from ui.screens.dashboard import DashboardScreen
from serial.gadget import USBGadget
from serial.console import SerialConsole


def signal_handler(sig, frame):
print('\nArrรชt...')
sys.exit(0)


def main():
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)

print('PiDebugger v0.1')
print('Initialisation...')

# Initialiser USB Gadget
gadget = USBGadget()
try:
tty = gadget.setup()
print(f'USB Gadget: {tty}')
except Exception as e:
print(f'Warning: USB Gadget non disponible: {e}')
tty = None

# Initialiser console sรฉrie
console = None
if tty:
console = SerialConsole(tty)
if console.connect():
console.start_reading()
print('Console sรฉrie active')

# Initialiser UI
ui = CircularUI()

# Enregistrer les รฉcrans
ui.register_screen('clock', ClockScreen)
ui.register_screen('dashboard', DashboardScreen)

# Dรฉmarrer sur l'horloge
ui.switch_screen('clock')

print('Dรฉmarrage interface...')

try:
ui.run()
finally:
if console:
console.disconnect()
gadget.teardown()
print('Arrรชt propre')


if __name__ == '__main__':
main()

๐Ÿ“‹ Phase 9 : Service systemd

๐Ÿ“Œ 9.1 Crรฉer le service

1
sudo nano /etc/systemd/system/pidebugger.service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
[Unit]
Description=PiDebugger Interface
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/home/pi/pidebugger
ExecStart=/usr/bin/python3 /home/pi/pidebugger/main.py
Restart=on-failure
RestartSec=5
Environment=SDL_FBDEV=/dev/fb0
Environment=DISPLAY=:0

[Install]
WantedBy=multi-user.target

๐Ÿ“Œ 9.2 Activer le service

1
2
3
4
5
6
7
8
9
sudo systemctl daemon-reload
sudo systemctl enable pidebugger
sudo systemctl start pidebugger

# Vรฉrifier le statut
sudo systemctl status pidebugger

# Voir les logs
journalctl -u pidebugger -f

๐Ÿ“‹ Phase 10 : Contrรดle alimentation USB cible

๐Ÿ“Œ 10.1 Architecture USB OTG Power

Le Pi Zero W peut alimenter les cibles EspressoBin/MochaBin via son port USB OTG en mode host avec contrรดle VBUS.

1
2
3
4
5
6
7
8
9
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”      USB Cable       โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Pi Zero W โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ EspressoBin โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ USB OTG Port โ”‚ +5V (500mA-2A) โ”‚ USB-C / Debug โ”‚
โ”‚ (dwc2 driver) โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ Port โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ GPIO Control โ”‚ VBUS Enable โ”‚ Powered via โ”‚
โ”‚ (optional) โ”‚โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–บโ”‚ USB โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“Œ 10.2 Configuration kernel pour USB Host

1
2
3
4
5
# /boot/config.txt
dtoverlay=dwc2,dr_mode=host

# Pour le mode dual (gadget + host switchable)
dtoverlay=dwc2,dr_mode=otg

๐Ÿ“Œ 10.3 Module de contrรดle alimentation (power/usb_power.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
#!/usr/bin/env python3
"""
Contrรดle alimentation USB pour cibles SoC.
Supporte EspressoBin (5V USB-C) et MochaBin (12V barrel - via relay).
"""

import subprocess
import time
from pathlib import Path
from dataclasses import dataclass
from enum import Enum
from typing import Optional
import threading


class PowerState(Enum):
OFF = 'off'
ON = 'on'
STANDBY = 'standby'
FAULT = 'fault'


class TargetPlatform(Enum):
ESPRESSOBIN = 'espressobin'
ESPRESSOBIN_V7 = 'espressobin-v7'
ESPRESSOBIN_ULTRA = 'espressobin-ultra'
MOCHABIN = 'mochabin'


@dataclass
class PowerConfig:
"""Configuration alimentation par plateforme."""
platform: TargetPlatform
voltage: float # Volts
max_current: float # Ampรจres
usb_powered: bool # True si alimentable via USB
connector: str
boot_delay: float # Dรฉlai aprรจs power-on avant boot


PLATFORM_CONFIGS = {
TargetPlatform.ESPRESSOBIN: PowerConfig(
platform=TargetPlatform.ESPRESSOBIN,
voltage=5.0,
max_current=2.0,
usb_powered=True,
connector='USB-C or Barrel 5V',
boot_delay=0.5,
),
TargetPlatform.ESPRESSOBIN_V7: PowerConfig(
platform=TargetPlatform.ESPRESSOBIN_V7,
voltage=5.0,
max_current=2.0,
usb_powered=True,
connector='USB-C',
boot_delay=0.5,
),
TargetPlatform.ESPRESSOBIN_ULTRA: PowerConfig(
platform=TargetPlatform.ESPRESSOBIN_ULTRA,
voltage=12.0,
max_current=5.0,
usb_powered=False, # Nรฉcessite 12V externe
connector='Barrel 5.5x2.1mm',
boot_delay=1.0,
),
TargetPlatform.MOCHABIN: PowerConfig(
platform=TargetPlatform.MOCHABIN,
voltage=12.0,
max_current=3.0,
usb_powered=False, # Nรฉcessite 12V externe
connector='Barrel 5.5x2.1mm',
boot_delay=1.0,
),
}


class USBPowerController:
"""
Contrรดleur d'alimentation USB pour le Pi Zero.

Modes supportรฉs:
1. USB Host direct (5V via VBUS) - pour EspressoBin
2. GPIO relay control - pour alimentations 12V externes
3. USB PD negotiation (future) - pour USB-C PD
"""

VBUS_GPIO = 4 # GPIO pour contrรดle VBUS externe (optionnel)
RELAY_GPIO = 17 # GPIO pour relais 12V (optionnel)

USB_HOST_PATH = Path('/sys/bus/usb/devices')
DWC2_PATH = Path('/sys/devices/platform/soc/20980000.usb')

def __init__(self, platform: TargetPlatform = TargetPlatform.ESPRESSOBIN):
self.platform = platform
self.config = PLATFORM_CONFIGS[platform]
self.state = PowerState.OFF
self.current_ma = 0
self.voltage_v = 0
self.uptime_seconds = 0
self._monitor_thread: Optional[threading.Thread] = None
self._monitoring = False

def set_platform(self, platform: TargetPlatform):
"""Change la plateforme cible."""
if self.state == PowerState.ON:
raise RuntimeError("Cannot change platform while power is ON")
self.platform = platform
self.config = PLATFORM_CONFIGS[platform]

def power_on(self) -> bool:
"""Active l'alimentation de la cible."""
if self.state == PowerState.ON:
return True

try:
if self.config.usb_powered:
# Alimentation via USB VBUS
success = self._enable_usb_host_power()
else:
# Alimentation via relais externe
success = self._enable_relay_power()

if success:
self.state = PowerState.ON
self._start_monitoring()

# Attendre le dรฉlai de boot
time.sleep(self.config.boot_delay)

return success

except Exception as e:
print(f"Power on error: {e}")
self.state = PowerState.FAULT
return False

def power_off(self) -> bool:
"""Coupe l'alimentation de la cible."""
self._stop_monitoring()

try:
if self.config.usb_powered:
success = self._disable_usb_host_power()
else:
success = self._disable_relay_power()

if success:
self.state = PowerState.OFF
self.current_ma = 0
self.voltage_v = 0
self.uptime_seconds = 0

return success

except Exception as e:
print(f"Power off error: {e}")
return False

def power_cycle(self, delay: float = 2.0) -> bool:
"""Effectue un cycle power off/on."""
if not self.power_off():
return False

time.sleep(delay)
return self.power_on()

def _enable_usb_host_power(self) -> bool:
"""Active le mode USB host avec VBUS."""
# Mรฉthode 1: Via sysfs si disponible
mode_path = self.DWC2_PATH / 'mode'
if mode_path.exists():
try:
mode_path.write_text('host')
return True
except:
pass

# Mรฉthode 2: Via modprobe
try:
subprocess.run(
['modprobe', 'dwc2', 'dr_mode=host'],
check=True, capture_output=True
)
return True
except:
pass

# Mรฉthode 3: GPIO direct pour VBUS
return self._gpio_set(self.VBUS_GPIO, True)

def _disable_usb_host_power(self) -> bool:
"""Dรฉsactive le VBUS USB."""
# Repasser en mode gadget
mode_path = self.DWC2_PATH / 'mode'
if mode_path.exists():
try:
mode_path.write_text('peripheral')
except:
pass

return self._gpio_set(self.VBUS_GPIO, False)

def _enable_relay_power(self) -> bool:
"""Active le relais pour alimentation 12V."""
return self._gpio_set(self.RELAY_GPIO, True)

def _disable_relay_power(self) -> bool:
"""Dรฉsactive le relais 12V."""
return self._gpio_set(self.RELAY_GPIO, False)

def _gpio_set(self, gpio: int, value: bool) -> bool:
"""Configure un GPIO."""
gpio_path = Path(f'/sys/class/gpio/gpio{gpio}')

try:
# Exporter le GPIO si nรฉcessaire
if not gpio_path.exists():
Path('/sys/class/gpio/export').write_text(str(gpio))
time.sleep(0.1)

# Configurer en sortie
(gpio_path / 'direction').write_text('out')

# Dรฉfinir la valeur
(gpio_path / 'value').write_text('1' if value else '0')

return True
except Exception as e:
print(f"GPIO {gpio} error: {e}")
return False

def _start_monitoring(self):
"""Dรฉmarre le monitoring de consommation."""
self._monitoring = True
self._monitor_thread = threading.Thread(target=self._monitor_loop, daemon=True)
self._monitor_thread.start()

def _stop_monitoring(self):
"""Arrรชte le monitoring."""
self._monitoring = False
if self._monitor_thread:
self._monitor_thread.join(timeout=1)

def _monitor_loop(self):
"""Boucle de monitoring."""
start_time = time.time()

while self._monitoring:
self.uptime_seconds = int(time.time() - start_time)

# Lire la consommation USB si disponible
# (nรฉcessite un INA219 ou similaire pour mesure rรฉelle)
# Ici on simule pour la dรฉmo
if self.state == PowerState.ON:
self.voltage_v = self.config.voltage * (0.98 + 0.02 * (time.time() % 1))
self.current_ma = 500 + 300 * (time.time() % 2) # Simulation

time.sleep(1)

def get_status(self) -> dict:
"""Retourne le status actuel."""
return {
'platform': self.platform.value,
'state': self.state.value,
'voltage_v': round(self.voltage_v, 2),
'current_ma': int(self.current_ma),
'power_w': round(self.voltage_v * self.current_ma / 1000, 2),
'uptime_s': self.uptime_seconds,
'config': {
'max_voltage': self.config.voltage,
'max_current': self.config.max_current,
'connector': self.config.connector,
'usb_powered': self.config.usb_powered,
}
}


class PowerSequencer:
"""
Sรฉquenceur pour les opรฉrations power complexes.
Gรจre les sรฉquences de boot recovery, flash, etc.
"""

def __init__(self, power: USBPowerController):
self.power = power

def boot_to_uboot(self, timeout: float = 10.0) -> bool:
"""
Sรฉquence pour atteindre le prompt U-Boot.
Power cycle + envoi break pour stopper autoboot.
"""
# Power cycle
self.power.power_off()
time.sleep(1)
self.power.power_on()

# Attendre et envoyer break
# (nรฉcessite la console sรฉrie)
return True

def recovery_mode(self) -> bool:
"""
Met la cible en mode recovery (UART boot).
Pour EspressoBin: maintenir le bouton reset pendant power-on.
"""
self.power.power_off()
time.sleep(0.5)

# Signal de recovery (plateforme spรฉcifique)
# ...

self.power.power_on()
return True

def flash_sequence(self) -> bool:
"""
Sรฉquence complรจte de flash:
1. Power off
2. Power on en mode recovery
3. Upload firmware via UART
4. Reset normal
"""
pass


# Test standalone
if __name__ == '__main__':
controller = USBPowerController(TargetPlatform.ESPRESSOBIN)

print(f"Platform: {controller.platform.value}")
print(f"Config: {controller.config}")

print("\nPowering on...")
if controller.power_on():
print("Power ON successful")

for i in range(5):
time.sleep(1)
status = controller.get_status()
print(f" {status['voltage_v']}V @ {status['current_ma']}mA = {status['power_w']}W")

print("\nPowering off...")
controller.power_off()
print("Power OFF")
else:
print("Power ON failed")

๐Ÿ“Œ 10.4 ร‰cran Power Control (ui/screens/power.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
#!/usr/bin/env python3
"""ร‰cran de contrรดle alimentation USB."""

import pygame
import math
from ui.core import Screen, COLORS, CENTER, RADIUS
from power.usb_power import USBPowerController, TargetPlatform, PowerState


class PowerScreen(Screen):
"""Interface de contrรดle alimentation."""

PLATFORM_NAMES = {
TargetPlatform.ESPRESSOBIN: 'EspressoBin',
TargetPlatform.ESPRESSOBIN_V7: 'EspressoBin V7',
TargetPlatform.ESPRESSOBIN_ULTRA: 'Espresso Ultra',
TargetPlatform.MOCHABIN: 'MochaBin',
}

def __init__(self, ui):
super().__init__(ui)
self.controller = USBPowerController()
self.platforms = list(TargetPlatform)
self.platform_index = 0
self.button_pressed = False

def draw(self, surface):
# Titre
title = self.ui.fonts['medium'].render('USB Power Control', True, (255, 87, 34))
title_rect = title.get_rect(center=(CENTER, 35))
surface.blit(title, title_rect)

# Sรฉlecteur de plateforme
platform = self.platforms[self.platform_index]
platform_name = self.PLATFORM_NAMES[platform]

pygame.draw.rect(surface, COLORS['bgLight'],
(CENTER - 80, 55, 160, 30), border_radius=6)
pygame.draw.rect(surface, COLORS['border'],
(CENTER - 80, 55, 160, 30), 2, border_radius=6)

plat_text = self.ui.fonts['medium'].render(platform_name, True, COLORS['text'])
plat_rect = plat_text.get_rect(center=(CENTER, 70))
surface.blit(plat_text, plat_rect)

# Flรจches navigation
arrow_left = self.ui.fonts['medium'].render('โ—€', True, COLORS['textDim'])
arrow_right = self.ui.fonts['medium'].render('โ–ถ', True, COLORS['textDim'])
surface.blit(arrow_left, (CENTER - 95, 62))
surface.blit(arrow_right, (CENTER + 82, 62))

# Gros bouton power
status = self.controller.get_status()
is_on = status['state'] == 'on'

btn_radius = 70
btn_color = COLORS['secondary'] if is_on else COLORS['danger']

# Cercle externe (progress si on)
pygame.draw.circle(surface, COLORS['bgLight'], CENTER, btn_radius + 10, 0)
pygame.draw.circle(surface, COLORS['border'], CENTER, btn_radius + 10, 2)

if is_on:
# Arc de progression
rect = pygame.Rect(CENTER - btn_radius - 5, CENTER - btn_radius - 5,
(btn_radius + 5) * 2, (btn_radius + 5) * 2)
pygame.draw.arc(surface, btn_color, rect,
math.radians(-135), math.radians(135), 6)

# Cercle bouton
pygame.draw.circle(surface, (*btn_color[:3], 50), CENTER, btn_radius)

# Icรดne power
icon_color = btn_color
# Ligne verticale
pygame.draw.line(surface, icon_color,
(CENTER, CENTER - 25), (CENTER, CENTER + 5), 6)
# Arc
pygame.draw.arc(surface, icon_color,
(CENTER - 25, CENTER - 15, 50, 50),
math.radians(40), math.radians(140), 6)

# Label รฉtat
state_text = 'POWER ON' if is_on else 'POWER OFF'
state_label = self.ui.fonts['medium'].render(state_text, True, btn_color)
state_rect = state_label.get_rect(center=(CENTER, CENTER + 60))
surface.blit(state_label, state_rect)

# Stats
stats_y = CENTER + 100

# Voltage
v_label = self.ui.fonts['small'].render('VOLTAGE', True, COLORS['textDim'])
surface.blit(v_label, (60, stats_y))
v_value = f"{status['voltage_v']:.2f}V" if is_on else '--'
v_text = self.ui.fonts['medium'].render(v_value, True,
COLORS['secondary'] if is_on else COLORS['textDim'])
surface.blit(v_text, (60, stats_y + 18))

# Current
c_label = self.ui.fonts['small'].render('CURRENT', True, COLORS['textDim'])
c_rect = c_label.get_rect(center=(CENTER, stats_y))
surface.blit(c_label, c_rect)
c_value = f"{status['current_ma']}mA" if is_on else '--'
c_text = self.ui.fonts['medium'].render(c_value, True,
COLORS['warning'] if is_on else COLORS['textDim'])
c_rect = c_text.get_rect(center=(CENTER, stats_y + 18))
surface.blit(c_text, c_rect)

# Uptime
u_label = self.ui.fonts['small'].render('UPTIME', True, COLORS['textDim'])
surface.blit(u_label, (SCREEN_SIZE - 120, stats_y))
if is_on:
mins = status['uptime_s'] // 60
secs = status['uptime_s'] % 60
u_value = f"{mins}:{secs:02d}"
else:
u_value = '--'
u_text = self.ui.fonts['medium'].render(u_value, True,
COLORS['primary'] if is_on else COLORS['textDim'])
surface.blit(u_text, (SCREEN_SIZE - 120, stats_y + 18))

# Info connecteur
config = status['config']
info = f"{config['connector']} โ€ข Max {config['max_voltage']}V/{config['max_current']}A"
info_text = self.ui.fonts['small'].render(info, True, COLORS['textDim'])
info_rect = info_text.get_rect(center=(CENTER, stats_y + 55))
surface.blit(info_text, info_rect)

def on_touch(self, pos):
x, y = pos

# Bouton power (centre)
dist = math.sqrt((x - CENTER) ** 2 + (y - CENTER) ** 2)
if dist < 80:
self._toggle_power()
return

# Sรฉlecteur plateforme
if 55 < y < 85:
if x < CENTER - 40:
self.platform_index = (self.platform_index - 1) % len(self.platforms)
self._update_platform()
elif x > CENTER + 40:
self.platform_index = (self.platform_index + 1) % len(self.platforms)
self._update_platform()

def _toggle_power(self):
"""Bascule l'alimentation."""
status = self.controller.get_status()
if status['state'] == 'on':
self.controller.power_off()
else:
self.controller.power_on()

def _update_platform(self):
"""Met ร  jour la plateforme sรฉlectionnรฉe."""
if self.controller.state != PowerState.ON:
platform = self.platforms[self.platform_index]
self.controller.set_platform(platform)

๐Ÿ“Œ 10.5 Schรฉma de cรขblage USB Power

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
Pour EspressoBin (5V USB):
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Pi Zero W EspressoBin
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ”‚ USB Cable โ”‚ โ”‚
โ”‚ USB OTG โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค USB-C โ”‚
โ”‚ (data) โ”‚ D+/D- โ”‚ (debug) โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ VBUS โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค VBUS โ”‚
โ”‚ (5V) โ”‚ 5V @ 2A max โ”‚ (power) โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ GND โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค GND โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜


Pour MochaBin (12V externe avec relay):
โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•

Pi Zero W Relay Module MochaBin
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ โ”‚ โ”‚ โ”‚ 12V PSU โ”‚ โ”‚
โ”‚ GPIO 17 โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค IN โ”‚โ—„โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค Barrel โ”‚
โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ Jack โ”‚
โ”‚ 3.3V โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค VCC โ”‚ โ”‚ โ”‚
โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ GND โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค GND โ”‚ โ”‚ โ”‚
โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ USB OTG โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค USB-C โ”‚
โ”‚ (data) โ”‚ D+/D- only โ”‚ โ”‚ (debug) โ”‚ (debug) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿ“‹ Phase 11 : Moniteur sรฉrie temps rรฉel

๐Ÿ“Œ 10.1 ร‰cran Serial Monitor (ui/screens/serial_monitor.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
#!/usr/bin/env python3
"""
Moniteur sรฉrie temps rรฉel avec scroll fluide et coloration syntaxique.
Optimisรฉ pour รฉcran circulaire 480x480.
"""

import pygame
import math
import re
import threading
from collections import deque
from datetime import datetime
from ui.core import Screen, COLORS, CENTER, RADIUS


class LineType:
"""Types de lignes pour coloration."""
NORMAL = 'normal'
UBOOT = 'uboot'
KERNEL = 'kernel'
ERROR = 'error'
WARNING = 'warning'
PROMPT = 'prompt'
TIMESTAMP = 'timestamp'
SPI = 'spi'
UEFI = 'uefi'


class SerialLine:
"""Reprรฉsente une ligne de la console."""
__slots__ = ['text', 'type', 'timestamp', 'raw']

def __init__(self, text: str, line_type: str = LineType.NORMAL):
self.text = text[:60] # Tronquer pour l'รฉcran rond
self.raw = text
self.type = line_type
self.timestamp = datetime.now()


class SerialBuffer:
"""Buffer circulaire thread-safe avec classification."""

def __init__(self, maxlen: int = 2000):
self.lines = deque(maxlen=maxlen)
self.lock = threading.Lock()
self.new_data = threading.Event()

# Patterns de classification
self.patterns = [
(re.compile(r'^(=>|[A-Za-z0-9_-]+>)\s*'), LineType.PROMPT),
(re.compile(r'U-Boot|SPL|TPL', re.I), LineType.UBOOT),
(re.compile(r'Linux version|kernel:|initramfs', re.I), LineType.KERNEL),
(re.compile(r'error|fail|fatal', re.I), LineType.ERROR),
(re.compile(r'warn|caution', re.I), LineType.WARNING),
(re.compile(r'WTMI|BootROM|SPI|SPINOR', re.I), LineType.SPI),
(re.compile(r'UEFI|EFI|TianoCore|EDK', re.I), LineType.UEFI),
(re.compile(r'^\[\s*\d+\.\d+\]'), LineType.TIMESTAMP),
]

def classify(self, text: str) -> str:
"""Classifie une ligne de texte."""
for pattern, line_type in self.patterns:
if pattern.search(text):
return line_type
return LineType.NORMAL

def append(self, text: str):
"""Ajoute une ligne."""
line_type = self.classify(text)
line = SerialLine(text, line_type)

with self.lock:
self.lines.append(line)
self.new_data.set()

def get_visible(self, start: int, count: int) -> list:
"""Retourne les lignes visibles."""
with self.lock:
lines = list(self.lines)

total = len(lines)
if start < 0:
start = max(0, total + start)

end = min(start + count, total)
return lines[start:end], total

def get_last(self, count: int) -> list:
"""Retourne les derniรจres lignes."""
with self.lock:
return list(self.lines)[-count:]


class SerialMonitorScreen(Screen):
"""ร‰cran moniteur sรฉrie avec scroll temps rรฉel."""

# Couleurs par type de ligne
LINE_COLORS = {
LineType.NORMAL: (200, 200, 200),
LineType.UBOOT: (100, 200, 255), # Bleu clair
LineType.KERNEL: (150, 255, 150), # Vert clair
LineType.ERROR: (255, 100, 100), # Rouge
LineType.WARNING: (255, 200, 100), # Orange
LineType.PROMPT: (255, 255, 100), # Jaune
LineType.TIMESTAMP: (150, 150, 150), # Gris
LineType.SPI: (255, 150, 255), # Magenta
LineType.UEFI: (100, 255, 255), # Cyan
}

def __init__(self, ui):
super().__init__(ui)

self.buffer = SerialBuffer()
self.scroll_offset = 0
self.auto_scroll = True
self.paused = False

# Dimensions zone d'affichage (cercle inscrit)
self.display_radius = RADIUS - 40
self.line_height = 16
self.max_visible_lines = 20

# Zone de texte (rectangle inscrit dans le cercle)
self.text_width = int(self.display_radius * 1.4)
self.text_left = CENTER[0] - self.text_width // 2

# Font monospace
try:
self.mono_font = pygame.font.Font(
'/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf', 12
)
except:
self.mono_font = pygame.font.SysFont('monospace', 12)

# Input buffer
self.input_text = ''
self.input_active = False

# Touch zones
self.scroll_touch_start = None
self.last_touch_y = 0

# Stats
self.bytes_received = 0
self.lines_received = 0

def on_enter(self):
"""Appelรฉ quand l'รฉcran devient actif."""
self.auto_scroll = True

def feed_data(self, data: str):
"""Reรงoit des donnรฉes du port sรฉrie."""
self.bytes_received += len(data)

for line in data.split('\n'):
line = line.strip('\r')
if line:
self.buffer.append(line)
self.lines_received += 1

def draw(self, surface):
# Header circulaire
self._draw_header(surface)

# Zone de log
self._draw_log_area(surface)

# Scrollbar
self._draw_scrollbar(surface)

# Status bar
self._draw_status(surface)

# Input zone
if self.input_active:
self._draw_input(surface)

def _draw_header(self, surface):
"""Dessine l'en-tรชte."""
# Titre
title = self.ui.fonts['medium'].render('Serial Monitor', True, COLORS['primary'])
title_rect = title.get_rect(center=(CENTER[0], 35))
surface.blit(title, title_rect)

# Indicateurs
# Auto-scroll
auto_color = COLORS['secondary'] if self.auto_scroll else COLORS['text_dim']
pygame.draw.circle(surface, auto_color, (CENTER[0] - 80, 55), 5)
auto_text = self.ui.fonts['small'].render('AUTO', True, auto_color)
surface.blit(auto_text, (CENTER[0] - 72, 48))

# Pause
pause_color = COLORS['warning'] if self.paused else COLORS['text_dim']
pygame.draw.circle(surface, pause_color, (CENTER[0] + 50, 55), 5)
pause_text = self.ui.fonts['small'].render('PAUSE', True, pause_color)
surface.blit(pause_text, (CENTER[0] + 58, 48))

def _draw_log_area(self, surface):
"""Dessine la zone de log avec scroll."""
# Fond semi-transparent
log_rect = pygame.Rect(
self.text_left - 10,
70,
self.text_width + 20,
320
)

# Masque arrondi pour la zone de log
log_surface = pygame.Surface((log_rect.width, log_rect.height), pygame.SRCALPHA)
pygame.draw.rect(log_surface, (20, 22, 26, 230),
(0, 0, log_rect.width, log_rect.height),
border_radius=10)
surface.blit(log_surface, log_rect.topleft)

# Obtenir les lignes visibles
if self.auto_scroll:
lines = self.buffer.get_last(self.max_visible_lines)
else:
lines, total = self.buffer.get_visible(
total - self.max_visible_lines - self.scroll_offset,
self.max_visible_lines
)

# Dessiner les lignes
y = 75
for line in lines:
# Vรฉrifier si la ligne est dans le cercle visible
if y > 70 and y < 385:
color = self.LINE_COLORS.get(line.type, self.LINE_COLORS[LineType.NORMAL])

# Tronquer le texte si nรฉcessaire
display_text = line.text
text_surface = self.mono_font.render(display_text, True, color)

# Clip horizontal
clip_rect = pygame.Rect(0, 0, self.text_width, self.line_height)
surface.blit(text_surface, (self.text_left, y), clip_rect)

y += self.line_height

def _draw_scrollbar(self, surface):
"""Dessine la scrollbar verticale."""
_, total = self.buffer.get_visible(0, 1)

if total <= self.max_visible_lines:
return

# Position de la scrollbar (arc sur le cรดtรฉ droit)
bar_height = 250
bar_top = 95

visible_ratio = self.max_visible_lines / total
thumb_height = max(20, int(bar_height * visible_ratio))

if self.auto_scroll:
thumb_pos = bar_height - thumb_height
else:
scroll_ratio = self.scroll_offset / (total - self.max_visible_lines)
thumb_pos = int((bar_height - thumb_height) * (1 - scroll_ratio))

# Track
pygame.draw.line(surface, COLORS['border'],
(CENTER[0] + 170, bar_top),
(CENTER[0] + 170, bar_top + bar_height), 2)

# Thumb
pygame.draw.line(surface, COLORS['primary'],
(CENTER[0] + 170, bar_top + thumb_pos),
(CENTER[0] + 170, bar_top + thumb_pos + thumb_height), 6)

def _draw_status(self, surface):
"""Dessine la barre de status."""
status_y = RADIUS * 2 - 60

# Compteurs
stats = f"RX: {self.bytes_received:,} B | {self.lines_received} lines"
stats_text = self.ui.fonts['small'].render(stats, True, COLORS['text_dim'])
stats_rect = stats_text.get_rect(center=(CENTER[0], status_y))
surface.blit(stats_text, stats_rect)

# Boutons tactiles
btn_y = RADIUS * 2 - 35

# Bouton Clear
pygame.draw.circle(surface, COLORS['bg_light'], (CENTER[0] - 60, btn_y), 20)
pygame.draw.circle(surface, COLORS['border'], (CENTER[0] - 60, btn_y), 20, 2)
clr = self.ui.fonts['small'].render('CLR', True, COLORS['text'])
clr_rect = clr.get_rect(center=(CENTER[0] - 60, btn_y))
surface.blit(clr, clr_rect)

# Bouton Send
pygame.draw.circle(surface, COLORS['primary'], (CENTER[0] + 60, btn_y), 20)
snd = self.ui.fonts['small'].render('SND', True, COLORS['text'])
snd_rect = snd.get_rect(center=(CENTER[0] + 60, btn_y))
surface.blit(snd, snd_rect)

def _draw_input(self, surface):
"""Dessine la zone de saisie."""
input_rect = pygame.Rect(self.text_left, 395, self.text_width, 25)
pygame.draw.rect(surface, COLORS['bg_light'], input_rect, border_radius=5)
pygame.draw.rect(surface, COLORS['primary'], input_rect, 2, border_radius=5)

# Texte saisi
input_display = '> ' + self.input_text + '_'
input_surface = self.mono_font.render(input_display, True, COLORS['text'])
surface.blit(input_surface, (input_rect.x + 5, input_rect.y + 5))

def on_touch(self, pos):
"""Gรจre les touches."""
x, y = pos

# Zone header - toggle auto-scroll
if 45 < y < 65:
if CENTER[0] - 90 < x < CENTER[0] - 40:
self.auto_scroll = not self.auto_scroll
return
elif CENTER[0] + 40 < x < CENTER[0] + 100:
self.paused = not self.paused
return

# Zone boutons bas
btn_y = RADIUS * 2 - 35
if btn_y - 25 < y < btn_y + 25:
# Clear
if abs(x - (CENTER[0] - 60)) < 25:
self.buffer.lines.clear()
self.bytes_received = 0
self.lines_received = 0
return
# Send
elif abs(x - (CENTER[0] + 60)) < 25:
self.input_active = not self.input_active
return

# Zone log - dรฉbut scroll
if 70 < y < 390:
self.scroll_touch_start = y
self.last_touch_y = y
self.auto_scroll = False

def on_touch_move(self, pos):
"""Gรจre le dรฉplacement tactile (scroll)."""
if self.scroll_touch_start is not None:
delta = self.last_touch_y - pos[1]
self.last_touch_y = pos[1]

# Convertir en lignes
lines_delta = delta / self.line_height
self.scroll_offset = max(0, self.scroll_offset + int(lines_delta))

def on_touch_end(self, pos):
"""Fin du touch."""
self.scroll_touch_start = None

def on_key(self, key, char):
"""Gรจre la saisie clavier."""
if not self.input_active:
return

if key == pygame.K_RETURN:
# Envoyer la commande
if hasattr(self, 'serial_console') and self.serial_console:
self.serial_console.send(self.input_text + '\n')
self.input_text = ''
elif key == pygame.K_BACKSPACE:
self.input_text = self.input_text[:-1]
elif char and ord(char) >= 32:
self.input_text += char

def send_break(self):
"""Envoie un signal BREAK."""
if hasattr(self, 'serial_console') and self.serial_console:
self.serial_console.send_break()

๐Ÿ“Œ 10.2 Widget de visualisation flux (ui/widgets/stream_view.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
#!/usr/bin/env python3
"""Widget de visualisation du flux sรฉrie en temps rรฉel."""

import pygame
import math
from collections import deque


class StreamView:
"""Visualisation graphique du dรฉbit sรฉrie."""

def __init__(self, x, y, width, height):
self.rect = pygame.Rect(x, y, width, height)
self.samples = deque(maxlen=100)
self.max_value = 1000 # bytes/sec

def add_sample(self, bytes_per_sec: int):
"""Ajoute un รฉchantillon de dรฉbit."""
self.samples.append(min(bytes_per_sec, self.max_value))

# Ajuster l'รฉchelle automatiquement
if bytes_per_sec > self.max_value * 0.8:
self.max_value = int(bytes_per_sec * 1.5)

def draw(self, surface, color=(0, 149, 218)):
"""Dessine le graphique."""
if len(self.samples) < 2:
return

# Fond
pygame.draw.rect(surface, (30, 32, 36), self.rect, border_radius=5)

# Points du graphique
points = []
sample_width = self.rect.width / (len(self.samples) - 1)

for i, sample in enumerate(self.samples):
x = self.rect.x + i * sample_width
y = self.rect.bottom - (sample / self.max_value) * self.rect.height
points.append((x, y))

# Ligne du graphique
if len(points) >= 2:
pygame.draw.lines(surface, color, False, points, 2)

# Remplissage sous la courbe
if len(points) >= 2:
fill_points = points + [
(self.rect.right, self.rect.bottom),
(self.rect.x, self.rect.bottom)
]
fill_surface = pygame.Surface(
(self.rect.width, self.rect.height), pygame.SRCALPHA
)
adjusted_points = [
(p[0] - self.rect.x, p[1] - self.rect.y)
for p in fill_points
]
pygame.draw.polygon(fill_surface, (*color, 50), adjusted_points)
surface.blit(fill_surface, self.rect.topleft)

๐Ÿ“‹ Phase 11 : USB Mass Storage Gadget

๐Ÿ“Œ 11.1 Configuration composite gadget (serial/gadget_composite.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
#!/usr/bin/env python3
"""
USB Gadget composite : Serial + Mass Storage
Permet d'exposer une image disque contenant les fichiers d'installation.
"""

import os
import subprocess
from pathlib import Path
from typing import Optional


class CompositeGadget:
"""Gadget USB composite avec serial et stockage de masse."""

GADGET_PATH = Path('/sys/kernel/config/usb_gadget/pidebugger')
STORAGE_IMAGE = Path('/home/pi/pidebugger/storage/armbian_files.img')
STORAGE_MOUNT = Path('/home/pi/pidebugger/storage/mount')

def __init__(self):
self.configured = False
self.serial_tty = None

def create_storage_image(self, size_mb: int = 512):
"""Crรฉe l'image disque pour le stockage de masse."""

self.STORAGE_IMAGE.parent.mkdir(parents=True, exist_ok=True)
self.STORAGE_MOUNT.mkdir(parents=True, exist_ok=True)

if not self.STORAGE_IMAGE.exists():
print(f'Crรฉation image {size_mb}MB...')

# Crรฉer fichier sparse
subprocess.run([
'dd', 'if=/dev/zero', f'of={self.STORAGE_IMAGE}',
'bs=1M', f'count={size_mb}'
], check=True)

# Formater en FAT32
subprocess.run([
'mkfs.vfat', '-F', '32', '-n', 'PIDEBUGGER',
str(self.STORAGE_IMAGE)
], check=True)

print('Image crรฉรฉe et formatรฉe')

return self.STORAGE_IMAGE

def mount_storage(self) -> Path:
"""Monte l'image de stockage pour accรจs local."""
if not self._is_mounted():
subprocess.run([
'mount', '-o', 'loop',
str(self.STORAGE_IMAGE),
str(self.STORAGE_MOUNT)
], check=True)
return self.STORAGE_MOUNT

def unmount_storage(self):
"""Dรฉmonte le stockage."""
if self._is_mounted():
subprocess.run(['umount', str(self.STORAGE_MOUNT)], check=True)

def _is_mounted(self) -> bool:
"""Vรฉrifie si le stockage est montรฉ."""
result = subprocess.run(
['mountpoint', '-q', str(self.STORAGE_MOUNT)],
capture_output=True
)
return result.returncode == 0

def setup(self) -> str:
"""Configure le gadget composite."""

# Charger module
subprocess.run(['modprobe', 'libcomposite'], check=True)

gadget = self.GADGET_PATH
gadget.mkdir(exist_ok=True)

# === IDs USB ===
(gadget / 'idVendor').write_text('0x1d6b') # Linux Foundation
(gadget / 'idProduct').write_text('0x0104') # Composite Gadget
(gadget / 'bcdDevice').write_text('0x0100')
(gadget / 'bcdUSB').write_text('0x0200')
(gadget / 'bDeviceClass').write_text('0xEF') # Misc
(gadget / 'bDeviceSubClass').write_text('0x02')
(gadget / 'bDeviceProtocol').write_text('0x01') # IAD

# === Strings ===
strings = gadget / 'strings/0x409'
strings.mkdir(parents=True, exist_ok=True)
(strings / 'serialnumber').write_text('PIDEBUG001')
(strings / 'manufacturer').write_text('PiDebugger')
(strings / 'product').write_text('Debug Console + Storage')

# === Configuration ===
config = gadget / 'configs/c.1'
config.mkdir(parents=True, exist_ok=True)
(config / 'MaxPower').write_text('250')

config_strings = config / 'strings/0x409'
config_strings.mkdir(parents=True, exist_ok=True)
(config_strings / 'configuration').write_text('Serial + Mass Storage')

# === Fonction ACM (Serial) ===
acm = gadget / 'functions/acm.usb0'
acm.mkdir(exist_ok=True)

# === Fonction Mass Storage ===
mass = gadget / 'functions/mass_storage.usb0'
mass.mkdir(exist_ok=True)

# Configurer le LUN
lun = mass / 'lun.0'
lun.mkdir(exist_ok=True)

# S'assurer que l'image existe
self.create_storage_image()

# Dรฉmonter si montรฉ localement
self.unmount_storage()

(lun / 'file').write_text(str(self.STORAGE_IMAGE))
(lun / 'removable').write_text('1')
(lun / 'ro').write_text('0') # Read-write
(lun / 'cdrom').write_text('0')

# === Lier les fonctions ===
acm_link = config / 'acm.usb0'
if not acm_link.exists():
acm_link.symlink_to(acm)

mass_link = config / 'mass_storage.usb0'
if not mass_link.exists():
mass_link.symlink_to(mass)

# === Activer ===
udc = list(Path('/sys/class/udc').iterdir())[0].name
(gadget / 'UDC').write_text(udc)

self.configured = True
self.serial_tty = '/dev/ttyGS0'

return self.serial_tty

def teardown(self):
"""Dรฉsactive le gadget."""
gadget = self.GADGET_PATH

if not gadget.exists():
return

try:
(gadget / 'UDC').write_text('')
except:
pass

# Supprimer liens
config = gadget / 'configs/c.1'
for link in config.glob('*.usb*'):
try:
link.unlink()
except:
pass

# Supprimer dans l'ordre
dirs_to_remove = [
'functions/mass_storage.usb0/lun.0',
'functions/mass_storage.usb0',
'functions/acm.usb0',
'configs/c.1/strings/0x409',
'configs/c.1',
'strings/0x409',
''
]

for d in dirs_to_remove:
try:
(gadget / d).rmdir()
except:
pass

self.configured = False


class StorageManager:
"""Gestionnaire du contenu du stockage de masse."""

def __init__(self, gadget: CompositeGadget):
self.gadget = gadget
self.structure = {
'armbian': {
'espressobin': [],
'mochabin': [],
'espressobin-ultra': [],
},
'uboot': {
'espressobin': [],
'mochabin': [],
},
'scripts': [],
'configs': [],
}

def initialize_structure(self):
"""Crรฉe la structure de dossiers sur le stockage."""
mount = self.gadget.mount_storage()

# Crรฉer arborescence
for category, content in self.structure.items():
cat_path = mount / category
cat_path.mkdir(exist_ok=True)

if isinstance(content, dict):
for subcat in content:
(cat_path / subcat).mkdir(exist_ok=True)

# Crรฉer README
readme = mount / 'README.txt'
readme.write_text("""PiDebugger Storage
==================

Structure:
- armbian/ Images Armbian par plateforme
- espressobin/ EspressoBin v5/v7
- mochabin/ MochaBin
- espressobin-ultra/

- uboot/ Binaires U-Boot et flash tools
- espressobin/
- mochabin/

- scripts/ Scripts d'installation automatique
- configs/ Fichiers de configuration (env, dtb)

Utilisation:
1. Copier les images dans les dossiers appropriรฉs
2. Le PiDebugger dรฉtectera automatiquement les fichiers
3. Utiliser l'interface pour flasher/installer
""")

self.gadget.unmount_storage()

def list_files(self, category: str = None) -> dict:
"""Liste les fichiers disponibles."""
mount = self.gadget.mount_storage()

result = {}

try:
if category:
cat_path = mount / category
if cat_path.is_dir():
result[category] = self._scan_dir(cat_path)
else:
for cat in self.structure:
cat_path = mount / cat
if cat_path.is_dir():
result[cat] = self._scan_dir(cat_path)
finally:
self.gadget.unmount_storage()

return result

def _scan_dir(self, path: Path) -> dict:
"""Scanne rรฉcursivement un dossier."""
result = {}

for item in path.iterdir():
if item.is_dir():
result[item.name] = self._scan_dir(item)
else:
if item.name not in result:
result['_files'] = []
result.setdefault('_files', []).append({
'name': item.name,
'size': item.stat().st_size,
})

return result

๐Ÿ“Œ 11.2 Gestionnaire Armbian/U-Boot (storage/firmware_manager.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
#!/usr/bin/env python3
"""
Gestionnaire des firmwares pour EspressoBin, MochaBin et Ultra.
Gรจre mvebu64boot, flash-image.bin, et installations Armbian.
"""

import os
import re
import hashlib
from pathlib import Path
from dataclasses import dataclass
from typing import Optional, List
from enum import Enum


class Platform(Enum):
ESPRESSOBIN_V5 = 'espressobin-v5'
ESPRESSOBIN_V7 = 'espressobin-v7'
ESPRESSOBIN_ULTRA = 'espressobin-ultra'
MOCHABIN = 'mochabin'


class MemoryConfig(Enum):
DDR3_1GB_1CS = '1g-1cs'
DDR3_2GB_2CS = '2g-2cs'
DDR4_4GB = '4g-ddr4'
DDR4_8GB = '8g-ddr4'


@dataclass
class FirmwareInfo:
"""Information sur un firmware."""
name: str
platform: Platform
version: str
path: Path
size: int
sha256: Optional[str] = None
memory_config: Optional[MemoryConfig] = None
description: str = ''


class MvebuBootManager:
"""
Gestionnaire pour mvebu64boot (MochaBin) et รฉquivalent EspressoBin.

Structure flash Marvell Armada:
- SPI NOR: 0x000000 - 0x200000 (2MB)
- 0x000000: WTMI (Trusted firmware)
- 0x020000: U-Boot
- 0x1F0000: U-Boot env
"""

FLASH_LAYOUT = {
Platform.MOCHABIN: {
'wtmi_offset': 0x000000,
'uboot_offset': 0x020000,
'env_offset': 0x1F0000,
'total_size': 0x200000,
},
Platform.ESPRESSOBIN_V7: {
'wtmi_offset': 0x000000,
'uboot_offset': 0x020000,
'env_offset': 0x1F0000,
'total_size': 0x200000,
},
Platform.ESPRESSOBIN_ULTRA: {
'wtmi_offset': 0x000000,
'uboot_offset': 0x020000,
'env_offset': 0x3F0000,
'total_size': 0x400000, # 4MB SPI
},
}

def __init__(self, storage_path: Path):
self.storage_path = storage_path
self.uboot_path = storage_path / 'uboot'
self.firmwares: List[FirmwareInfo] = []

def scan_firmwares(self) -> List[FirmwareInfo]:
"""Scanne les firmwares disponibles."""
self.firmwares = []

for platform in Platform:
platform_path = self.uboot_path / platform.value.split('-')[0]
if not platform_path.exists():
continue

for fw_file in platform_path.glob('*.bin'):
info = self._parse_firmware(fw_file, platform)
if info:
self.firmwares.append(info)

return self.firmwares

def _parse_firmware(self, path: Path, platform: Platform) -> Optional[FirmwareInfo]:
"""Parse le nom d'un firmware pour extraire les infos."""
name = path.stem

# Patterns de nom de fichier
# flash-image-espressobin-v7-2g-2cs-2000_800.bin
# u-boot-mochabin-ddr4-4g.bin

patterns = [
r'flash-image-(?P<plat>\w+)-(?P<ver>v\d+)-(?P<mem>\d+g-\d+cs)',
r'u-boot-(?P<plat>\w+)-(?P<mem>ddr\d+-\d+g)',
r'(?P<plat>espressobin|mochabin).*(?P<mem>\d+g)',
]

version = 'unknown'
mem_config = None

for pattern in patterns:
m = re.search(pattern, name, re.I)
if m:
groups = m.groupdict()
if 'ver' in groups:
version = groups['ver']
if 'mem' in groups:
mem_str = groups['mem'].lower()
for mc in MemoryConfig:
if mc.value in mem_str or mem_str in mc.value:
mem_config = mc
break
break

return FirmwareInfo(
name=name,
platform=platform,
version=version,
path=path,
size=path.stat().st_size,
sha256=self._compute_hash(path),
memory_config=mem_config,
)

def _compute_hash(self, path: Path) -> str:
"""Calcule le SHA256 d'un fichier."""
sha256 = hashlib.sha256()
with open(path, 'rb') as f:
for chunk in iter(lambda: f.read(8192), b''):
sha256.update(chunk)
return sha256.hexdigest()[:16]

def get_flash_commands(self, firmware: FirmwareInfo) -> List[str]:
"""Gรฉnรจre les commandes U-Boot pour flasher le firmware."""
layout = self.FLASH_LAYOUT.get(firmware.platform)
if not layout:
return []

filename = firmware.path.name

commands = [
f'# Flash {firmware.name} sur {firmware.platform.value}',
'',
'# Mรฉthode USB:',
'usb start',
f'load usb 0:1 $loadaddr {filename}',
f'sf probe',
f'sf erase 0 {hex(layout["total_size"])}',
f'sf write $loadaddr 0 $filesize',
'',
'# Mรฉthode TFTP:',
'setenv serverip 192.168.1.1',
'setenv ipaddr 192.168.1.100',
f'tftp $loadaddr {filename}',
f'sf probe',
f'sf erase 0 {hex(layout["total_size"])}',
f'sf write $loadaddr 0 $filesize',
'',
'# Vรฉrification:',
'sf probe',
f'sf read $loadaddr 0 0x100',
'md $loadaddr 0x10',
]

return commands

def get_recovery_commands(self, platform: Platform) -> List[str]:
"""Commandes de recovery via UART/XMODEM."""
commands = [
f'# Recovery {platform.value} via UART',
'',
'# 1. Connecter UART (115200 8N1)',
'# 2. Mettre la carte sous tension',
'# 3. Envoyer pattern de boot escape',
'',
]

if platform in [Platform.MOCHABIN, Platform.ESPRESSOBIN_ULTRA]:
commands.extend([
'# MochaBin/Ultra - mvebu64boot:',
'# - Utiliser mvebu64boot pour upload WTMI+U-Boot',
'# - Commande: mvebu64boot -t -b flash-image.bin /dev/ttyUSB0',
])
else:
commands.extend([
'# EspressoBin - WTP download:',
'# - Utiliser WtpDownload ou uart-download.py',
'# - Patterns escape: 0xBB 0x11 0x22 0x33...',
])

return commands


class ArmbianManager:
"""Gestionnaire des images Armbian."""

SUPPORTED_IMAGES = {
Platform.ESPRESSOBIN_V7: [
'Armbian_*_Espressobin_*.img*',
],
Platform.MOCHABIN: [
'Armbian_*_Mochabin_*.img*',
],
Platform.ESPRESSOBIN_ULTRA: [
'Armbian_*_Espressobin-ultra_*.img*',
],
}

def __init__(self, storage_path: Path):
self.storage_path = storage_path
self.armbian_path = storage_path / 'armbian'
self.images: List[FirmwareInfo] = []

def scan_images(self) -> List[FirmwareInfo]:
"""Scanne les images Armbian disponibles."""
self.images = []

for platform in Platform:
plat_name = platform.value.split('-')[0]
platform_path = self.armbian_path / plat_name

if not platform_path.exists():
continue

for img_file in platform_path.glob('*.img*'):
info = self._parse_image(img_file, platform)
if info:
self.images.append(info)

return self.images

def _parse_image(self, path: Path, platform: Platform) -> Optional[FirmwareInfo]:
"""Parse une image Armbian."""
name = path.stem

# Armbian_24.11_Espressobin_bookworm_current_6.6.38.img
m = re.search(r'Armbian_(\d+\.\d+)_\w+_(\w+)_(\w+)_(\d+\.\d+)', name)

version = 'unknown'
description = ''

if m:
version = m.group(1)
release = m.group(2)
branch = m.group(3)
kernel = m.group(4)
description = f'{release} {branch} kernel {kernel}'

return FirmwareInfo(
name=name,
platform=platform,
version=version,
path=path,
size=path.stat().st_size,
description=description,
)

def get_install_commands(self, image: FirmwareInfo, target_dev: str = 'mmc') -> List[str]:
"""Gรฉnรจre les commandes d'installation."""
commands = [
f'# Installation Armbian {image.version}',
f'# Image: {image.name}',
f'# Plateforme: {image.platform.value}',
'',
]

if image.path.suffix == '.xz':
commands.extend([
'# Image compressรฉe XZ',
f'xzcat {image.path.name} | dd of=/dev/{target_dev} bs=4M status=progress',
])
elif image.path.suffix == '.gz':
commands.extend([
'# Image compressรฉe GZ',
f'zcat {image.path.name} | dd of=/dev/{target_dev} bs=4M status=progress',
])
else:
commands.extend([
'# Image raw',
f'dd if={image.path.name} of=/dev/{target_dev} bs=4M status=progress',
])

commands.extend([
'',
'# Post-installation:',
'sync',
f'partprobe /dev/{target_dev}',
f'mount /dev/{target_dev}p1 /mnt',
'ls /mnt/',
])

return commands

๐Ÿ“‹ Phase 12 : Analyseur SPI Boot

๐Ÿ“Œ 12.1 Parser SPI/WTMI (analyzers/spi_boot.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#!/usr/bin/env python3
"""
Analyseur de sรฉquence boot SPI pour Marvell Armada.
Parse les messages WTMI, BootROM et initialisation early boot.
"""

import re
from dataclasses import dataclass, field
from typing import Optional, List
from enum import Enum
from datetime import datetime


class BootStage(Enum):
BOOTROM = 'bootrom'
WTMI = 'wtmi'
ATF_BL1 = 'atf_bl1'
ATF_BL2 = 'atf_bl2'
ATF_BL31 = 'atf_bl31'
UBOOT_SPL = 'uboot_spl'
UBOOT = 'uboot'
KERNEL = 'kernel'
UNKNOWN = 'unknown'


class BootSource(Enum):
SPI = 'spi'
EMMC = 'emmc'
SD = 'sd'
UART = 'uart'
SATA = 'sata'
UNKNOWN = 'unknown'


@dataclass
class BootEvent:
"""ร‰vรฉnement de boot."""
timestamp: datetime
stage: BootStage
message: str
raw_line: str
level: str = 'info' # info, warning, error
data: dict = field(default_factory=dict)


@dataclass
class HardwareInfo:
"""Informations matรฉrielles dรฉtectรฉes."""
cpu: Optional[str] = None
cpu_freq: Optional[int] = None
ddr_type: Optional[str] = None
ddr_size: Optional[str] = None
ddr_freq: Optional[int] = None
board: Optional[str] = None
boot_source: BootSource = BootSource.UNKNOWN
chip_revision: Optional[str] = None
efuse: Optional[str] = None


class SPIBootAnalyzer:
"""Analyseur de sรฉquence boot SPI Marvell."""

def __init__(self):
self.events: List[BootEvent] = []
self.hardware = HardwareInfo()
self.current_stage = BootStage.UNKNOWN
self.start_time: Optional[datetime] = None

# Patterns de dรฉtection
self.stage_patterns = {
BootStage.BOOTROM: [
re.compile(r'BootROM', re.I),
re.compile(r'Boot\s*ROM\s*:\s*Image', re.I),
],
BootStage.WTMI: [
re.compile(r'WTMI', re.I),
re.compile(r'SVC REV:', re.I),
re.compile(r'NOTICE:\s*Booting', re.I),
],
BootStage.ATF_BL1: [
re.compile(r'BL1:\s', re.I),
],
BootStage.ATF_BL2: [
re.compile(r'BL2:\s', re.I),
],
BootStage.ATF_BL31: [
re.compile(r'BL31:\s', re.I),
],
BootStage.UBOOT_SPL: [
re.compile(r'U-Boot SPL', re.I),
],
BootStage.UBOOT: [
re.compile(r'^U-Boot \d', re.I),
],
BootStage.KERNEL: [
re.compile(r'Linux version', re.I),
re.compile(r'Booting Linux', re.I),
],
}

# Patterns d'extraction hardware
self.hw_patterns = {
'cpu': re.compile(r'CPU:\s*(.+)|SoC:\s*(.+)', re.I),
'cpu_freq': re.compile(r'CPU.*?(\d+)\s*MHz', re.I),
'ddr_type': re.compile(r'(DDR[34][A-Z]*)', re.I),
'ddr_size': re.compile(r'DRAM:\s*(\d+\s*[GMK]i?B)', re.I),
'ddr_freq': re.compile(r'DDR.*?(\d+)\s*MHz', re.I),
'board': re.compile(r'Board:\s*(.+)|Model:\s*(.+)', re.I),
'boot_source': re.compile(r'Boot\s*(?:device|source):\s*(\w+)', re.I),
'chip_rev': re.compile(r'(?:Chip|SoC)\s*(?:rev|revision)[:\s]*(\w+)', re.I),
}

# Patterns d'erreur
self.error_patterns = [
re.compile(r'error|fail|fatal|panic|abort', re.I),
re.compile(r'DDR\s*init.*fail', re.I),
re.compile(r'SPI.*fail', re.I),
re.compile(r'timeout', re.I),
]

self.warning_patterns = [
re.compile(r'warn|caution', re.I),
re.compile(r'retry', re.I),
]

def feed(self, line: str) -> Optional[BootEvent]:
"""Analyse une ligne et retourne un รฉvรฉnement si pertinent."""
if not self.start_time:
self.start_time = datetime.now()

line = line.strip()
if not line:
return None

# Dรฉtecter le stage
new_stage = self._detect_stage(line)
if new_stage != BootStage.UNKNOWN:
self.current_stage = new_stage

# Dรฉtecter le niveau
level = 'info'
for pattern in self.error_patterns:
if pattern.search(line):
level = 'error'
break
if level == 'info':
for pattern in self.warning_patterns:
if pattern.search(line):
level = 'warning'
break

# Extraire hardware info
self._extract_hardware(line)

# Crรฉer รฉvรฉnement
event = BootEvent(
timestamp=datetime.now(),
stage=self.current_stage,
message=self._clean_message(line),
raw_line=line,
level=level,
)

self.events.append(event)
return event

def _detect_stage(self, line: str) -> BootStage:
"""Dรฉtecte le stage de boot actuel."""
for stage, patterns in self.stage_patterns.items():
for pattern in patterns:
if pattern.search(line):
return stage
return BootStage.UNKNOWN

def _extract_hardware(self, line: str):
"""Extrait les infos hardware."""
for key, pattern in self.hw_patterns.items():
m = pattern.search(line)
if m:
value = next((g for g in m.groups() if g), None)
if value:
value = value.strip()

if key == 'boot_source':
for bs in BootSource:
if bs.value in value.lower():
self.hardware.boot_source = bs
break
elif key == 'cpu_freq':
self.hardware.cpu_freq = int(value)
elif key == 'ddr_freq':
self.hardware.ddr_freq = int(value)
elif key == 'cpu':
self.hardware.cpu = value
elif key == 'ddr_type':
self.hardware.ddr_type = value
elif key == 'ddr_size':
self.hardware.ddr_size = value
elif key == 'board':
self.hardware.board = value
elif key == 'chip_rev':
self.hardware.chip_revision = value

def _clean_message(self, line: str) -> str:
"""Nettoie un message pour affichage."""
# Supprimer timestamps type [0.000000]
line = re.sub(r'^\[\s*\d+\.\d+\]\s*', '', line)
# Supprimer prรฉfixes NOTICE: WARNING: etc
line = re.sub(r'^(NOTICE|WARNING|ERROR|INFO):\s*', '', line)
return line[:80] # Tronquer

def get_timeline(self) -> List[dict]:
"""Retourne une timeline des รฉvรฉnements."""
if not self.events:
return []

timeline = []
stage_events = {}

for event in self.events:
stage = event.stage.value
if stage not in stage_events:
stage_events[stage] = {
'stage': stage,
'start': event.timestamp,
'end': event.timestamp,
'count': 0,
'errors': 0,
'warnings': 0,
}

stage_events[stage]['end'] = event.timestamp
stage_events[stage]['count'] += 1

if event.level == 'error':
stage_events[stage]['errors'] += 1
elif event.level == 'warning':
stage_events[stage]['warnings'] += 1

# Calculer durรฉes
for stage_data in stage_events.values():
duration = (stage_data['end'] - stage_data['start']).total_seconds()
stage_data['duration_ms'] = int(duration * 1000)
timeline.append(stage_data)

return sorted(timeline, key=lambda x: x['start'])

def get_summary(self) -> dict:
"""Retourne un rรฉsumรฉ de l'analyse."""
timeline = self.get_timeline()

total_duration = 0
if self.events:
total_duration = (
self.events[-1].timestamp - self.events[0].timestamp
).total_seconds()

return {
'hardware': {
'cpu': self.hardware.cpu,
'cpu_freq_mhz': self.hardware.cpu_freq,
'ddr': f'{self.hardware.ddr_type} {self.hardware.ddr_size}',
'ddr_freq_mhz': self.hardware.ddr_freq,
'board': self.hardware.board,
'boot_source': self.hardware.boot_source.value,
},
'boot': {
'total_duration_s': round(total_duration, 2),
'stages': len(timeline),
'total_events': len(self.events),
'errors': sum(1 for e in self.events if e.level == 'error'),
'warnings': sum(1 for e in self.events if e.level == 'warning'),
},
'timeline': timeline,
}

def detect_platform(self) -> Optional[str]:
"""Tente de dรฉtecter la plateforme."""
board = (self.hardware.board or '').lower()

if 'mochabin' in board:
return 'mochabin'
elif 'espressobin' in board:
if 'ultra' in board:
return 'espressobin-ultra'
return 'espressobin'
elif 'armada' in board:
return 'armada-generic'

# Dรฉtection par patterns dans les logs
all_text = ' '.join(e.raw_line for e in self.events[:50])

if 'CN9130' in all_text or 'cn9130' in all_text:
return 'mochabin'
elif 'A3720' in all_text or 'a3720' in all_text:
return 'espressobin'

return None

๐Ÿ“Œ 12.2 ร‰cran Boot Analyzer (ui/screens/boot_analyzer.py)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
#!/usr/bin/env python3
"""ร‰cran d'analyse de boot avec timeline visuelle."""

import pygame
import math
from datetime import datetime
from ui.core import Screen, COLORS, CENTER, RADIUS
from analyzers.spi_boot import SPIBootAnalyzer, BootStage


class BootAnalyzerScreen(Screen):
"""Visualisation de la sรฉquence de boot."""

STAGE_COLORS = {
BootStage.BOOTROM: (200, 100, 100),
BootStage.WTMI: (255, 150, 100),
BootStage.ATF_BL1: (255, 200, 100),
BootStage.ATF_BL2: (200, 255, 100),
BootStage.ATF_BL31: (100, 255, 150),
BootStage.UBOOT_SPL: (100, 200, 255),
BootStage.UBOOT: (100, 150, 255),
BootStage.KERNEL: (150, 100, 255),
BootStage.UNKNOWN: (128, 128, 128),
}

def __init__(self, ui):
super().__init__(ui)
self.analyzer = SPIBootAnalyzer()
self.selected_stage = None
self.view_mode = 'timeline' # timeline, details, hardware

def feed_line(self, line: str):
"""Reรงoit une ligne de la console."""
self.analyzer.feed(line)

def draw(self, surface):
if self.view_mode == 'timeline':
self._draw_timeline(surface)
elif self.view_mode == 'details':
self._draw_details(surface)
elif self.view_mode == 'hardware':
self._draw_hardware(surface)

# Navigation
self._draw_nav(surface)

def _draw_timeline(self, surface):
"""Dessine la timeline circulaire."""
# Titre
title = self.ui.fonts['medium'].render('Boot Timeline', True, COLORS['primary'])
title_rect = title.get_rect(center=(CENTER[0], 35))
surface.blit(title, title_rect)

timeline = self.analyzer.get_timeline()

if not timeline:
# Pas de donnรฉes
msg = self.ui.fonts['medium'].render('En attente...', True, COLORS['text_dim'])
msg_rect = msg.get_rect(center=CENTER)
surface.blit(msg, msg_rect)
return

# Calculer les angles pour chaque stage
total_duration = sum(s['duration_ms'] for s in timeline) or 1

# Timeline circulaire
timeline_radius = RADIUS - 80
start_angle = -90

for i, stage_data in enumerate(timeline):
stage = BootStage(stage_data['stage'])
duration_ratio = stage_data['duration_ms'] / total_duration
sweep_angle = max(10, duration_ratio * 360) # Min 10 degrรฉs

color = self.STAGE_COLORS.get(stage, (128, 128, 128))

# Arc pour ce stage
rect = pygame.Rect(
CENTER[0] - timeline_radius,
CENTER[1] - timeline_radius,
timeline_radius * 2,
timeline_radius * 2
)

start_rad = math.radians(start_angle)
end_rad = math.radians(start_angle + sweep_angle)

# Dessiner l'arc (approximation avec lignes)
points = []
for angle in range(int(start_angle), int(start_angle + sweep_angle) + 1, 2):
rad = math.radians(angle)
x = CENTER[0] + timeline_radius * math.cos(rad)
y = CENTER[1] + timeline_radius * math.sin(rad)
points.append((x, y))

if len(points) >= 2:
pygame.draw.lines(surface, color, False, points, 15)

# Label au milieu de l'arc
mid_angle = math.radians(start_angle + sweep_angle / 2)
label_r = timeline_radius - 40
lx = CENTER[0] + label_r * math.cos(mid_angle)
ly = CENTER[1] + label_r * math.sin(mid_angle)

# Nom court du stage
short_name = stage.value[:6].upper()
label = self.ui.fonts['small'].render(short_name, True, color)
label_rect = label.get_rect(center=(lx, ly))
surface.blit(label, label_rect)

start_angle += sweep_angle

# Centre - rรฉsumรฉ
summary = self.analyzer.get_summary()

duration_text = f"{summary['boot']['total_duration_s']}s"
dur_surf = self.ui.fonts['xlarge'].render(duration_text, True, COLORS['text'])
dur_rect = dur_surf.get_rect(center=(CENTER[0], CENTER[1] - 20))
surface.blit(dur_surf, dur_rect)

boot_label = self.ui.fonts['small'].render('Boot time', True, COLORS['text_dim'])
boot_rect = boot_label.get_rect(center=(CENTER[0], CENTER[1] + 20))
surface.blit(boot_label, boot_rect)

# Erreurs/warnings
if summary['boot']['errors'] > 0:
err = self.ui.fonts['small'].render(
f"โš  {summary['boot']['errors']} errors",
True, COLORS['danger']
)
err_rect = err.get_rect(center=(CENTER[0], CENTER[1] + 50))
surface.blit(err, err_rect)

def _draw_hardware(self, surface):
"""Dessine les infos hardware."""
title = self.ui.fonts['medium'].render('Hardware Info', True, COLORS['primary'])
title_rect = title.get_rect(center=(CENTER[0], 35))
surface.blit(title, title_rect)

hw = self.analyzer.hardware

info_lines = [
('CPU', hw.cpu or 'Unknown'),
('Freq', f'{hw.cpu_freq} MHz' if hw.cpu_freq else 'Unknown'),
('RAM', f'{hw.ddr_type} {hw.ddr_size}' if hw.ddr_size else 'Unknown'),
('DDR', f'{hw.ddr_freq} MHz' if hw.ddr_freq else 'Unknown'),
('Board', hw.board or 'Unknown'),
('Boot', hw.boot_source.value),
]

y = 80
for label, value in info_lines:
# Label
lbl = self.ui.fonts['small'].render(label + ':', True, COLORS['text_dim'])
surface.blit(lbl, (CENTER[0] - 100, y))

# Value
val = self.ui.fonts['medium'].render(value[:20], True, COLORS['text'])
surface.blit(val, (CENTER[0] - 30, y - 2))

y += 35

# Platform dรฉtectรฉe
platform = self.analyzer.detect_platform()
if platform:
plat_text = self.ui.fonts['large'].render(
platform.upper(), True, COLORS['primary']
)
plat_rect = plat_text.get_rect(center=(CENTER[0], RADIUS * 2 - 80))
surface.blit(plat_text, plat_rect)

def _draw_details(self, surface):
"""Dessine les derniers รฉvรฉnements."""
title = self.ui.fonts['medium'].render('Boot Events', True, COLORS['primary'])
title_rect = title.get_rect(center=(CENTER[0], 35))
surface.blit(title, title_rect)

# Derniers รฉvรฉnements
events = self.analyzer.events[-15:]

y = 65
for event in events:
color = COLORS['text']
if event.level == 'error':
color = COLORS['danger']
elif event.level == 'warning':
color = COLORS['warning']

# Stage tag
stage_color = self.STAGE_COLORS.get(event.stage, (128, 128, 128))
pygame.draw.rect(surface, stage_color, (50, y, 4, 18))

# Message
msg = self.ui.fonts['small'].render(
event.message[:45], True, color
)
surface.blit(msg, (60, y + 2))

y += 22

def _draw_nav(self, surface):
"""Dessine les boutons de navigation."""
btn_y = RADIUS * 2 - 35
modes = ['timeline', 'hardware', 'details']

for i, mode in enumerate(modes):
x = CENTER[0] - 70 + i * 70

is_active = self.view_mode == mode
color = COLORS['primary'] if is_active else COLORS['bg_light']

pygame.draw.circle(surface, color, (x, btn_y), 25)
pygame.draw.circle(surface, COLORS['border'], (x, btn_y), 25, 2)

label = mode[:4].upper()
text = self.ui.fonts['small'].render(label, True, COLORS['text'])
text_rect = text.get_rect(center=(x, btn_y))
surface.blit(text, text_rect)

def on_touch(self, pos):
"""Gรจre les touches."""
x, y = pos
btn_y = RADIUS * 2 - 35

if btn_y - 30 < y < btn_y + 30:
modes = ['timeline', 'hardware', 'details']
for i, mode in enumerate(modes):
btn_x = CENTER[0] - 70 + i * 70
if abs(x - btn_x) < 30:
self.view_mode = mode
return

def reset(self):
"""Rรฉinitialise l'analyseur."""
self.analyzer = SPIBootAnalyzer()

๐Ÿ“‹ Phase 13 : Dรฉmo interactive

๐Ÿ“Œ 13.1 Fonctionnalitรฉs de la dรฉmo

La dรฉmo interactive HTML/React inclut toutes les fonctionnalitรฉs :

ร‰cran Emoji Fonctionnalitรฉs
๐Ÿญ Factory Boot ๐Ÿ“ Sรฉquence boot Pi Zero avec status progressifs
๐Ÿ• Clock ๐Ÿ• Horloge analogique + mini-status bar
๐Ÿ“Š Dashboard ๐Ÿ”ง Menu radial + mini-dashboards Master/Target
๐Ÿ’ป Serial Monitor ๐Ÿ’ป Logs colorรฉs + status bar + stats RX
๐Ÿ“ˆ Boot Timeline ๐Ÿš€ Arcs colorรฉs par stage + infos hardware
๐Ÿ”‹ Power Control ๐Ÿ”‹ Bouton ON/OFF + voltage/current/uptime
๐Ÿ“ Files ๐Ÿ’ฟ Explorateur arborescence USB storage

๐Ÿ“Œ 13.2 Mini-Dashboards dans la dรฉmo

Chaque รฉcran affiche les status Master/Target :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// Status Master (Pi Zero)
const master = {
power: true, // ๐Ÿ”‹ Alimentation Pi
gadget: true, // ๐Ÿ”— USB Gadget composite actif
tty: true, // ๐Ÿ“Ÿ /dev/ttyGS0 disponible
storage: true, // ๐Ÿ’ฟ Mass Storage montรฉ
wifi: true, // ๐Ÿ“ถ WiFi connectรฉ
temp: 48, // ๐ŸŒก๏ธ Tempรฉrature CPU
};

// Status Target (SoC cible)
const target = {
power: false, // ๐Ÿ”‹ Alimentation cible
usb: false, // ๐Ÿ”Œ Connexion USB
serial: false, // ๐Ÿ“ก Liaison UART
platform: 'โ˜• ESPRESSOBin', // Plateforme dรฉtectรฉe
booting: false, // ๐Ÿ”„ Boot en cours
ready: false, // โœ… Systรจme prรชt
};

๐Ÿ“Œ 13.3 Intรฉgration dans Hexo

Il y a plusieurs mรฉthodes pour intรฉgrer la dรฉmo interactive dans un article Hexo :

Mรฉthode 1 : iframe (recommandรฉ)

Placez le fichier pidebugger-demo.html dans le dossier source/demos/ de votre projet Hexo, puis utilisez un iframe :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
---
title: PiDebugger - Outil portable de debug SoC
date: 2025-01-15
---

## Dรฉmonstration interactive

<div style="display: flex; justify-content: center; margin: 30px 0;">
<iframe
src="/demos/pidebugger-demo.html"
width="450"
height="680"
frameborder="0"
style="border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
</iframe>
</div>

Mรฉthode 2 : Tag Hexo personnalisรฉ

Crรฉez un tag personnalisรฉ dans scripts/pidebugger-tag.js :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// scripts/pidebugger-tag.js
hexo.extend.tag.register('pidebugger_demo', function(args) {
return `
<div class="pidebugger-container" style="display:flex;justify-content:center;margin:30px 0;">
<iframe
src="/demos/pidebugger-demo.html"
width="450"
height="680"
frameborder="0"
loading="lazy"
style="border-radius:12px;box-shadow:0 4px 20px rgba(0,0,0,0.3);background:#0a0b0d;">
</iframe>
</div>
`;
});

Puis dans votre article :

1
{% pidebugger_demo %}

Mรฉthode 3 : Embed direct (pour thรจmes supportant le JS inline)

Si votre thรจme Hexo supporte le JavaScript inline, vous pouvez inclure directement le code dans un fichier .ejs ou utiliser le plugin hexo-filter-inline-html.

๐Ÿ“Œ 13.2 Structure des fichiers Hexo

1
2
3
4
5
6
7
8
9
10
your-hexo-blog/
โ”œโ”€โ”€ source/
โ”‚ โ”œโ”€โ”€ _posts/
โ”‚ โ”‚ โ””โ”€โ”€ pidebugger-guide.md # Article principal
โ”‚ โ””โ”€โ”€ demos/
โ”‚ โ””โ”€โ”€ pidebugger-demo.html # Dรฉmo interactive
โ”œโ”€โ”€ scripts/
โ”‚ โ””โ”€โ”€ pidebugger-tag.js # Tag personnalisรฉ (optionnel)
โ””โ”€โ”€ themes/
โ””โ”€โ”€ your-theme/

๐Ÿ“Œ 13.3 Exemple dโ€™article complet

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
---
title: PiDebugger - Swiss Army Knife pour Debug SoC ARM
date: 2025-01-15
tags:
- raspberry-pi
- embedded
- debug
- espressobin
categories:
- Hardware
- Projets
cover: /images/pidebugger-cover.jpg
---

# PiDebugger

Un outil portable basรฉ sur **Raspberry Pi Zero W** et รฉcran **HyperPixel 2.1" Round**
pour le debug, monitoring et installation de plateformes SoC ARM.

## Dรฉmonstration Live

Testez l'interface directement dans votre navigateur :

<div style="display: flex; justify-content: center; margin: 30px 0;">
<iframe
src="/demos/pidebugger-demo.html"
width="450"
height="680"
frameborder="0"
style="border-radius: 12px; box-shadow: 0 4px 20px rgba(0,0,0,0.3);">
</iframe>
</div>

> **Instructions** : Cliquez sur l'horloge pour accรฉder au dashboard.
> Sรฉlectionnez **UART** pour voir une simulation de boot EspressoBin.

## Plateformes supportรฉes

| Plateforme | SoC | RAM | Boot |
|------------|-----|-----|------|
| EspressoBin V5 | Armada 3720 | 1GB DDR3 | SPI |
| EspressoBin V7 | Armada 3720 | 1-2GB DDR3 | SPI |
| MochaBin | CN9130 | 4-8GB DDR4 | SPI |
| EspressoBin Ultra | CN9130 | 4GB DDR4 | SPI |

<!-- Suite de l'article... -->

๐Ÿ“Œ 13.4 CSS additionnel pour le thรจme

Ajoutez ces styles dans votre fichier CSS de thรจme pour un meilleur rendu :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
/* Style pour la dรฉmo PiDebugger */
.pidebugger-container {
display: flex;
justify-content: center;
margin: 30px 0;
padding: 20px;
background: linear-gradient(135deg, #1a1c22 0%, #0a0b0d 100%);
border-radius: 16px;
}

.pidebugger-container iframe {
border: none;
border-radius: 12px;
box-shadow:
0 4px 20px rgba(0, 0, 0, 0.4),
0 0 40px rgba(0, 149, 218, 0.1);
}

/* Mode sombre automatique */
@media (prefers-color-scheme: dark) {
.pidebugger-container {
background: transparent;
}
}

/* Responsive */
@media (max-width: 500px) {
.pidebugger-container iframe {
width: 100%;
max-width: 380px;
height: 600px;
}
}

๐Ÿ”— Ressources

Emoji Ressource Lien
๐Ÿ”ฒ HyperPixel 2.1โ€ shop.pimoroni.com
๐Ÿ”Œ USB Gadget API kernel.org
๐Ÿฅพ U-Boot docs u-boot.readthedocs.io
โ˜• EspressoBin wiki wiki.espressobin.net
๐Ÿซ MochaBin docs developer.solid-run.com

๐ŸŽฏ Plateformes cibles supportรฉes

Emoji Plateforme SoC RAM Boot USB Power
โ˜• EspressoBin V5 Armada 3720 1GB DDR3 SPI โœ… 5V/2A
โ˜• EspressoBin V7 Armada 3720 1-2GB DDR3 SPI โœ… 5V/2A
โšก EspressoBin Ultra CN9130 4GB DDR4 SPI โŒ 12V ext
๐Ÿซ MochaBin CN9130 4-8GB DDR4 SPI โŒ 12V ext

โœ… Checklist des fonctionnalitรฉs

๐ŸŸข Implรฉmentรฉ (v2.1)

Interface & Navigation

  • ๐Ÿ• Interface horloge circulaire avec status bar
  • ๐Ÿ“Š Dashboard avec menu radial ร  emojis (9 items)
  • ๐Ÿ“Š Mini-dashboards Master/Target centrรฉs sur tous les รฉcrans
  • ๐ŸŽฎ Gestures tactiles (swipe โ†โ†’โ†‘โ†“, pinch in/out, long press 600ms)
  • ๐Ÿ”” Notifications sonores Web Audio API (click, success, boot, notify, power)
  • ๐ŸŽจ 4 Thรจmes (๐ŸŒ™ Dark, โ˜€๏ธ Light, โฌ› OLED, ๐Ÿ“ Berry)
  • ๐Ÿ“ฑ Interface responsive (adapte ร  toutes tailles dโ€™รฉcran)

Hardware & USB

  • ๐Ÿ”— Indicateurs USB Gadget (๐Ÿ”— Gadget, ๐Ÿ“Ÿ TTY, ๐Ÿ’ฟ Storage)
  • ๐ŸŽฏ Indicateurs Target (๐Ÿ”‹ Power, ๐Ÿ”Œ USB, ๐Ÿ“ก Serial)
  • ๐Ÿ”Œ USB Gadget composite (ACM + Mass Storage 512MB)
  • ๐Ÿญ Mode Factory Boot avec sรฉquence progressive Pi Zero W
  • โญ๏ธ Bouton SKIP pour passer le boot

Multi-Target Support (v2.1)

  • โ˜• ESPRESSObin V7 (Armada 3720, 2ร—A53 @ 1.0GHz)
  • ๐Ÿš€ ESPRESSObin Ultra (Armada 3720 + PoE, 2ร—A53 @ 1.2GHz)
  • ๐Ÿซ MOCHAbin (Armada 7040, 4ร—A72 @ 1.4GHz, 10G SFP+)
  • ๐Ÿ”Œ Sheeva64 (Armada 3720, plug computer form factor)
  • ๐Ÿ”„ Sรฉlection dynamique du target dans Power Screen
  • ๐Ÿ“Š Sรฉquences de boot spรฉcifiques par target

Serial & Boot Analysis

  • ๐Ÿ’ป Moniteur sรฉrie temps rรฉel avec coloration syntaxique
  • ๐Ÿš€ Analyseur de boot multi-stages (๐Ÿ”’โ†’๐Ÿ”‘โ†’๐Ÿ›ก๏ธโ†’๐Ÿ“ฆโ†’๐Ÿฅพโ†’๐Ÿงโ†’๐Ÿ‘ค)
  • ๐Ÿ“ˆ Timeline circulaire avec emojis par stage
  • ๐Ÿ›ก๏ธ Parser ARM Trusted Firmware (BL1, BL2, BL31, PSCI, FIP)
  • ๐Ÿ”‘ Support WTMI (Cortex-M3) pour Armada 3720
  • โธ๏ธ Pause/Resume du flux sรฉrie
  • ๐Ÿงน Clear buffer sรฉrie

Power & Monitoring

  • ๐Ÿ”‹ Contrรดle alimentation USB cibles avec stats V/A/temps
  • ๐Ÿ“Š Graphiques temps rรฉel voltage/courant (30 points, 500ms)
  • โšก Affichage puissance calculรฉe (W)
  • ๐ŸŒก๏ธ Monitoring tempรฉrature CPU en temps rรฉel
  • ๐Ÿ“ถ Indicateur force signal WiFi (%)

Network & Transfers

  • ๐ŸŒ ร‰cran configuration rรฉseau (IP, Gateway, DHCP)
  • ๐Ÿ“ถ WiFi scan avec liste rรฉseaux disponibles
  • ๐Ÿ” Authentification WiFi WPA2/WPA3 avec clavier virtuel 40 touches
  • ๐Ÿ“ก Serveur TFTP intรฉgrรฉ (port 69)
  • ๐Ÿ“ค Transfert XMODEM-CRC pour recovery UART (128B packets)
  • ๐Ÿ›ก๏ธ UEFI Shell interactif (6 commandes: help, ver, map, ls, reset, memmap)

Files & Storage

  • ๐Ÿ“ Explorateur fichiers USB Storage avec icรดnes
  • ๐Ÿ’พ Export logs vers USB (/logs/)

๐ŸŸก ร€ venir

  • ๐Ÿ“ฑ Mode portrait/paysage auto-rotate
  • ๐Ÿ”„ OTA firmware update Pi Zero W
  • ๐Ÿ“Š Export timeline JSON/CSV
  • ๐Ÿ”’ Mode kiosk / verrouillage รฉcran
  • ๐Ÿ”Œ Support INA219 rรฉel (I2C) pour mesures V/A
  • ๐ŸŽฏ Multi-targets simultanรฉs
  • ๐Ÿ“ Enregistrement macros commandes
  • ๐Ÿ” Recherche dans logs sรฉrie

๐Ÿ“‹ Rรฉsumรฉ des emojis utilisรฉs

๐ŸŽจ Thรจmes disponibles

Emoji Thรจme Background Primary Description
๐ŸŒ™ Dark #181a1f #0095da Thรจme sombre par dรฉfaut
โ˜€๏ธ Light #f5f5f5 #1976d2 Thรจme clair
โฌ› OLED #000000 #00bcd4 Noir pur pour รฉcrans OLED
๐Ÿ“ Berry #1a0a10 #c51a4a Thรจme Raspberry Pi

๐ŸŒ Configuration rรฉseau

Lโ€™รฉcran Network propose 3 onglets :

Onglet Emoji Fonctions
Status ๐ŸŒ IP, Gateway, SSID, DHCP status
WiFi ๐Ÿ“ถ Scan rรฉseaux, connexion, signal %
TFTP ๐Ÿ“ก Serveur TFTP port 69, root dir

๐Ÿ“ก Serveur TFTP intรฉgrรฉ

1
2
3
4
5
# Configuration U-Boot pour boot TFTP
setenv serverip 192.168.1.42
setenv ipaddr 192.168.1.100
tftp 0x5000000 flash-image.bin
sf update 0x5000000 0x0 $filesize

โŒจ๏ธ Clavier virtuel

Le clavier virtuel permet dโ€™envoyer des commandes UART :

1
2
3
4
5
6
7
8
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ > help_ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ [1][2][3][4][5][6][7][8][9][0] โ”‚
โ”‚ [q][w][e][r][t][y][u][i][o][p] โ”‚
โ”‚ [a][s][d][f][g][h][j][k][l][โ—€๏ธ] โ”‚
โ”‚ [z][x][c][v][b][n][m][.][โˆ’][๐Ÿ“ค] โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Commandes U-Boot courantes :

  • help - Liste des commandes
  • printenv - Variables dโ€™environnement
  • boot - Dรฉmarrer le kernel
  • reset - Redรฉmarrer

๐ŸŒก๏ธ Monitoring systรจme

Indicateur Emoji Plage Alerte
CPU Temp ๐ŸŒก๏ธ 35-85ยฐC >70ยฐC orange, >80ยฐC rouge
WiFi Signal ๐Ÿ“ถ 0-100% <30% orange, <15% rouge
Voltage โšก 4.8-5.2V <4.8V orange
Current ใ€ฐ๏ธ 0-2A >1.8A orange

๐Ÿ’พ Export des logs

Les logs sont exportรฉs vers /logs/ sur le stockage USB :

1
2
3
4
5
6
/mnt/usb/PIDEBUGGER/
โ”œโ”€โ”€ logs/
โ”‚ โ”œโ”€โ”€ serial_2024-12-08.log # Logs sรฉrie
โ”‚ โ”œโ”€โ”€ boot_analysis_001.json # Timeline JSON
โ”‚ โ””โ”€โ”€ system_2024-12-08.log # Logs systรจme
โ””โ”€โ”€ ...

Format du fichier log :

1
2
3
4
[2024-12-08 14:32:15] ๐Ÿฅพ U-Boot 2024.01-armbian
[2024-12-08 14:32:15] โ˜• ESPRESSOBin V7
[2024-12-08 14:32:16] โšก Armada 3720 @ 1000 MHz
...

๐Ÿ“ค Transfert XMODEM

Lโ€™รฉcran XMODEM permet dโ€™envoyer des fichiers via le protocole XMODEM-CRC :

Caractรฉristiques

Paramรจtre Valeur
Protocol XMODEM-CRC
Packet size 128 bytes
Checksum CRC-16
Start char SOH (0x01)
End char EOT (0x04)

Sรฉquence de transfert

1
2
3
4
5
6
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ 1. Idle: Ready to send โ”‚
โ”‚ 2. Wait: Waiting for NAK from target โ”‚
โ”‚ 3. Transfer: Sending packets โ”‚
โ”‚ 4. Complete: Transfer done โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Commandes cรดtรฉ target

1
2
3
4
5
6
# U-Boot
loady 0x5000000 # Receive via YMODEM
loadx 0x5000000 # Receive via XMODEM

# Linux
rx /tmp/firmware.bin # Receive file

๐Ÿ›ก๏ธ UEFI Shell Commands

Lโ€™รฉcran UEFI Shell permet dโ€™exรฉcuter des commandes UEFI standard :

Commandes disponibles

Catรฉgorie Commandes
General help, ver
Device map, devices, drivers
File ls, cd, cp, rm, type
Boot bcfg, boot, exit
System reset, memmap, dmpstore

Exemple dโ€™utilisation

1
2
3
4
5
6
7
8
9
10
11
Shell> map
Mapping table:
FS0: Alias HD0b
BLK0: Alias USB0

Shell> ls FS0:\
Directory of FS0:\
EFI <DIR>
startup.nsh 1,234

Shell> bcfg boot dump

๐Ÿ›ก๏ธ Parser ARM Trusted Firmware

Le parser ATF dรฉtecte automatiquement les stages de boot ARM Trusted Firmware :

Stages dรฉtectรฉs

Stage Description Pattern
BL1 Boot Loader Stage 1 /BL1[:\s]/i
BL2 Boot Loader Stage 2 /BL2[:\s]/i
BL31 EL3 Runtime (Secure) /BL31[:\s]/i
BL32 Secure Payload (OP-TEE) /BL32[:\s]/i
BL33 Non-secure (U-Boot) /BL33[:\s]/i
PSCI Power State Coordination /PSCI/i
SMCCC SMC Calling Convention /SMCCC/i
FIP Firmware Image Package /FIP[:\s]/i

Affichage dans Boot Analyzer

Lโ€™onglet โ€œATFโ€ affiche les stages dรฉtectรฉs avec indicateur visuel :

  • โœ… Stage dรฉtectรฉ dans les logs
  • โ—‹ Stage non dรฉtectรฉ

๐Ÿ” Authentification WiFi WPA2/WPA3

Lโ€™รฉcran WiFi Auth permet de se connecter aux rรฉseaux sรฉcurisรฉs :

Caractรฉristiques

  • Clavier virtuel complet (a-z, 0-9, symboles)
  • Sรฉlection WPA2 / WPA3
  • Masquage mot de passe (โ€ข โ€ข โ€ข)
  • Validation min 8 caractรจres
  • Animation de connexion

Flux de connexion

1
2
3
4
5
6
7
1. Scan rรฉseaux disponibles
2. Sรฉlection rรฉseau sรฉcurisรฉ
3. ร‰cran d'authentification
4. Saisie mot de passe (clavier virtuel)
5. Sรฉlection WPA2/WPA3
6. Connexion
7. Notification succรจs

๐Ÿ“Š Graphiques temps rรฉel

Lโ€™รฉcran Power Control affiche des graphiques en temps rรฉel :

Voltage Graph

  • Plage: 4.5V - 5.5V
  • Historique: 30 points
  • Update: 500ms
  • Couleur: vert (secondary)

Current Graph

  • Plage: 0A - 2A
  • Historique: 30 points
  • Update: 500ms
  • Couleur: orange (warning)

Calcul puissance

1
Power (W) = Voltage (V) ร— Current (A)

๐ŸŽฎ Gestures tactiles

Gestures supportรฉes

Gesture Action
Swipe โ† ร‰cran prรฉcรฉdent
Swipe โ†’ ร‰cran suivant
Swipe โ†‘ Retour Dashboard
Swipe โ†“ (rรฉservรฉ)
Pinch in Notification โ€œPinch inโ€
Pinch out Notification โ€œPinch outโ€
Long press Notification โ€œLong pressโ€

Paramรจtres de dรฉtection

1
2
3
4
5
6
7
8
9
// Swipe
minDistance: 50px
maxTime: 300ms

// Pinch
scaleThreshold: 0.2 (20%)

// Long press
duration: 600ms

๐Ÿ”” Notifications sonores

Sons disponibles

Type Frรฉquence Durรฉe Usage
click 800 Hz 50ms Boutons
success 880 Hz 150ms Succรจs
boot 440 Hz 100ms Boot stages
notify 660 Hz 80ms Notifications
power 150 Hz 300ms Power toggle

Implรฉmentation Web Audio API

1
2
3
4
5
6
7
8
9
10
11
const playSound = (type) => {
const ctx = new AudioContext();
const osc = ctx.createOscillator();
const gain = ctx.createGain();
osc.connect(gain);
gain.connect(ctx.destination);
osc.frequency.setValueAtTime(freq, ctx.currentTime);
gain.gain.exponentialRampToValueAtTime(0.001, ctx.currentTime + duration);
osc.start();
osc.stop(ctx.currentTime + duration);
};

Toggle Sound

Le son peut รชtre activรฉ/dรฉsactivรฉ dans Settings :

  • ๐Ÿ”” Sound ON
  • ๐Ÿ”‡ Sound OFF

Interface principale

Emoji Usage Description
๐Ÿ”ง Titre PiDebugger
๐Ÿ• Menu Horloge
๐Ÿ’ป Menu Terminal UART
๐Ÿš€ Menu Boot Analyzer
๐Ÿ”‹ Menu Power Control
๐Ÿ“ Menu File Browser
โš™๏ธ Menu Configuration

Status indicators

Emoji Usage Description
๐ŸŸข Status ON / Actif
๐Ÿ”ด Status OFF / Inactif
๐ŸŸก Status Standby
๐Ÿ”„ Status Loading / En cours
โœ… Status OK / Succรจs
โŒ Status Erreur

Hardware Master

Emoji Usage Description
๐Ÿ“ Master Raspberry Pi
๐Ÿ”— Master USB Gadget
๐Ÿ“Ÿ Master TTY Serial
๐Ÿ’ฟ Master Mass Storage
๐Ÿ“ถ Master WiFi
๐ŸŒก๏ธ Master Tempรฉrature

Hardware Target

Emoji Usage Description
๐ŸŽฏ Target Cible SoC
๐Ÿ”‹ Target Power
๐Ÿ”Œ Target USB
๐Ÿ“ก Target Serial UART
โ˜• Target ESPRESSObin V7
๐Ÿš€ Target ESPRESSObin Ultra
๐Ÿซ Target MOCHAbin
๐Ÿ”Œ Target Sheeva64

Boot stages

Emoji Stage Description
๐Ÿ”’ BootROM ROM boot initial
๐Ÿ”‘ WTMI Marvell Trusted FW
๐Ÿ›ก๏ธ ATF ARM Trusted FW
๐Ÿ“ฆ SPL Secondary Loader
๐Ÿฅพ U-Boot Bootloader
๐Ÿง Kernel Linux
๐Ÿ‘ค Login User prompt

๐Ÿ“œ Licence

MIT License - Libre dโ€™utilisation et modification.


๐Ÿ”ง PiDebugger v2.1 - Multi-Target ARM Debugger

Made with โค๏ธ for the embedded community

๐Ÿ“ Pi Zero W | ๐Ÿ”ฒ HyperPixel 480ร—480 | โ˜• ESPRESSObin | ๐Ÿš€ Ultra | ๐Ÿซ MOCHAbin | ๐Ÿ”Œ Sheeva64