Files
ardu-scan/run_scan.py
T
2026-07-08 21:56:21 +08:00

176 lines
5.3 KiB
Python

"""
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(0.3)
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()
time.sleep(PHOTO_TO_MOVE_DELAY) # Buffer to account for shutter speed
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)."