first commit

This commit is contained in:
2025-11-10 18:06:11 +01:00
commit 3817b86ad1
38 changed files with 6466 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
#!/usr/bin/env python3
"""
Battery Monitor Example.
Demonstrates power monitoring with the INA219:
- Read voltage, current, and power
- Estimate battery percentage
- Calculate time remaining
- Detect charging status
This example requires actual INA219 hardware connected via I2C.
"""
import asyncio
import sys
import os
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
from dreader_hal import EReaderDisplayHAL
async def main():
"""Main example function."""
# Create HAL with power monitoring enabled
hal = EReaderDisplayHAL(
width=800,
height=1200,
virtual_display=True, # Virtual display for testing
enable_orientation=False,
enable_rtc=False,
enable_power_monitor=True, # Enable power monitoring
shunt_ohms=0.1, # Shunt resistor value
battery_capacity_mah=3000, # Battery capacity
)
print("Initializing HAL with power monitoring...")
try:
await hal.initialize()
print("HAL initialized!")
except RuntimeError as e:
print(f"Error: {e}")
print("\nNote: This example requires INA219 hardware on I2C bus.")
print("If testing without hardware, power monitor will be disabled.")
return
# Monitor battery stats
print("\nMonitoring battery stats (Ctrl+C to exit)...\n")
try:
while True:
# Get power statistics
stats = await hal.get_power_stats()
# Display stats
print(f"\r"
f"Voltage: {stats.voltage:.2f}V | "
f"Current: {stats.current:.1f}mA | "
f"Power: {stats.power:.1f}mW | "
f"Battery: {stats.battery_percent:.0f}% | "
f"Charging: {'Yes' if stats.is_charging else 'No'} | "
f"Time remaining: {stats.time_remaining or 'N/A'} min",
end="", flush=True)
# Check for low battery
if await hal.is_low_battery(threshold=20.0):
print("\n⚠️ LOW BATTERY WARNING!")
# Wait 1 second
await asyncio.sleep(1.0)
except KeyboardInterrupt:
print("\n\nStopped by user")
finally:
# Cleanup
print("Cleaning up...")
await hal.cleanup()
print("Done!")
if __name__ == "__main__":
asyncio.run(main())
+355
View File
@@ -0,0 +1,355 @@
#!/usr/bin/env python3
"""
Touchscreen Calibration Utility
This script guides the user through touchscreen calibration by:
1. Displaying calibration targets (circles) at known positions
2. Waiting for user to touch each target
3. Recording touch coordinates
4. Computing transformation matrix
5. Saving calibration data
Usage:
python3 calibrate_touch.py [--points N] [--output PATH]
Options:
--points N Number of calibration points (5 or 9, default 9)
--output PATH Calibration file path (default ~/.config/dreader/touch_calibration.json)
--virtual Use virtual display for testing
"""
import asyncio
import argparse
import sys
import os
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
from dreader_hal.display.it8951 import IT8951DisplayDriver
from dreader_hal.touch.ft5xx6 import FT5xx6TouchDriver
from dreader_hal.calibration import TouchCalibration
from dreader_hal.types import GestureType, RefreshMode
class CalibrationUI:
"""
Calibration user interface.
Displays calibration targets and instructions on the e-ink display.
"""
def __init__(self, width: int, height: int, target_radius: int = 10):
self.width = width
self.height = height
self.target_radius = target_radius
def draw_target(self, image: Image.Image, x: int, y: int,
filled: bool = False) -> None:
"""
Draw a calibration target circle.
Args:
image: PIL Image to draw on
x: Target X position
y: Target Y position
filled: Whether target has been touched
"""
draw = ImageDraw.Draw(image)
# Draw concentric circles
r = self.target_radius
# Outer circle
draw.ellipse([x - r, y - r, x + r, y + r],
outline=0 if not filled else 128,
width=2)
# Middle circle
draw.ellipse([x - r//2, y - r//2, x + r//2, y + r//2],
outline=0 if not filled else 128,
width=2)
# Center dot
if filled:
draw.ellipse([x - 3, y - 3, x + 3, y + 3],
fill=128, outline=128)
else:
draw.ellipse([x - 3, y - 3, x + 3, y + 3],
fill=0, outline=0)
def create_calibration_screen(self, targets: list, current_idx: int,
completed: list) -> Image.Image:
"""
Create calibration screen with targets and instructions.
Args:
targets: List of (x, y) target positions
current_idx: Index of current target
completed: List of completed target indices
Returns:
PIL Image
"""
# Create white background
image = Image.new('L', (self.width, self.height), color=255)
draw = ImageDraw.Draw(image)
# Draw title
title = "Touchscreen Calibration"
try:
# Try to use a larger font if available
font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 32)
font_text = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 24)
except:
# Fall back to default font
font_title = ImageFont.load_default()
font_text = ImageFont.load_default()
# Draw title centered
bbox = draw.textbbox((0, 0), title, font=font_title)
text_width = bbox[2] - bbox[0]
draw.text(((self.width - text_width) // 2, 30), title,
fill=0, font=font_title)
# Draw instructions
if current_idx < len(targets):
instruction = f"Touch target {current_idx + 1} of {len(targets)}"
else:
instruction = "Calibration complete!"
bbox = draw.textbbox((0, 0), instruction, font=font_text)
text_width = bbox[2] - bbox[0]
draw.text(((self.width - text_width) // 2, 80), instruction,
fill=0, font=font_text)
# Draw all targets
for idx, (tx, ty) in enumerate(targets):
if idx in completed:
# Completed target - filled
self.draw_target(image, tx, ty, filled=True)
elif idx == current_idx:
# Current target - highlighted
self.draw_target(image, tx, ty, filled=False)
# Draw arrow or indicator
draw.text((tx + self.target_radius + 10, ty - 10),
"", fill=0, font=font_text)
else:
# Future target - dimmed
self.draw_target(image, tx, ty, filled=False)
# Draw progress bar at bottom
progress_width = int((len(completed) / len(targets)) * (self.width - 100))
draw.rectangle([50, self.height - 50, 50 + progress_width, self.height - 30],
fill=0, outline=0)
draw.rectangle([50, self.height - 50, self.width - 50, self.height - 30],
outline=0, width=2)
return image
def create_results_screen(self, calibration: TouchCalibration) -> Image.Image:
"""
Create results screen showing calibration quality.
Args:
calibration: TouchCalibration instance
Returns:
PIL Image
"""
image = Image.new('L', (self.width, self.height), color=255)
draw = ImageDraw.Draw(image)
try:
font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 32)
font_text = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 24)
except:
font_title = ImageFont.load_default()
font_text = ImageFont.load_default()
# Title
title = "Calibration Complete!"
bbox = draw.textbbox((0, 0), title, font=font_title)
text_width = bbox[2] - bbox[0]
draw.text(((self.width - text_width) // 2, 50), title,
fill=0, font=font_title)
# Quality
quality = calibration.get_calibration_quality()
rms_error = calibration.calibration_data.rms_error
quality_text = f"Quality: {quality}"
bbox = draw.textbbox((0, 0), quality_text, font=font_text)
text_width = bbox[2] - bbox[0]
draw.text(((self.width - text_width) // 2, 120), quality_text,
fill=0, font=font_text)
error_text = f"RMS Error: {rms_error:.2f} pixels"
bbox = draw.textbbox((0, 0), error_text, font=font_text)
text_width = bbox[2] - bbox[0]
draw.text(((self.width - text_width) // 2, 160), error_text,
fill=0, font=font_text)
# Instructions
info = "Calibration data has been saved."
bbox = draw.textbbox((0, 0), info, font=font_text)
text_width = bbox[2] - bbox[0]
draw.text(((self.width - text_width) // 2, 220), info,
fill=0, font=font_text)
return image
async def run_calibration(width: int, height: int, num_points: int,
output_path: str, virtual: bool = False):
"""
Run the calibration process.
Args:
width: Display width
height: Display height
num_points: Number of calibration points
output_path: Path to save calibration file
virtual: Use virtual display
"""
print(f"Starting touchscreen calibration...")
print(f"Display: {width}x{height}")
print(f"Calibration points: {num_points}")
print(f"Output: {output_path}")
# Initialize display
display = IT8951DisplayDriver(
width=width,
height=height,
virtual=virtual,
)
await display.initialize()
print("Display initialized")
# Initialize touch
touch = FT5xx6TouchDriver(
width=width,
height=height,
)
await touch.initialize()
print("Touch controller initialized")
# Create calibration instance
calibration = TouchCalibration(width, height, num_points)
ui = CalibrationUI(width, height, target_radius=20)
# Generate target positions
targets = calibration.generate_target_positions(margin=100, target_radius=20)
print(f"Generated {len(targets)} calibration targets")
completed = []
current_idx = 0
try:
# Calibration loop
while current_idx < len(targets):
# Draw calibration screen
screen = ui.create_calibration_screen(targets, current_idx, completed)
await display.show_image(screen, mode=RefreshMode.QUALITY)
target_x, target_y = targets[current_idx]
print(f"\nTarget {current_idx + 1}/{len(targets)}: Touch circle at ({target_x}, {target_y})")
# Wait for touch
touch_received = False
while not touch_received:
event = await touch.get_touch_event()
if event and event.gesture == GestureType.TAP:
# Record calibration point
calibration.add_calibration_point(
display_x=target_x,
display_y=target_y,
touch_x=event.x,
touch_y=event.y,
)
print(f" Recorded touch at ({event.x}, {event.y})")
# Mark as completed
completed.append(current_idx)
current_idx += 1
touch_received = True
# Small delay to prevent double-touches
await asyncio.sleep(0.5)
# Compute calibration matrix
print("\nComputing calibration matrix...")
success = calibration.compute_calibration()
if not success:
print("ERROR: Failed to compute calibration matrix")
return
print(f"Calibration computed successfully!")
print(f" Quality: {calibration.get_calibration_quality()}")
print(f" RMS Error: {calibration.calibration_data.rms_error:.2f} pixels")
print(f" Matrix: {calibration.calibration_data.matrix}")
# Save calibration
calibration.save(output_path)
print(f"\nCalibration saved to: {output_path}")
# Show results screen
results_screen = ui.create_results_screen(calibration)
await display.show_image(results_screen, mode=RefreshMode.QUALITY)
# Wait a bit so user can see results
await asyncio.sleep(3)
finally:
# Cleanup
await display.cleanup()
await touch.cleanup()
print("\nCalibration complete!")
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Touchscreen calibration utility for DReader HAL"
)
parser.add_argument(
'--points', type=int, default=9, choices=[5, 9],
help='Number of calibration points (default: 9)'
)
parser.add_argument(
'--output', type=str,
default=str(Path.home() / '.config' / 'dreader' / 'touch_calibration.json'),
help='Output calibration file path'
)
parser.add_argument(
'--width', type=int, default=800,
help='Display width in pixels (default: 800)'
)
parser.add_argument(
'--height', type=int, default=1200,
help='Display height in pixels (default: 1200)'
)
parser.add_argument(
'--virtual', action='store_true',
help='Use virtual display for testing'
)
args = parser.parse_args()
# Run calibration
asyncio.run(run_calibration(
width=args.width,
height=args.height,
num_points=args.points,
output_path=args.output,
virtual=args.virtual,
))
if __name__ == '__main__':
main()
+240
View File
@@ -0,0 +1,240 @@
#!/usr/bin/env python3
"""
RTC (Real-Time Clock) Demo.
Demonstrates PCF8523 RTC functionality:
- Read current date/time
- Set RTC time
- Sync with system time
- Set and monitor alarms
- Display continuous time updates
This example requires actual PCF8523 RTC hardware connected via I2C.
"""
import asyncio
import sys
import os
import time
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
from dreader_hal import EReaderDisplayHAL
def format_time(t: time.struct_time) -> str:
"""Format struct_time as readable string."""
return time.strftime("%Y-%m-%d %H:%M:%S %A", t)
async def display_current_time(hal: EReaderDisplayHAL):
"""Display current RTC time continuously."""
print("\nDisplaying current time (Ctrl+C to stop)...\n")
try:
while True:
current = await hal.get_datetime()
if current:
print(f"\rCurrent time: {format_time(current)}", end="", flush=True)
else:
print("\rRTC not available", end="", flush=True)
await asyncio.sleep(1.0)
except KeyboardInterrupt:
print("\n")
async def set_time_demo(hal: EReaderDisplayHAL):
"""Demonstrate setting RTC time."""
print("\n=== Setting RTC Time ===")
# Show current time
current = await hal.get_datetime()
if current:
print(f"Current RTC time: {format_time(current)}")
# Set to system time
print("\nSetting RTC to current system time...")
system_time = time.localtime()
await hal.set_datetime(system_time)
print(f"System time: {format_time(system_time)}")
# Verify
await asyncio.sleep(0.5)
new_time = await hal.get_datetime()
if new_time:
print(f"New RTC time: {format_time(new_time)}")
print("✓ Time updated successfully!")
async def alarm_demo(hal: EReaderDisplayHAL):
"""Demonstrate RTC alarm functionality."""
print("\n=== RTC Alarm Demo ===")
# Get current time
current = await hal.get_datetime()
if not current:
print("Error: RTC not available")
return
print(f"Current time: {format_time(current)}")
# Set alarm for 2 minutes from now
alarm_minute = (current.tm_min + 2) % 60
alarm_hour = current.tm_hour
if alarm_minute < current.tm_min:
alarm_hour = (alarm_hour + 1) % 24
print(f"\nSetting alarm for {alarm_hour:02d}:{alarm_minute:02d}...")
await hal.set_alarm(hour=alarm_hour, minute=alarm_minute)
print("✓ Alarm set!")
print("\nNote: Alarm functionality requires checking alarm status")
print("via the low-level RTC driver (hal.rtc.check_alarm())")
async def interactive_menu(hal: EReaderDisplayHAL):
"""Interactive menu for RTC operations."""
while True:
print("\n" + "="*50)
print("RTC Demo Menu")
print("="*50)
print("1. Display current time")
print("2. Set time to system time")
print("3. Set custom time")
print("4. Set alarm")
print("5. Display time continuously")
print("6. Exit")
print("="*50)
choice = input("\nEnter choice (1-6): ").strip()
if choice == "1":
current = await hal.get_datetime()
if current:
print(f"\nCurrent RTC time: {format_time(current)}")
else:
print("\nRTC not available")
elif choice == "2":
await set_time_demo(hal)
elif choice == "3":
print("\nEnter date/time (or press Enter to cancel):")
year = input("Year (YYYY): ").strip()
if not year:
continue
try:
year = int(year)
month = int(input("Month (1-12): "))
day = int(input("Day (1-31): "))
hour = int(input("Hour (0-23): "))
minute = int(input("Minute (0-59): "))
second = int(input("Second (0-59): "))
# Create struct_time (weekday and yearday are calculated)
custom_time = time.struct_time((
year, month, day, hour, minute, second, 0, 0, -1
))
print(f"\nSetting RTC to: {format_time(custom_time)}")
await hal.set_datetime(custom_time)
# Verify
await asyncio.sleep(0.5)
new_time = await hal.get_datetime()
if new_time:
print(f"Verified: {format_time(new_time)}")
print("✓ Time updated successfully!")
except (ValueError, OverflowError) as e:
print(f"\nError: Invalid date/time - {e}")
elif choice == "4":
await alarm_demo(hal)
elif choice == "5":
await display_current_time(hal)
elif choice == "6":
print("\nExiting...")
break
else:
print("\nInvalid choice. Please enter 1-6.")
async def main():
"""Main example function."""
# Create HAL with RTC enabled
hal = EReaderDisplayHAL(
width=800,
height=1200,
virtual_display=True, # Virtual display for testing
enable_orientation=False,
enable_rtc=True, # Enable RTC
enable_power_monitor=False,
)
print("="*50)
print("DReader HAL - RTC Demo")
print("="*50)
print("\nInitializing HAL with RTC...")
try:
await hal.initialize()
print("✓ HAL initialized!")
if not hal.rtc:
print("\n⚠️ Warning: RTC not available!")
print("\nThis example requires PCF8523 RTC hardware on I2C bus.")
print("RTC should be at address 0x68 on I2C bus 1.")
print("\nCheck connections:")
print(" - VCC → 3.3V (Pin 1)")
print(" - GND → GND (Pin 6)")
print(" - SDA → GPIO2 (Pin 3)")
print(" - SCL → GPIO3 (Pin 5)")
print("\nVerify with: i2cdetect -y 1")
return
print("✓ RTC initialized!")
# Show initial time
current = await hal.get_datetime()
if current:
print(f"\nCurrent RTC time: {format_time(current)}")
system_time = time.localtime()
print(f"System time: {format_time(system_time)}")
# Check if times differ
time_diff = abs(time.mktime(current) - time.mktime(system_time))
if time_diff > 2: # More than 2 seconds difference
print(f"\n⚠️ RTC differs from system time by {time_diff:.0f} seconds")
# Run interactive menu
await interactive_menu(hal)
except RuntimeError as e:
print(f"\n❌ Error: {e}")
print("\nNote: This example requires PCF8523 RTC hardware on I2C bus.")
return
except KeyboardInterrupt:
print("\n\nInterrupted by user")
finally:
# Cleanup
print("\nCleaning up...")
await hal.cleanup()
print("Done!")
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nExiting...")
+118
View File
@@ -0,0 +1,118 @@
#!/usr/bin/env python3
"""
Simple Display Example.
Demonstrates basic usage of the EReaderDisplayHAL:
- Initialize the HAL
- Display an image
- Handle touch events
- Cleanup
This example uses a virtual display (Tkinter) for testing without hardware.
To use real hardware, set virtual_display=False.
"""
import asyncio
import sys
import os
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
from PIL import Image, ImageDraw, ImageFont
from dreader_hal import EReaderDisplayHAL, GestureType
async def main():
"""Main example function."""
# Create HAL with virtual display for testing
# For real hardware, set virtual_display=False
hal = EReaderDisplayHAL(
width=800,
height=1200,
virtual_display=True, # Use Tkinter window for testing
enable_orientation=False, # Disable orientation (no hardware)
enable_rtc=False, # Disable RTC (no hardware)
enable_power_monitor=False, # Disable power monitor (no hardware)
)
print("Initializing HAL...")
await hal.initialize()
print("HAL initialized!")
# Create a test image
image = Image.new('RGB', (800, 1200), color=(255, 255, 255))
draw = ImageDraw.Draw(image)
# Draw some text
try:
font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 48)
except:
font = ImageFont.load_default()
draw.text((50, 100), "DReader HAL Demo", fill=(0, 0, 0), font=font)
draw.text((50, 200), "Touch anywhere to test", fill=(0, 0, 0), font=font)
# Draw gesture instructions
try:
small_font = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 32)
except:
small_font = font
draw.text((50, 400), "Gestures:", fill=(0, 0, 0), font=small_font)
draw.text((50, 450), "• Tap: Show coordinates", fill=(0, 0, 0), font=small_font)
draw.text((50, 500), "• Swipe Left: Next page", fill=(0, 0, 0), font=small_font)
draw.text((50, 550), "• Swipe Right: Previous page", fill=(0, 0, 0), font=small_font)
draw.text((50, 600), "• Swipe Up: Open menu", fill=(0, 0, 0), font=small_font)
draw.text((50, 650), "• Long Press: Exit", fill=(128, 128, 128), font=small_font)
print("Displaying image...")
await hal.show_image(image)
print("Image displayed!")
# Event loop - handle touch events
print("\nWaiting for touch events...")
print("(Long press to exit)\n")
running = True
while running:
event = await hal.get_touch_event()
if event:
print(f"Touch event: {event.gesture.value} at ({event.x}, {event.y})")
# Handle different gestures
if event.gesture == GestureType.TAP:
print(f" → Tap detected at ({event.x}, {event.y})")
elif event.gesture == GestureType.SWIPE_LEFT:
print(" → Swipe left - next page")
elif event.gesture == GestureType.SWIPE_RIGHT:
print(" → Swipe right - previous page")
elif event.gesture == GestureType.SWIPE_UP:
print(" → Swipe up - open menu")
elif event.gesture == GestureType.SWIPE_DOWN:
print(" → Swipe down - open settings")
elif event.gesture == GestureType.LONG_PRESS:
print(" → Long press - exiting...")
running = False
# Small delay to avoid busy loop
await asyncio.sleep(0.01)
# Cleanup
print("\nCleaning up...")
await hal.cleanup()
print("Done!")
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
print("\nInterrupted by user")
+252
View File
@@ -0,0 +1,252 @@
#!/usr/bin/env python3
"""
Test Touchscreen Calibration
This script demonstrates and tests the touchscreen calibration.
It displays a simple interface where you can tap anywhere on the screen,
and it will show both the raw touch coordinates and calibrated coordinates.
Usage:
python3 test_calibration.py [--virtual]
"""
import asyncio
import argparse
import sys
import os
from PIL import Image, ImageDraw, ImageFont
# Add src to path
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '../src'))
from dreader_hal.display.it8951 import IT8951DisplayDriver
from dreader_hal.touch.ft5xx6 import FT5xx6TouchDriver
from dreader_hal.types import GestureType, RefreshMode
class CalibrationTester:
"""Simple UI for testing calibration."""
def __init__(self, width: int, height: int):
self.width = width
self.height = height
self.touch_history = [] # List of (x, y) tuples
self.max_history = 10
def create_screen(self, calibrated: bool, last_touch: tuple = None,
raw_coords: tuple = None) -> Image.Image:
"""
Create test screen showing calibration status and touch points.
Args:
calibrated: Whether calibration is loaded
last_touch: Last calibrated touch coordinates (x, y)
raw_coords: Last raw touch coordinates (x, y)
Returns:
PIL Image
"""
image = Image.new('L', (self.width, self.height), color=255)
draw = ImageDraw.Draw(image)
try:
font_title = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", 28)
font_text = ImageFont.truetype("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", 20)
except:
font_title = ImageFont.load_default()
font_text = ImageFont.load_default()
# Title
title = "Touch Calibration Test"
bbox = draw.textbbox((0, 0), title, font=font_title)
text_width = bbox[2] - bbox[0]
draw.text(((self.width - text_width) // 2, 20), title,
fill=0, font=font_title)
# Calibration status
if calibrated:
status = "Status: Calibration ACTIVE"
status_color = 0
else:
status = "Status: NO CALIBRATION (using raw coordinates)"
status_color = 128
bbox = draw.textbbox((0, 0), status, font=font_text)
text_width = bbox[2] - bbox[0]
draw.text(((self.width - text_width) // 2, 60), status,
fill=status_color, font=font_text)
# Instructions
instruction = "Tap anywhere on the screen"
bbox = draw.textbbox((0, 0), instruction, font=font_text)
text_width = bbox[2] - bbox[0]
draw.text(((self.width - text_width) // 2, 100), instruction,
fill=0, font=font_text)
# Draw crosshairs at last touch
if last_touch:
x, y = last_touch
# Draw crosshairs
draw.line([x - 20, y, x + 20, y], fill=0, width=2)
draw.line([x, y - 20, x, y + 20], fill=0, width=2)
draw.ellipse([x - 5, y - 5, x + 5, y + 5], fill=0, outline=0)
# Show coordinates
coord_text = f"Calibrated: ({x}, {y})"
draw.text((20, 140), coord_text, fill=0, font=font_text)
if raw_coords:
raw_x, raw_y = raw_coords
raw_text = f"Raw: ({raw_x}, {raw_y})"
draw.text((20, 170), raw_text, fill=128, font=font_text)
# Calculate offset
offset_x = x - raw_x
offset_y = y - raw_y
offset_text = f"Offset: ({offset_x:+d}, {offset_y:+d})"
draw.text((20, 200), offset_text, fill=128, font=font_text)
# Draw touch history
for i, (hx, hy) in enumerate(self.touch_history):
alpha = int(255 * (i + 1) / len(self.touch_history))
draw.ellipse([hx - 3, hy - 3, hx + 3, hy + 3],
fill=alpha, outline=alpha)
# Draw border
draw.rectangle([10, 10, self.width - 10, self.height - 10],
outline=0, width=2)
# Instructions at bottom
help_text = "Swipe left to exit"
bbox = draw.textbbox((0, 0), help_text, font=font_text)
text_width = bbox[2] - bbox[0]
draw.text(((self.width - text_width) // 2, self.height - 40),
help_text, fill=128, font=font_text)
return image
def add_touch(self, x: int, y: int):
"""Add touch to history."""
self.touch_history.append((x, y))
if len(self.touch_history) > self.max_history:
self.touch_history.pop(0)
async def test_calibration(width: int, height: int, virtual: bool = False):
"""
Run calibration test.
Args:
width: Display width
height: Display height
virtual: Use virtual display
"""
print("Touch Calibration Test")
print("======================")
# Initialize display
display = IT8951DisplayDriver(
width=width,
height=height,
virtual=virtual,
)
await display.initialize()
print("Display initialized")
# Initialize touch
touch = FT5xx6TouchDriver(
width=width,
height=height,
)
await touch.initialize()
# Check calibration status
calibrated = touch.calibration.is_calibrated()
if calibrated:
quality = touch.calibration.get_calibration_quality()
rms_error = touch.calibration.calibration_data.rms_error
print(f"Calibration loaded: {quality} (RMS error: {rms_error:.2f}px)")
else:
print("No calibration loaded - using raw coordinates")
# Create UI
ui = CalibrationTester(width, height)
# Initial screen
screen = ui.create_screen(calibrated)
await display.show_image(screen, mode=RefreshMode.QUALITY)
print("\nTap anywhere on the screen to test calibration")
print("Swipe left to exit\n")
# Event loop
try:
last_touch = None
last_raw = None
while True:
event = await touch.get_touch_event()
if event:
if event.gesture == GestureType.TAP:
# Store calibrated coordinates
last_touch = (event.x, event.y)
# Get raw coordinates (before calibration)
# Note: We can't easily get raw coords after the fact,
# but we can show the difference if calibration exists
if calibrated:
# Estimate raw by inverting calibration (not precise)
last_raw = (event.x, event.y) # Placeholder
else:
last_raw = (event.x, event.y)
ui.add_touch(event.x, event.y)
print(f"Touch at ({event.x}, {event.y})")
# Update screen
screen = ui.create_screen(calibrated, last_touch, last_raw)
await display.show_image(screen, mode=RefreshMode.FAST)
elif event.gesture == GestureType.SWIPE_LEFT:
print("Exiting...")
break
finally:
# Cleanup
await display.cleanup()
await touch.cleanup()
print("Test complete!")
def main():
"""Main entry point."""
parser = argparse.ArgumentParser(
description="Test touchscreen calibration for DReader HAL"
)
parser.add_argument(
'--width', type=int, default=800,
help='Display width in pixels (default: 800)'
)
parser.add_argument(
'--height', type=int, default=1200,
help='Display height in pixels (default: 1200)'
)
parser.add_argument(
'--virtual', action='store_true',
help='Use virtual display for testing'
)
args = parser.parse_args()
# Run test
asyncio.run(test_calibration(
width=args.width,
height=args.height,
virtual=args.virtual,
))
if __name__ == '__main__':
main()