Advertisement
DamagedDolphin

clock.py

Dec 28th, 2024
270
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.63 KB | None | 0 0
  1. import board
  2. import busio
  3. import adafruit_pca9685
  4. import datetime
  5. import time
  6. import json
  7. import sys
  8. import termios
  9. import tty
  10. import select
  11.  
  12. # Constants
  13. PWM_FREQUENCY = 60
  14. CALIBRATION_FILE = "calibration_data.json"
  15. FINE_STEP = 50  # Fine tuning step (for left/right arrows)
  16. COARSE_STEP = 500  # Coarse tuning step (for up/down arrows)
  17.  
  18. # Initialize PCA9685
  19. i2c = busio.I2C(board.SCL, board.SDA)
  20. hat = adafruit_pca9685.PCA9685(i2c)
  21. hat.frequency = PWM_FREQUENCY
  22.  
  23. # Define PWM channels
  24. clockSeconds = hat.channels[2]
  25. clockMinutes = hat.channels[1]
  26. clockHours = hat.channels[0]
  27.  
  28.  
  29. def is_key_pressed():
  30.     """Checks if a key is pressed (non-blocking)."""
  31.     fd = sys.stdin.fileno()
  32.     old_settings = termios.tcgetattr(fd)
  33.     try:
  34.         tty.setcbreak(fd)
  35.         rlist, _, _ = select.select([sys.stdin], [], [], 0.1)
  36.         if rlist:
  37.             key = sys.stdin.read(1)
  38.             return key
  39.         return None
  40.     finally:
  41.         termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
  42.  
  43.  
  44. def calibrate_dials():
  45.     """
  46.    Calibration mode to record or adjust PWM values for each step.
  47.    Existing values from the calibration file can be adjusted or skipped.
  48.    Saves the results to the calibration file.
  49.    """
  50.     print(
  51.         "Calibration mode. Use LEFT/RIGHT for fine tuning, UP/DOWN for coarse tuning. Press SPACE to save and advance. Press ENTER to skip."
  52.     )
  53.  
  54.     # Calibration steps
  55.     seconds_minutes_steps = [0, 10, 20, 30, 40, 50, 60]
  56.     hours_steps = list(range(0, 13))
  57.  
  58.     # Try to load existing calibration data
  59.     try:
  60.         with open(CALIBRATION_FILE, "r") as f:
  61.             calibration_data = json.load(f)
  62.     except FileNotFoundError:
  63.         calibration_data = {"seconds": {}, "minutes": {}, "hours": {}}
  64.  
  65.     # Helper function to calibrate a single dial
  66.     def calibrate_dial(channel, steps, label):
  67.         print(f"Calibrating {label} needle...")
  68.         for step in steps:
  69.             current_value = calibration_data.get(label, {}).get(str(step), 0)
  70.             print(
  71.                 f"Step {step}: Adjust the needle to {step} using LEFT/RIGHT (fine) or UP/DOWN (coarse). Press SPACE when done, or ENTER to skip."
  72.             )
  73.  
  74.             if current_value:
  75.                 print(f"Existing value for {step}: {current_value}. Press ENTER to skip or adjust.")
  76.  
  77.             pwm_value = current_value
  78.  
  79.             # Default PWM value for the channel
  80.             channel.duty_cycle = pwm_value
  81.  
  82.             # Adjust PWM value until the user confirms or skips
  83.             while True:
  84.                 key = is_key_pressed()
  85.  
  86.                 if key == "\n":  # Enter to skip
  87.                     print(f"Skipped adjustment for {label} {step}. Keeping value: {pwm_value}")
  88.                     break
  89.                 elif key == " ":  # Spacebar to confirm the position
  90.                     print(f"Recorded PWM value {pwm_value} for {label} {step}.")
  91.                     calibration_data[label][str(step)] = pwm_value
  92.                     break
  93.                 elif key == "\x1b":  # Arrow keys (Escape sequences)
  94.                     arrow = sys.stdin.read(2)
  95.                     if arrow == "[D":  # Left arrow key (fine decrement)
  96.                         pwm_value = max(0, pwm_value - FINE_STEP)
  97.                     elif arrow == "[C":  # Right arrow key (fine increment)
  98.                         pwm_value = min(65535, pwm_value + FINE_STEP)
  99.                     elif arrow == "[A":  # Up arrow key (coarse increment)
  100.                         pwm_value = min(65535, pwm_value + COARSE_STEP)
  101.                     elif arrow == "[B":  # Down arrow key (coarse decrement)
  102.                         pwm_value = max(0, pwm_value - COARSE_STEP)
  103.  
  104.                     # Update the needle position
  105.                     channel.duty_cycle = pwm_value
  106.  
  107.     # Calibrate each dial
  108.     calibrate_dial(clockSeconds, seconds_minutes_steps, "seconds")
  109.     calibrate_dial(clockMinutes, seconds_minutes_steps, "minutes")
  110.     calibrate_dial(clockHours, hours_steps, "hours")
  111.  
  112.     # Save the calibration data to a file
  113.     with open(CALIBRATION_FILE, "w") as f:
  114.         json.dump(calibration_data, f, indent=4)
  115.     print(f"Calibration complete. Data saved to {CALIBRATION_FILE}.")
  116.  
  117.  
  118. def load_calibration_data():
  119.     """Load calibration data from the file."""
  120.     try:
  121.         with open(CALIBRATION_FILE, "r") as f:
  122.             return json.load(f)
  123.     except FileNotFoundError:
  124.         print(f"No calibration file found. Please run the program with --calibrate to create one.")
  125.         sys.exit(1)
  126.  
  127.  
  128. def interpolate_pwm(calibration, current_value):
  129.     """
  130.    Interpolates PWM values from calibration data for a given current value.
  131.    Args:
  132.        calibration: A dictionary containing calibration data (e.g., seconds, minutes).
  133.        current_value: The current second, minute, or hour to interpolate for.
  134.    Returns:
  135.        The interpolated PWM value.
  136.    """
  137.     keys = sorted(int(k) for k in calibration.keys())
  138.     for i in range(len(keys) - 1):
  139.         if keys[i] <= current_value <= keys[i + 1]:
  140.             lower_key = keys[i]
  141.             upper_key = keys[i + 1]
  142.             lower_pwm = calibration[str(lower_key)]
  143.             upper_pwm = calibration[str(upper_key)]
  144.             # Linear interpolation
  145.             return int(lower_pwm + (upper_pwm - lower_pwm) * ((current_value - lower_key) / (upper_key - lower_key)))
  146.     return int(calibration[str(keys[-1])])  # Default to the last key
  147.  
  148. def move_needle_smoothly(channel, start_pwm, end_pwm, duration=0.1):
  149.     """
  150.    Smoothly move a needle between two PWM values.
  151.    Args:
  152.        channel: PCA9685 channel controlling the needle.
  153.        start_pwm: Starting PWM value.
  154.        end_pwm: Target PWM value.
  155.        duration: Time duration (seconds) for the smooth transition.
  156.    """
  157.     steps = 50  # Number of steps for smooth motion
  158.     step_time = duration / steps
  159.     pwm_step = (end_pwm - start_pwm) / steps
  160.  
  161.     current_pwm = start_pwm
  162.     for _ in range(steps):
  163.         current_pwm += pwm_step
  164.         channel.duty_cycle = int(current_pwm)
  165.         time.sleep(step_time)
  166.  
  167. def run_clock():
  168.     """
  169.    Main clock routine to move the needles based on current time
  170.    using calibrated PWM values.
  171.    """
  172.     calibration_data = load_calibration_data()
  173.  
  174.     # Store the last known PWM values for smooth transitions
  175.     last_second_pwm = 0
  176.     last_minute_pwm = 0
  177.     last_hour_pwm = 0
  178.  
  179.     while True:
  180.         # Get the current time
  181.         now = datetime.datetime.now()
  182.         current_second = now.second + now.microsecond / 1_000_000  # Include fractional seconds
  183.         current_minute = now.minute + current_second / 60  # Fractional minute
  184.         current_hour = (now.hour % 12 or 12) + current_minute / 60  # Fractional hour
  185.  
  186.         # Interpolate PWM values
  187.         second_pwm = interpolate_pwm(calibration_data["seconds"], current_second)
  188.         minute_pwm = interpolate_pwm(calibration_data["minutes"], current_minute)
  189.         hour_pwm = interpolate_pwm(calibration_data["hours"], current_hour)
  190.  
  191.         # Smoothly move the needles
  192.         move_needle_smoothly(clockSeconds, last_second_pwm, second_pwm, 0.02)
  193.         move_needle_smoothly(clockMinutes, last_minute_pwm, minute_pwm, 0.02)
  194.         move_needle_smoothly(clockHours, last_hour_pwm, hour_pwm, 0.02)
  195.  
  196.         # Update last PWM values
  197.         last_second_pwm = second_pwm
  198.         last_minute_pwm = minute_pwm
  199.         last_hour_pwm = hour_pwm
  200.  
  201.         # Sleep briefly before the next update (for smooth continuous operation)
  202.         time.sleep(0.02)
  203.  
  204. # Main logic
  205. if len(sys.argv) > 1 and sys.argv[1] == "--calibrate":
  206.     calibrate_dials()
  207. else:
  208.     run_clock()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement