""" Automated Photogrammetry Capture Suite ===================================== This module automates a 360-degree photography sequence using an Olympus OM-D EM-10 Mk III camera and an Arduino-controlled rotation stand. The script synchronizes the two systems by: 1. Initializing the camera's internal API to enter shutter mode. 2. Establishing a persistent serial connection with the Arduino controller to manage rotation speed and movement. 3. Executing a sequenced loop of image capture, delay cycles, and incremental rotation steps. Hardware Requirements: ----------------------- - Camera: Olympus OM-D EM-10 Mk III (connected via its internal WiFi network). - Stand: Arduino-based rotation table (connected via USB/Serial). Usage Note: ---------- Ensure the Camera's WiFi is active and the script 'CAMERA_IP' matches its assigned address on your network. Ensure the correct 'ARDUINO_PORT' is mapped to your local system (e.g., /dev/ttyUSB0 or COMx). """ import requests import serial import time # ================= CONFIGURATION ================= CAMERA_IP = "192.168.0.10" ARDUINO_PORT = "/dev/ttyUSB0" BAUD_RATE = 9600 # Motion Control ROTATION_SPEED_DPS = 45.0 ROTATION_STEP_SIZE = 15.0 TOTAL_TARGET_DEGREES = 360.0 # Timing PHOTO_TO_MOVE_DELAY = 8.0 TIMEOUT = 10.0 # ================================================ class SerialTimeoutError(Exception): pass def initialize_camera(): """ Configures the camera's hardware state for photography. """ print("[CAMERA] Switching to SHUTTER mode.") requests.get( f"http://{CAMERA_IP}/switch_cammode.cgi?mode=shutter", timeout=TIMEOUT ) time.sleep(0.3) def initialize_stand(ser): """ Configures the Arduino-controlled rotation stand's parameters. This is a blocking function. It sends commands to set the stand into MANUAL mode and defines the rotation speed (deg/sec). It will not return until the Arduino confirms these states have been successfully accepted. Args: ser (serial.Serial): The open serial connection to the Arduino controller. """ print("[STAND] Switching to MANUAL mode.") ser.write(f"m1\n".encode()) time.sleep(0.3) await_completion(ser, "Mode: MANUAL") print( f"[STAND] Setting rotation speed to {ROTATION_SPEED_DPS} degrees/second." ) ser.write(f"s{ROTATION_SPEED_DPS}\n".encode()) time.sleep(0.3) await_completion(ser, f"Speed: {ROTATION_SPEED_DPS:.0f}") def take_photo(): """ Triggers the camera's shutter and waits for capture completion. """ print("[CAMERA] Activating electronic shutter.") requests.get( f"http://{CAMERA_IP}/exec_shutter.cgi?type=1st2ndpush", timeout=TIMEOUT ) time.sleep(0.3) requests.get( f"http://{CAMERA_IP}/exec_shutter.cgi?com=2nd1strelease", timeout=TIMEOUT, ) time.sleep(PHOTO_TO_MOVE_DELAY) # Buffer to account for shutter speed def rotate_stand(ser): """ Commands the stand to perform a specific rotation. This is a blocking function. It sends the degrees-to-rotate command and monitors the serial line until the Arduino confirms that the movement has finished or is ready for the next instruction. Args: ser (serial.Serial): The open serial connection to the Arduino controller. """ print(f"[STAND] Rotating stand by {ROTATION_STEP_SIZE} degrees.") ser.write(f"a{ROTATION_STEP_SIZE}\n".encode()) time.sleep(0.3) await_completion(ser, "Waiting") def await_completion(ser, s): """ Blocks the execution of the script until a specific confirmation string is detected in the serial output from the Arduino. Args: ser (serial.Serial): The open serial connection to the Arduino. s (str): The substring expected in the response to indicate completion. Raises: SerialTimeoutError: If no data is received or if a non-matching line is found repeatedly. """ while True: line_bytes = ser.readline() line_str = line_bytes.decode("utf-8").strip() if not line_str: raise SerialTimeoutError if s in line_str: break time.sleep(0.1) def main(): with serial.Serial(ARDUINO_PORT, BAUD_RATE, timeout=TIMEOUT) as ser: try: time.sleep(2.0) # Buffer to account for Arduino startup print("Flushing serial buffers.") ser.reset_input_buffer() ser.reset_output_buffer() initialize_stand(ser) initialize_camera() time.sleep(2.0) # Buffer to ensure the display is not moving current_angle = 0.0 while current_angle < TOTAL_TARGET_DEGREES: take_photo() rotate_stand(ser) current_angle += ROTATION_STEP_SIZE except requests.Timeout: print("Connection to camera API lost.") except SerialTimeoutError: print("Arduino failed to respond in time.") if __name__ == "__main__": main() # Send `m1` command # Response: "Mode: MANUAL | State: WAIT | Steps Left: 0 | Speed: 45.00 deg/s | Wait: 8.00s" # Send `s45` command # Response: "Mode: MANUAL | State: WAIT | Steps Left: 0 | Speed: 45.00 deg/s | Wait: 8.00s" # Send `a15` command # Response: "Waiting indefinitely for new command (MANUAL mode)."