263 lines
9.2 KiB
Python
263 lines
9.2 KiB
Python
|
|
"""
|
||
|
|
Hardware Local - Physical device access (ADB/Fastboot/Serial)
|
||
|
|
Direct access to USB-connected devices on this machine.
|
||
|
|
"""
|
||
|
|
|
||
|
|
DESCRIPTION = "Physical device access (ADB/Fastboot/Serial)"
|
||
|
|
AUTHOR = "AUTARCH"
|
||
|
|
VERSION = "1.0"
|
||
|
|
CATEGORY = "hardware"
|
||
|
|
|
||
|
|
|
||
|
|
class HardwareLocal:
|
||
|
|
"""Interactive hardware access menu."""
|
||
|
|
|
||
|
|
def __init__(self):
|
||
|
|
from core.hardware import get_hardware_manager
|
||
|
|
self.mgr = get_hardware_manager()
|
||
|
|
|
||
|
|
def show_menu(self):
|
||
|
|
status = self.mgr.get_status()
|
||
|
|
print(f"\n{'='*50}")
|
||
|
|
print(" Hardware Access (Local)")
|
||
|
|
print(f"{'='*50}")
|
||
|
|
print(f" ADB: {'Available' if status['adb'] else 'Not found'}")
|
||
|
|
print(f" Fastboot: {'Available' if status['fastboot'] else 'Not found'}")
|
||
|
|
print(f" Serial: {'Available' if status['serial'] else 'Not installed'}")
|
||
|
|
print(f" ESPTool: {'Available' if status['esptool'] else 'Not installed'}")
|
||
|
|
print()
|
||
|
|
print(" 1) List ADB Devices")
|
||
|
|
print(" 2) ADB Device Info")
|
||
|
|
print(" 3) ADB Shell")
|
||
|
|
print(" 4) ADB Sideload/Install")
|
||
|
|
print(" 5) List Fastboot Devices")
|
||
|
|
print(" 6) Fastboot Device Info")
|
||
|
|
print(" 7) Fastboot Flash Partition")
|
||
|
|
print(" 8) List Serial Ports")
|
||
|
|
print(" 9) Detect ESP Chip")
|
||
|
|
print(" 10) Flash ESP32 Firmware")
|
||
|
|
print(" 0) Back")
|
||
|
|
print()
|
||
|
|
|
||
|
|
def _pick_device(self, devices, label="device"):
|
||
|
|
if not devices:
|
||
|
|
print(f" No {label}s found.")
|
||
|
|
return None
|
||
|
|
if len(devices) == 1:
|
||
|
|
return devices[0]['serial']
|
||
|
|
print(f"\n Select {label}:")
|
||
|
|
for i, d in enumerate(devices, 1):
|
||
|
|
extra = d.get('model', '') or d.get('state', '')
|
||
|
|
print(f" {i}) {d['serial']} {extra}")
|
||
|
|
try:
|
||
|
|
choice = int(input(" > ").strip())
|
||
|
|
if 1 <= choice <= len(devices):
|
||
|
|
return devices[choice - 1]['serial']
|
||
|
|
except (ValueError, EOFError):
|
||
|
|
pass
|
||
|
|
return None
|
||
|
|
|
||
|
|
def list_adb_devices(self):
|
||
|
|
devices = self.mgr.adb_devices()
|
||
|
|
if not devices:
|
||
|
|
print(" No ADB devices connected.")
|
||
|
|
return
|
||
|
|
print(f"\n {'Serial':<20} {'State':<12} {'Model':<15} {'Product'}")
|
||
|
|
print(f" {'-'*60}")
|
||
|
|
for d in devices:
|
||
|
|
print(f" {d['serial']:<20} {d['state']:<12} {d.get('model',''):<15} {d.get('product','')}")
|
||
|
|
|
||
|
|
def adb_device_info(self):
|
||
|
|
devices = self.mgr.adb_devices()
|
||
|
|
serial = self._pick_device(devices, "ADB device")
|
||
|
|
if not serial:
|
||
|
|
return
|
||
|
|
info = self.mgr.adb_device_info(serial)
|
||
|
|
print(f"\n Device Info: {serial}")
|
||
|
|
print(f" {'-'*40}")
|
||
|
|
for k, v in info.items():
|
||
|
|
print(f" {k:<20} {v}")
|
||
|
|
|
||
|
|
def adb_shell(self):
|
||
|
|
devices = self.mgr.adb_devices()
|
||
|
|
serial = self._pick_device(devices, "ADB device")
|
||
|
|
if not serial:
|
||
|
|
return
|
||
|
|
print(f" ADB Shell ({serial}) - type 'exit' to quit")
|
||
|
|
while True:
|
||
|
|
try:
|
||
|
|
cmd = input(f" {serial}$ ").strip()
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
break
|
||
|
|
if cmd.lower() in ('exit', 'quit', ''):
|
||
|
|
break
|
||
|
|
result = self.mgr.adb_shell(serial, cmd)
|
||
|
|
if result['output']:
|
||
|
|
print(result['output'])
|
||
|
|
|
||
|
|
def adb_sideload(self):
|
||
|
|
devices = self.mgr.adb_devices()
|
||
|
|
serial = self._pick_device(devices, "ADB device")
|
||
|
|
if not serial:
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
filepath = input(" File path: ").strip()
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
return
|
||
|
|
if not filepath:
|
||
|
|
return
|
||
|
|
result = self.mgr.adb_sideload(serial, filepath)
|
||
|
|
if result.get('success'):
|
||
|
|
print(f" Sideload started (op: {result['op_id']})")
|
||
|
|
# Poll progress
|
||
|
|
import time
|
||
|
|
while True:
|
||
|
|
time.sleep(1)
|
||
|
|
prog = self.mgr.get_operation_progress(result['op_id'])
|
||
|
|
print(f" [{prog.get('progress', 0)}%] {prog.get('message', '')}", end='\r')
|
||
|
|
if prog.get('status') in ('done', 'error'):
|
||
|
|
print()
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
print(f" Error: {result.get('error', 'Unknown error')}")
|
||
|
|
|
||
|
|
def list_fastboot_devices(self):
|
||
|
|
devices = self.mgr.fastboot_devices()
|
||
|
|
if not devices:
|
||
|
|
print(" No Fastboot devices connected.")
|
||
|
|
return
|
||
|
|
print(f"\n {'Serial':<25} {'State'}")
|
||
|
|
print(f" {'-'*35}")
|
||
|
|
for d in devices:
|
||
|
|
print(f" {d['serial']:<25} {d['state']}")
|
||
|
|
|
||
|
|
def fastboot_device_info(self):
|
||
|
|
devices = self.mgr.fastboot_devices()
|
||
|
|
serial = self._pick_device(devices, "Fastboot device")
|
||
|
|
if not serial:
|
||
|
|
return
|
||
|
|
info = self.mgr.fastboot_device_info(serial)
|
||
|
|
print(f"\n Fastboot Info: {serial}")
|
||
|
|
print(f" {'-'*40}")
|
||
|
|
for k, v in info.items():
|
||
|
|
print(f" {k:<20} {v}")
|
||
|
|
|
||
|
|
def fastboot_flash(self):
|
||
|
|
devices = self.mgr.fastboot_devices()
|
||
|
|
serial = self._pick_device(devices, "Fastboot device")
|
||
|
|
if not serial:
|
||
|
|
return
|
||
|
|
try:
|
||
|
|
partition = input(" Partition (boot/recovery/system/vendor): ").strip()
|
||
|
|
filepath = input(" Firmware path: ").strip()
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
return
|
||
|
|
if not partition or not filepath:
|
||
|
|
return
|
||
|
|
result = self.mgr.fastboot_flash(serial, partition, filepath)
|
||
|
|
if result.get('success'):
|
||
|
|
print(f" Flash started (op: {result['op_id']})")
|
||
|
|
import time
|
||
|
|
while True:
|
||
|
|
time.sleep(1)
|
||
|
|
prog = self.mgr.get_operation_progress(result['op_id'])
|
||
|
|
print(f" [{prog.get('progress', 0)}%] {prog.get('message', '')}", end='\r')
|
||
|
|
if prog.get('status') in ('done', 'error'):
|
||
|
|
print()
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
print(f" Error: {result.get('error', 'Unknown error')}")
|
||
|
|
|
||
|
|
def list_serial_ports(self):
|
||
|
|
ports = self.mgr.list_serial_ports()
|
||
|
|
if not ports:
|
||
|
|
print(" No serial ports found.")
|
||
|
|
return
|
||
|
|
print(f"\n {'Port':<20} {'Description':<30} {'VID:PID'}")
|
||
|
|
print(f" {'-'*60}")
|
||
|
|
for p in ports:
|
||
|
|
vid_pid = f"{p['vid']}:{p['pid']}" if p['vid'] else ''
|
||
|
|
print(f" {p['port']:<20} {p['desc']:<30} {vid_pid}")
|
||
|
|
|
||
|
|
def detect_esp(self):
|
||
|
|
ports = self.mgr.list_serial_ports()
|
||
|
|
if not ports:
|
||
|
|
print(" No serial ports found.")
|
||
|
|
return
|
||
|
|
print(" Select port:")
|
||
|
|
for i, p in enumerate(ports, 1):
|
||
|
|
print(f" {i}) {p['port']} - {p['desc']}")
|
||
|
|
try:
|
||
|
|
choice = int(input(" > ").strip())
|
||
|
|
port = ports[choice - 1]['port']
|
||
|
|
except (ValueError, IndexError, EOFError):
|
||
|
|
return
|
||
|
|
result = self.mgr.detect_esp_chip(port)
|
||
|
|
if result.get('success'):
|
||
|
|
print(f" Chip: {result['chip']}")
|
||
|
|
print(f" ID: {result.get('chip_id', 'N/A')}")
|
||
|
|
else:
|
||
|
|
print(f" Error: {result.get('error', 'Detection failed')}")
|
||
|
|
|
||
|
|
def flash_esp(self):
|
||
|
|
ports = self.mgr.list_serial_ports()
|
||
|
|
if not ports:
|
||
|
|
print(" No serial ports found.")
|
||
|
|
return
|
||
|
|
print(" Select port:")
|
||
|
|
for i, p in enumerate(ports, 1):
|
||
|
|
print(f" {i}) {p['port']} - {p['desc']}")
|
||
|
|
try:
|
||
|
|
choice = int(input(" > ").strip())
|
||
|
|
port = ports[choice - 1]['port']
|
||
|
|
firmware = input(" Firmware path: ").strip()
|
||
|
|
except (ValueError, IndexError, EOFError):
|
||
|
|
return
|
||
|
|
if not firmware:
|
||
|
|
return
|
||
|
|
result = self.mgr.flash_esp(port, firmware)
|
||
|
|
if result.get('success'):
|
||
|
|
print(f" Flash started (op: {result['op_id']})")
|
||
|
|
import time
|
||
|
|
while True:
|
||
|
|
time.sleep(1)
|
||
|
|
prog = self.mgr.get_operation_progress(result['op_id'])
|
||
|
|
print(f" [{prog.get('progress', 0)}%] {prog.get('message', '')}", end='\r')
|
||
|
|
if prog.get('status') in ('done', 'error'):
|
||
|
|
print()
|
||
|
|
break
|
||
|
|
else:
|
||
|
|
print(f" Error: {result.get('error', 'Flash failed')}")
|
||
|
|
|
||
|
|
def run_interactive(self):
|
||
|
|
while True:
|
||
|
|
self.show_menu()
|
||
|
|
try:
|
||
|
|
choice = input(" Select > ").strip()
|
||
|
|
except (EOFError, KeyboardInterrupt):
|
||
|
|
break
|
||
|
|
if choice == '0':
|
||
|
|
break
|
||
|
|
actions = {
|
||
|
|
'1': self.list_adb_devices,
|
||
|
|
'2': self.adb_device_info,
|
||
|
|
'3': self.adb_shell,
|
||
|
|
'4': self.adb_sideload,
|
||
|
|
'5': self.list_fastboot_devices,
|
||
|
|
'6': self.fastboot_device_info,
|
||
|
|
'7': self.fastboot_flash,
|
||
|
|
'8': self.list_serial_ports,
|
||
|
|
'9': self.detect_esp,
|
||
|
|
'10': self.flash_esp,
|
||
|
|
}
|
||
|
|
action = actions.get(choice)
|
||
|
|
if action:
|
||
|
|
action()
|
||
|
|
else:
|
||
|
|
print(" Invalid choice.")
|
||
|
|
|
||
|
|
|
||
|
|
def run():
|
||
|
|
hw = HardwareLocal()
|
||
|
|
hw.run_interactive()
|