feat(hal): read display rotation from hardware_config.json
Load the full hardware config up front rather than only the GPIO section, so display settings can come from the same file. Adds a rotate parameter that falls back to display.rotate in the config when not passed explicitly, and logs the resolved value. Also bumps the dreader-hal submodule to 64c9177 (use partial draw) and adds a direct IT8951 example that bypasses the HAL, for isolating display problems to the driver layer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+23
-12
@@ -140,6 +140,7 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
height: int = 1404,
|
height: int = 1404,
|
||||||
vcom: float = -2.0,
|
vcom: float = -2.0,
|
||||||
spi_hz: int = 24_000_000,
|
spi_hz: int = 24_000_000,
|
||||||
|
rotate: Optional[str] = None,
|
||||||
virtual_display: bool = False,
|
virtual_display: bool = False,
|
||||||
auto_sleep_display: bool = True,
|
auto_sleep_display: bool = True,
|
||||||
enable_orientation: bool = True,
|
enable_orientation: bool = True,
|
||||||
@@ -170,8 +171,29 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
self.width = width
|
self.width = width
|
||||||
self.height = height
|
self.height = height
|
||||||
|
|
||||||
|
# Load config from file to get display settings
|
||||||
|
full_config = None
|
||||||
|
if config_file or gpio_config is None:
|
||||||
|
config_path = Path(config_file or "hardware_config.json")
|
||||||
|
if config_path.exists():
|
||||||
|
try:
|
||||||
|
with open(config_path, 'r') as f:
|
||||||
|
full_config = json.load(f)
|
||||||
|
logger.info(f"Loaded hardware config from {config_path}")
|
||||||
|
|
||||||
|
# Override display parameters from config if not explicitly provided
|
||||||
|
display_config = full_config.get('display', {})
|
||||||
|
if rotate is None and 'rotate' in display_config:
|
||||||
|
rotate = display_config['rotate']
|
||||||
|
logger.info(f" Using rotate from config: {rotate}")
|
||||||
|
|
||||||
|
gpio_config = full_config
|
||||||
|
except Exception as e:
|
||||||
|
logger.warning(f"Could not load hardware config from {config_path}: {e}")
|
||||||
|
|
||||||
logger.info(f"Initializing HardwareDisplayHAL: {width}x{height}")
|
logger.info(f"Initializing HardwareDisplayHAL: {width}x{height}")
|
||||||
logger.info(f" VCOM: {vcom}V")
|
logger.info(f" VCOM: {vcom}V")
|
||||||
|
logger.info(f" Rotate: {rotate}")
|
||||||
logger.info(f" Virtual display: {virtual_display}")
|
logger.info(f" Virtual display: {virtual_display}")
|
||||||
logger.info(f" Orientation: {enable_orientation}")
|
logger.info(f" Orientation: {enable_orientation}")
|
||||||
logger.info(f" RTC: {enable_rtc}")
|
logger.info(f" RTC: {enable_rtc}")
|
||||||
@@ -183,6 +205,7 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
height=height,
|
height=height,
|
||||||
vcom=vcom,
|
vcom=vcom,
|
||||||
spi_hz=spi_hz,
|
spi_hz=spi_hz,
|
||||||
|
rotate=rotate,
|
||||||
virtual_display=virtual_display,
|
virtual_display=virtual_display,
|
||||||
auto_sleep_display=auto_sleep_display,
|
auto_sleep_display=auto_sleep_display,
|
||||||
enable_orientation=enable_orientation,
|
enable_orientation=enable_orientation,
|
||||||
@@ -195,18 +218,6 @@ class HardwareDisplayHAL(DisplayHAL):
|
|||||||
# GPIO button handler (optional)
|
# GPIO button handler (optional)
|
||||||
self.gpio_handler: Optional[GPIOButtonHandler] = None
|
self.gpio_handler: Optional[GPIOButtonHandler] = None
|
||||||
|
|
||||||
# Load GPIO config from file if specified
|
|
||||||
if config_file or gpio_config is None:
|
|
||||||
config_path = Path(config_file or "hardware_config.json")
|
|
||||||
if config_path.exists():
|
|
||||||
try:
|
|
||||||
with open(config_path, 'r') as f:
|
|
||||||
full_config = json.load(f)
|
|
||||||
gpio_config = full_config
|
|
||||||
logger.info(f"Loaded hardware config from {config_path}")
|
|
||||||
except Exception as e:
|
|
||||||
logger.warning(f"Could not load hardware config from {config_path}: {e}")
|
|
||||||
|
|
||||||
# Initialize GPIO buttons if configured
|
# Initialize GPIO buttons if configured
|
||||||
if gpio_config and GPIO_BUTTONS_AVAILABLE:
|
if gpio_config and GPIO_BUTTONS_AVAILABLE:
|
||||||
try:
|
try:
|
||||||
|
|||||||
Executable
+110
@@ -0,0 +1,110 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Direct IT8951 test - bypasses the HAL completely.
|
||||||
|
Uses IT8951 library directly like the working example.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from IT8951 import constants
|
||||||
|
from IT8951.display import AutoEPDDisplay
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Test display with direct IT8951 access."""
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("Direct IT8951 Test - Half Black, Half White")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
print("This test bypasses the HAL and uses IT8951 directly.")
|
||||||
|
print("Matches the working code you provided.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Display size
|
||||||
|
width, height = 1872, 1404
|
||||||
|
shape = (height, width) # IT8951 uses (height, width) format
|
||||||
|
|
||||||
|
print(f"Display shape: {shape} (height, width)")
|
||||||
|
print(f"Dimensions: {width}x{height}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Create test image
|
||||||
|
print("Creating test image...")
|
||||||
|
print(" Left half: BLACK (0)")
|
||||||
|
print(" Right half: WHITE (255)")
|
||||||
|
|
||||||
|
# Create grayscale image
|
||||||
|
screen = Image.new('L', shape, color=255) # White background
|
||||||
|
|
||||||
|
# Draw left half black
|
||||||
|
pixels = screen.load()
|
||||||
|
for y in range(height):
|
||||||
|
for x in range(width // 2):
|
||||||
|
pixels[x, y] = 0 # Black
|
||||||
|
|
||||||
|
print(f"✓ Image created: {screen.size} {screen.mode}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Save for reference
|
||||||
|
output_file = "direct_test.png"
|
||||||
|
screen.save(output_file)
|
||||||
|
print(f"✓ Saved to: {output_file}")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Initialize display
|
||||||
|
print("Initializing IT8951 display...")
|
||||||
|
print(" VCOM: -1.7V")
|
||||||
|
print(" Rotation: CW (clockwise)")
|
||||||
|
print(" SPI: 24MHz")
|
||||||
|
|
||||||
|
display = AutoEPDDisplay(
|
||||||
|
vcom=-1.7,
|
||||||
|
rotate="CW",
|
||||||
|
spi_hz=24000000,
|
||||||
|
device=0,
|
||||||
|
bus=0
|
||||||
|
)
|
||||||
|
|
||||||
|
print("✓ Display initialized")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Display the image
|
||||||
|
print("Displaying image...")
|
||||||
|
print(" Using frame_buf.paste() + draw_full(GC16)")
|
||||||
|
|
||||||
|
display.frame_buf.paste(screen, (0, 0))
|
||||||
|
display.draw_full(constants.DisplayModes.GC16)
|
||||||
|
|
||||||
|
print("✓ Image displayed!")
|
||||||
|
print()
|
||||||
|
|
||||||
|
print("=" * 60)
|
||||||
|
print("CHECK YOUR SCREEN:")
|
||||||
|
print("=" * 60)
|
||||||
|
print()
|
||||||
|
print("You should see:")
|
||||||
|
print(" • LEFT HALF: BLACK")
|
||||||
|
print(" • RIGHT HALF: WHITE")
|
||||||
|
print()
|
||||||
|
print("If this works, the display hardware is fine!")
|
||||||
|
print("If this doesn't work, there's a hardware/wiring issue.")
|
||||||
|
print()
|
||||||
|
|
||||||
|
# Sleep display
|
||||||
|
input("Press Enter to put display to sleep and exit...")
|
||||||
|
|
||||||
|
epd = display.epd
|
||||||
|
epd.sleep()
|
||||||
|
|
||||||
|
print("Display put to sleep. Done!")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
try:
|
||||||
|
main()
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("\n\nTest interrupted")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"\nERROR: {e}")
|
||||||
|
import traceback
|
||||||
|
traceback.print_exc()
|
||||||
Vendored
+1
-1
Submodule external/dreader-hal updated: bfa51c1439...64c9177604
Reference in New Issue
Block a user