33
44import argparse
55import hashlib
6+ import os
67import shutil
78import sys
89from datetime import datetime , timezone
3031 if (REPO_ROOT / "vendor" / "themefor3.5" ).is_dir ()
3132 else REPO_ROOT / "vendor" / "themefor3.5"
3233)
34+ SMARTMONITOR_VID_PID = "0483:0065"
3335
3436
3537def _yaml ():
@@ -322,6 +324,117 @@ def import_all_compiled_vendor_themes(vendor_root: Path):
322324 print (f" { name } : { error } " )
323325
324326
327+ def _check_line (ok : bool , message : str , detail : str = "" ) -> bool :
328+ marker = "OK" if ok else "WARN"
329+ print (f"[{ marker } ] { message } " )
330+ if detail and not ok :
331+ print (f" { detail } " )
332+ return ok
333+
334+
335+ def _find_hidraw_candidates () -> list [Path ]:
336+ candidates = []
337+ for path in sorted (Path ("/dev" ).glob ("hidraw*" )):
338+ if path .exists ():
339+ candidates .append (path )
340+ return candidates
341+
342+
343+ def _lsusb_contains_smartmonitor () -> bool | None :
344+ try :
345+ import subprocess
346+
347+ result = subprocess .run (
348+ ["lsusb" ],
349+ text = True ,
350+ capture_output = True ,
351+ timeout = 3 ,
352+ check = False ,
353+ )
354+ except Exception :
355+ return None
356+ return SMARTMONITOR_VID_PID .lower () in result .stdout .lower ()
357+
358+
359+ def run_doctor ():
360+ print ("SmartMonitor HIDdev doctor" )
361+ print ("==========================" )
362+ all_ok = True
363+
364+ all_ok &= _check_line (sys .version_info >= (3 , 10 ), f"Python version: { sys .version .split ()[0 ]} " )
365+ all_ok &= _check_line (CONFIG_PATH .is_file (), f"config.yaml present: { CONFIG_PATH } " )
366+ all_ok &= _check_line ((REPO_ROOT / "requirements.txt" ).is_file (), "requirements.txt present" )
367+
368+ try :
369+ import PIL # noqa: F401
370+ import psutil # noqa: F401
371+ import ruamel .yaml # noqa: F401
372+ deps_ok = True
373+ except ModuleNotFoundError as exc :
374+ deps_ok = False
375+ all_ok = False
376+ _check_line (False , "Python dependencies installed" , f"Missing module: { exc .name } " )
377+ else :
378+ _check_line (True , "Python dependencies installed" )
379+
380+ config_ok = False
381+ active_theme = None
382+ if CONFIG_PATH .is_file () and deps_ok :
383+ try :
384+ config_data = read_config ()
385+ display = config_data .get ("display" , {})
386+ revision = display .get ("REVISION" )
387+ active_theme = Path (display .get ("SMARTMONITOR_HID_THEME_FILE" , "" ) or "" )
388+ config_ok = revision == "A_HID"
389+ all_ok &= _check_line (config_ok , f"display.REVISION = { revision !r} " , "Expected 'A_HID' for this fork" )
390+ except Exception as exc :
391+ all_ok = False
392+ _check_line (False , "config.yaml can be parsed" , str (exc ))
393+
394+ themes = list_installed_themes ()
395+ all_ok &= _check_line (bool (themes ), f"installed SmartMonitor themes: { len (themes )} " )
396+
397+ if active_theme :
398+ if not active_theme .is_absolute ():
399+ active_theme = REPO_ROOT / active_theme
400+ all_ok &= _check_line (active_theme .is_file (), f"active theme file exists: { active_theme } " )
401+
402+ lsusb_state = _lsusb_contains_smartmonitor ()
403+ if lsusb_state is None :
404+ _check_line (False , "lsusb check skipped" , "Install usbutils if you want VID:PID detection" )
405+ else :
406+ all_ok &= _check_line (
407+ lsusb_state ,
408+ f"USB device { SMARTMONITOR_VID_PID } visible" if lsusb_state else f"USB device { SMARTMONITOR_VID_PID } not visible" ,
409+ "Connect/replug the SmartMonitor or check USB permissions." ,
410+ )
411+
412+ hidraw_candidates = _find_hidraw_candidates ()
413+ all_ok &= _check_line (bool (hidraw_candidates ), f"hidraw devices visible: { ', ' .join (str (p ) for p in hidraw_candidates ) or 'none' } " )
414+ if hidraw_candidates :
415+ readable = [path for path in hidraw_candidates if os .access (path , os .R_OK | os .W_OK )]
416+ if readable :
417+ _check_line (True , f"hidraw read/write permission: { ', ' .join (str (p ) for p in readable )} " )
418+ else :
419+ all_ok = False
420+ _check_line (
421+ False ,
422+ "hidraw read/write permission" ,
423+ "Install the udev rule from tools/99-smartmonitor-hiddev.rules or run with adjusted permissions." ,
424+ )
425+
426+ print ()
427+ if all_ok :
428+ print ("Doctor result: ready" )
429+ else :
430+ print ("Doctor result: attention needed" )
431+ print ("Recommended next steps:" )
432+ print (" 1. Run ./install.sh" )
433+ print (" 2. Replug the SmartMonitor" )
434+ print (" 3. Run python3 tools/smartmonitor-theme-manager.py doctor" )
435+ return 0 if all_ok else 1
436+
437+
325438def main ():
326439 parser = argparse .ArgumentParser (description = "Manage vendor img.dat themes for the HID SmartMonitor" )
327440 subparsers = parser .add_subparsers (dest = "command" , required = True )
@@ -332,6 +445,9 @@ def main():
332445 parser_current = subparsers .add_parser ("current" , help = "Print active SmartMonitor theme file from config.yaml" )
333446 parser_current .set_defaults (func = lambda args : print_current ())
334447
448+ parser_doctor = subparsers .add_parser ("doctor" , help = "Check installation, config, HID device, and theme setup" )
449+ parser_doctor .set_defaults (func = lambda args : sys .exit (run_doctor ()))
450+
335451 parser_vendor = subparsers .add_parser ("vendor-list" , help = "List vendor theme directories found in unpacked app" )
336452 parser_vendor .add_argument ("--vendor-root" , type = Path , default = DEFAULT_VENDOR_ROOT ,
337453 help = f"Vendor theme root (default: { DEFAULT_VENDOR_ROOT } )" )
0 commit comments