first commit
This commit is contained in:
@@ -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())
|
||||
Executable
+355
@@ -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()
|
||||
Executable
+240
@@ -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...")
|
||||
@@ -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")
|
||||
Executable
+252
@@ -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()
|
||||
Reference in New Issue
Block a user