Clean up repository and add Python formatting

This commit is contained in:
2026-07-08 23:01:28 +08:00
parent 49a2a5b3bd
commit 200a500289
5 changed files with 37 additions and 376 deletions
+1
View File
@@ -0,0 +1 @@
.vscode
+2
View File
@@ -0,0 +1,2 @@
[tool.black]
line-length = 80
-170
View File
@@ -1,170 +0,0 @@
// #include <Arduino.h>
// // --- Hardware Configuration ---
// #define IN1 9
// #define IN2 10
// #define IN3 11
// #define IN4 12
// // The step sequence (standard for 28BYJ-48 motors)
// int stepSequence[8][4] = {
// { 1, 0, 0, 0 },
// { 1, 1, 0, 0 },
// { 0, 1, 0, 0 },
// { 0, 1, 1, 0 },
// { 0, 0, 1, 0 },
// { 0, 0, 1, 1 },
// { 0, 0, 0, 1 },
// { 1, 0, 0, 1 }
// };
// // --- Configuration Constants ---
// const int STEPS_PER_REV = 4096;
// int stepInterval = 1;
// bool autoMode = true;
// // --- State Variables ---
// long targetSteps = 0;
// long currentStepCount = 0;
// unsigned long lastMoveTime = 0; // Timer for movement steps
// int speedScale = 1;
// // --- Auto Mode Specific Variables ---
// unsigned long autoTimer = 0; // Tracks time since last auto-move
// unsigned long autoInterval = 5000; // 5 seconds delay between moves
// int autoState = 0; // 0 = Waiting, 1 = Moving
// int autoStepsToMove = 0; // Calculated steps for 15 degrees
// int autoStepIndex = 0; // Current step index within the move
// void setStep(int a, int b, int c, int d) {
// digitalWrite(IN1, a);
// digitalWrite(IN2, b);
// digitalWrite(IN3, c);
// digitalWrite(IN4, d);
// }
// void setup() {
// pinMode(IN1, OUTPUT);
// pinMode(IN2, OUTPUT);
// pinMode(IN3, OUTPUT);
// pinMode(IN4, OUTPUT);
// Serial.begin(9600);
// while (!Serial) {};
// stepInterval = map(speedScale, 1, 50, 1, 100);
// // Calculate steps for 15 degrees once
// autoStepsToMove = (int)((15.0 / 360.0) * STEPS_PER_REV);
// autoTimer = millis(); // Start the 5s timer immediately
// Serial.println("System Started.");
// Serial.println("Current Mode: AUTO (15 degrees every 5 seconds)");
// Serial.println("Send a number or 's[1-50]' to switch to MANUAL mode.");
// }
// void loop() {
// // --- 1. SERIAL INPUT PROCESSING (HIGHEST PRIORITY) ---
// // If ANY input is received, switch to manual mode IMMEDIATELY.
// if (Serial.available() > 0) {
// String input = Serial.readString();
// input.trim();
// if (input.length() > 0) {
// if (input.startsWith("s")) {
// // Handle Speed change
// int newSpeed = input.substring(1).toInt();
// if (newSpeed >= 1 && newSpeed <= 50) {
// speedScale = newSpeed;
// stepInterval = map(speedScale, 1, 50, 1, 100);
// // If we were moving, stop immediately
// currentStepCount = targetSteps;
// autoStepIndex = autoStepsToMove; // Force auto move to finish logic (or reset)
// Serial.print("Success: Speed set to ");
// Serial.println(speedScale);
// } else {
// Serial.println("Error: Use scale 1-50.");
// }
// } else {
// // Handle Degree change
// float val = input.toFloat();
// if (val != 0) {
// targetSteps = (long)((val / 360.0) * STEPS_PER_REV);
// currentStepCount = 0; // Reset count to start from zero
// lastMoveTime = millis();
// autoMode = false; // SWITCH TO MANUAL MODE
// autoState = 0; // Reset auto state machine
// Serial.print("Switching to Manual Mode. Target: ");
// Serial.print(val);
// Serial.println(" degrees.");
// }
// }
// }
// }
// // --- 2. EXECUTION LOGIC ---
// if (autoMode) {
// /*
// NON-BLOCKING AUTO MODE:
// State Machine:
// 0: Waiting for 5 seconds
// 1: Moving the 15 degrees
// */
// if (autoState == 0) {
// // Check if 5 seconds have passed
// if (millis() - autoTimer >= autoInterval) {
// autoTimer = millis(); // Reset timer
// autoState = 1; // Start moving
// autoStepIndex = 0; // Reset step counter for this move
// lastMoveTime = millis(); // Start the step timer
// }
// }
// else if (autoState == 1) {
// // Move one step if enough time passed
// if (millis() - lastMoveTime >= (unsigned long)stepInterval) {
// lastMoveTime = millis();
// // Calculate sequence index
// int seqIdx = autoStepIndex % 8;
// setStep(stepSequence[seqIdx][0], stepSequence[seqIdx][1],
// stepSequence[seqIdx][2], stepSequence[seqIdx][3]);
// autoStepIndex++;
// // Check if we finished the 15-degree move
// if (autoStepIndex >= autoStepsToMove) {
// autoState = 0; // Back to waiting
// Serial.println("Auto move complete. Waiting 5s...");
// }
// }
// }
// }
// else {
// /*
// MANUAL MODE:
// Non-blocking movement toward target.
// */
// if (currentStepCount != targetSteps) {
// if (millis() - lastMoveTime >= (unsigned long)stepInterval) {
// lastMoveTime = millis();
// // Determine direction
// long direction = (targetSteps > currentStepCount) ? 1 : -1;
// // Calculate sequence index (handling negative modulo correctly)
// int seqIdx = (int)((currentStepCount % 8 + 8) % 8);
// setStep(stepSequence[seqIdx][0],
// stepSequence[seqIdx][1],
// stepSequence[seqIdx][2],
// stepSequence[seqIdx][3]);
// currentStepCount += direction;
// }
// }
// }
// }
+34 -24
View File
@@ -1,14 +1,14 @@
""" """
Automated Photogrammetry Capture Suite Automated Photogrammetry Capture Suite
===================================== =====================================
This module automates a 360-degree photography sequence using an Olympus OM-D This module automates a 360-degree photography sequence using an Olympus OM-D
EM-10 Mk III camera and an Arduino-controlled rotation stand. EM-10 Mk III camera and an Arduino-controlled rotation stand.
The script synchronizes the two systems by: The script synchronizes the two systems by:
1. Initializing the camera's internal API to enter shutter mode. 1. Initializing the camera's internal API to enter shutter mode.
2. Establishing a persistent serial connection with the Arduino controller to 2. Establishing a persistent serial connection with the Arduino controller to
manage rotation speed and movement. manage rotation speed and movement.
3. Executing a sequenced loop of image capture, delay cycles, and incremental 3. Executing a sequenced loop of image capture, delay cycles, and incremental
rotation steps. rotation steps.
Hardware Requirements: Hardware Requirements:
@@ -18,8 +18,8 @@ Hardware Requirements:
Usage Note: Usage Note:
---------- ----------
Ensure the Camera's WiFi is active and the script 'CAMERA_IP' matches its 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 assigned address on your network. Ensure the correct 'ARDUINO_PORT' is mapped
to your local system (e.g., /dev/ttyUSB0 or COMx). to your local system (e.g., /dev/ttyUSB0 or COMx).
""" """
@@ -52,7 +52,9 @@ def initialize_camera():
Configures the camera's hardware state for photography. Configures the camera's hardware state for photography.
""" """
print("[CAMERA] Switching to SHUTTER mode.") print("[CAMERA] Switching to SHUTTER mode.")
requests.get(f"http://{CAMERA_IP}/switch_cammode.cgi?mode=shutter", timeout=TIMEOUT) requests.get(
f"http://{CAMERA_IP}/switch_cammode.cgi?mode=shutter", timeout=TIMEOUT
)
time.sleep(0.3) time.sleep(0.3)
@@ -60,8 +62,8 @@ def initialize_stand(ser):
""" """
Configures the Arduino-controlled rotation stand's parameters. Configures the Arduino-controlled rotation stand's parameters.
This is a blocking function. It sends commands to set the stand into 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 MANUAL mode and defines the rotation speed (deg/sec). It will not return
until the Arduino confirms these states have been successfully accepted. until the Arduino confirms these states have been successfully accepted.
Args: Args:
@@ -72,7 +74,9 @@ def initialize_stand(ser):
time.sleep(0.3) time.sleep(0.3)
await_completion(ser, "Mode: MANUAL") await_completion(ser, "Mode: MANUAL")
print(f"[STAND] Setting rotation speed to {ROTATION_SPEED_DPS} degrees/second.") print(
f"[STAND] Setting rotation speed to {ROTATION_SPEED_DPS} degrees/second."
)
ser.write(f"s{ROTATION_SPEED_DPS}\n".encode()) ser.write(f"s{ROTATION_SPEED_DPS}\n".encode())
time.sleep(0.3) time.sleep(0.3)
await_completion(ser, f"Speed: {ROTATION_SPEED_DPS:.0f}") await_completion(ser, f"Speed: {ROTATION_SPEED_DPS:.0f}")
@@ -83,10 +87,15 @@ def take_photo():
Triggers the camera's shutter and waits for capture completion. Triggers the camera's shutter and waits for capture completion.
""" """
print("[CAMERA] Activating electronic shutter.") print("[CAMERA] Activating electronic shutter.")
requests.get(f"http://{CAMERA_IP}/exec_shutter.cgi?type=1st2ndpush", timeout=TIMEOUT) requests.get(
f"http://{CAMERA_IP}/exec_shutter.cgi?type=1st2ndpush", timeout=TIMEOUT
)
time.sleep(0.3) time.sleep(0.3)
requests.get(f"http://{CAMERA_IP}/exec_shutter.cgi?com=2nd1strelease", timeout=TIMEOUT) requests.get(
f"http://{CAMERA_IP}/exec_shutter.cgi?com=2nd1strelease",
timeout=TIMEOUT,
)
time.sleep(0.3) time.sleep(0.3)
@@ -94,8 +103,8 @@ def rotate_stand(ser):
""" """
Commands the stand to perform a specific rotation. Commands the stand to perform a specific rotation.
This is a blocking function. It sends the degrees-to-rotate command This is a blocking function. It sends the degrees-to-rotate command
and monitors the serial line until the Arduino confirms that the and monitors the serial line until the Arduino confirms that the
movement has finished or is ready for the next instruction. movement has finished or is ready for the next instruction.
Args: Args:
@@ -109,7 +118,7 @@ def rotate_stand(ser):
def await_completion(ser, s): def await_completion(ser, s):
""" """
Blocks the execution of the script until a specific confirmation Blocks the execution of the script until a specific confirmation
string is detected in the serial output from the Arduino. string is detected in the serial output from the Arduino.
Args: Args:
@@ -117,16 +126,16 @@ def await_completion(ser, s):
s (str): The substring expected in the response to indicate completion. s (str): The substring expected in the response to indicate completion.
Raises: Raises:
SerialTimeoutError: If no data is received or if a SerialTimeoutError: If no data is received or if a
non-matching line is found repeatedly. non-matching line is found repeatedly.
""" """
while True: while True:
line_bytes = ser.readline() line_bytes = ser.readline()
line_str = line_bytes.decode('utf-8').strip() line_str = line_bytes.decode("utf-8").strip()
if not line_str: if not line_str:
raise SerialTimeoutError raise SerialTimeoutError
if s in line_str: if s in line_str:
break break
@@ -136,7 +145,7 @@ def await_completion(ser, s):
def main(): def main():
with serial.Serial(ARDUINO_PORT, BAUD_RATE, timeout=TIMEOUT) as ser: with serial.Serial(ARDUINO_PORT, BAUD_RATE, timeout=TIMEOUT) as ser:
try: try:
time.sleep(2.0) # Buffer to account for Arduino startup time.sleep(2.0) # Buffer to account for Arduino startup
print("Flushing serial buffers.") print("Flushing serial buffers.")
ser.reset_input_buffer() ser.reset_input_buffer()
@@ -144,13 +153,14 @@ def main():
initialize_stand(ser) initialize_stand(ser)
initialize_camera() initialize_camera()
time.sleep(2.0) # Buffer to ensure the display is not moving time.sleep(2.0) # Buffer to ensure the display is not moving
current_angle = 0.0 current_angle = 0.0
while current_angle < TOTAL_TARGET_DEGREES: while current_angle < TOTAL_TARGET_DEGREES:
take_photo() take_photo()
time.sleep(PHOTO_TO_MOVE_DELAY) # Buffer to account for shutter speed # Buffer to account for shutter speed
time.sleep(PHOTO_TO_MOVE_DELAY)
rotate_stand(ser) rotate_stand(ser)
current_angle += ROTATION_STEP_SIZE current_angle += ROTATION_STEP_SIZE
@@ -173,4 +183,4 @@ if __name__ == "__main__":
# Response: "Mode: MANUAL | State: WAIT | Steps Left: 0 | Speed: 45.00 deg/s | Wait: 8.00s" # Response: "Mode: MANUAL | State: WAIT | Steps Left: 0 | Speed: 45.00 deg/s | Wait: 8.00s"
# Send `a15` command # Send `a15` command
# Response: "Waiting indefinitely for new command (MANUAL mode)." # Response: "Waiting indefinitely for new command (MANUAL mode)."
-182
View File
@@ -1,182 +0,0 @@
import requests
import serial
import time
import sys
import re
# ================= 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 # Wait after photo before rotation starts
SERIAL_TIMEOUT = 10.0 # Increased timeout for long moves
# ================================================
def emergency_shutdown(error_msg, ser=None):
"""
Failsafe routine: Attempts to switch camera to play mode and exits.
"""
print(f"\n[!] ERROR DETECTED: {error_msg}")
print("Initiating failsafe: Switching camera back to default state...")
try:
requests.get(f"http://{CAMERA_IP}/switch_cammode.cgi?mode=play", timeout=5)
print("Camera switched to play mode.")
except Exception as e:
print(f"Failed to switch camera mode during shutdown: {e}")
if ser:
try:
ser.close()
print("Serial connection closed.")
except:
pass
print("System halted.")
sys.exit(1)
def send_arduino_command(ser, command):
"""
Sends a command to the Arduino.
Does NOT wait for a response here to keep the flow simple,
but the response is consumed in the main loop.
"""
try:
ser.write(f"{command}\n".encode())
# Small delay to ensure Arduino receives command before next action
time.sleep(0.1)
except Exception as e:
raise Exception(f"Failed to send command '{command}': {e}")
def wait_for_move_completion(ser, expected_angle, speed_dps):
"""
Waits until the Arduino reports 'Steps Left: 0'.
Falls back to a calculated timeout if serial parsing fails.
"""
# Calculate expected time for the move as a safety fallback
# angle / speed = seconds. Add 10% buffer.
expected_time = (expected_angle / speed_dps) * 1.2
start_time = time.time()
print(f"Waiting for movement ({expected_angle}°) to complete...")
while True:
if ser.in_waiting > 0:
try:
line = ser.readline().decode('utf-8').strip()
if not line:
continue
# Check for "Steps Left: 0" in the Arduino status output
# Your Arduino prints: " | Steps Left: 0"
if "Steps Left: 0" in line:
print("Movement complete (Steps Left: 0 detected).")
return True
# Optional: Debug print if needed
# print(f"Arduino: {line}")
except Exception as e:
print(f"Warning: Error reading serial: {e}")
# Fallback timeout
elapsed = time.time() - start_time
if elapsed > max(expected_time + 5, 30): # Max 30s or calculated time + 5s
print(f"Timeout reached ({elapsed:.1f}s). Assuming move finished.")
return False
time.sleep(0.1) # Prevent CPU spinning
def main():
ser = None
try:
# 1. Initialize Serial Connection (Persistent)
print("Opening serial port...")
ser = serial.Serial(ARDUINO_PORT, BAUD_RATE, timeout=1)
time.sleep(2) # Wait for Arduino to reset and boot
# Flush any garbage data from boot
ser.reset_input_buffer()
ser.reset_output_buffer()
# 2. Initialize Table Parameters
print("Initializing table parameters...")
send_arduino_command(ser, f"s{ROTATION_SPEED_DPS}")
time.sleep(0.5)
send_arduino_command(ser, "m1") # Set to Manual mode (controlled by script)
time.sleep(0.5)
# 3. Prepare Camera State
print("Switching camera to shutter mode...")
requests.get(f"http://{CAMERA_IP}/switch_cammode.cgi?mode=shutter", timeout=5)
print("System ready.")
current_angle = 0.0
total_steps = int(TOTAL_TARGET_DEGREES / ROTATION_STEP_SIZE)
# Ensure we cover the full circle if 360 is the target
# If 360/15 = 24 steps, we do 24 moves.
# The loop runs while current_angle < TOTAL (0 to 345), then we do one last check?
# Actually, simpler: Loop for exactly the number of steps needed.
steps_taken = 0
while steps_taken <= total_steps:
# A. Capture Image
angle_label = int(current_angle)
print(f"\n--- Step {steps_taken + 1}/{total_steps + 1} ---")
print(f"Capturing image at {angle_label}°...")
try:
# 1. Push Shutter (Start exposure)
req1 = requests.get(f"http://{CAMERA_IP}/exec_shutter.cgi?type=1st2ndpush", timeout=10)
time.sleep(0.1)
# 2. Release Shutter (End exposure) - CRITICAL STEP
# The user specified this command is required to actually take the photo
req2 = requests.get(f"http://{CAMERA_IP}/exec_shutter.cgi?com=2nd1strelease", timeout=10)
if req1.status_code != 200 or req2.status_code != 200:
raise Exception(f"Camera returned status {req1.status_code} / {req2.status_code}")
print("Image captured successfully.")
except Exception as e:
raise Exception(f"Camera shutter sequence failed: {e}")
# If this is the last step, we don't need to move or wait
if steps_taken == total_steps:
print("All steps completed.")
break
# B. Wait for camera processing
print(f"Waiting {PHOTO_TO_MOVE_DELAY}s for camera processing...")
time.sleep(PHOTO_TO_MOVE_DELAY)
# C. Rotate Table
print(f"Rotating {ROTATION_STEP_SIZE}°...")
send_arduino_command(ser, f"a{ROTATION_STEP_SIZE}")
# D. Wait for movement to finish
# We pass the step size and speed to calculate a safe wait time
wait_for_move_completion(ser, ROTATION_STEP_SIZE, ROTATION_SPEED_DPS)
current_angle += ROTATION_STEP_SIZE
steps_taken += 1
print(f"Progress: {int(current_angle)}/{TOTAL_TARGET_DEGREES} degrees.")
print("\n[SUCCESS] Full rotation completed successfully.")
except Exception as e:
emergency_shutdown(str(e), ser)
finally:
if ser and ser.is_open:
ser.close()
if __name__ == "__main__":
main()