Clean up repository and add Python formatting
This commit is contained in:
@@ -0,0 +1 @@
|
||||
.vscode
|
||||
@@ -0,0 +1,2 @@
|
||||
[tool.black]
|
||||
line-length = 80
|
||||
@@ -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
@@ -1,14 +1,14 @@
|
||||
"""
|
||||
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.
|
||||
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
|
||||
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
|
||||
3. Executing a sequenced loop of image capture, delay cycles, and incremental
|
||||
rotation steps.
|
||||
|
||||
Hardware Requirements:
|
||||
@@ -18,8 +18,8 @@ Hardware Requirements:
|
||||
|
||||
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
|
||||
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).
|
||||
"""
|
||||
|
||||
@@ -52,7 +52,9 @@ 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)
|
||||
requests.get(
|
||||
f"http://{CAMERA_IP}/switch_cammode.cgi?mode=shutter", timeout=TIMEOUT
|
||||
)
|
||||
time.sleep(0.3)
|
||||
|
||||
|
||||
@@ -60,8 +62,8 @@ 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
|
||||
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:
|
||||
@@ -72,7 +74,9 @@ def initialize_stand(ser):
|
||||
time.sleep(0.3)
|
||||
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())
|
||||
time.sleep(0.3)
|
||||
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.
|
||||
"""
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
@@ -94,8 +103,8 @@ 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
|
||||
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:
|
||||
@@ -109,7 +118,7 @@ def rotate_stand(ser):
|
||||
|
||||
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.
|
||||
|
||||
Args:
|
||||
@@ -117,16 +126,16 @@ def await_completion(ser, s):
|
||||
s (str): The substring expected in the response to indicate completion.
|
||||
|
||||
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.
|
||||
"""
|
||||
while True:
|
||||
line_bytes = ser.readline()
|
||||
line_str = line_bytes.decode('utf-8').strip()
|
||||
|
||||
line_str = line_bytes.decode("utf-8").strip()
|
||||
|
||||
if not line_str:
|
||||
raise SerialTimeoutError
|
||||
|
||||
|
||||
if s in line_str:
|
||||
break
|
||||
|
||||
@@ -136,7 +145,7 @@ def await_completion(ser, s):
|
||||
def main():
|
||||
with serial.Serial(ARDUINO_PORT, BAUD_RATE, timeout=TIMEOUT) as ser:
|
||||
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.")
|
||||
ser.reset_input_buffer()
|
||||
@@ -144,13 +153,14 @@ def main():
|
||||
|
||||
initialize_stand(ser)
|
||||
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
|
||||
|
||||
while current_angle < TOTAL_TARGET_DEGREES:
|
||||
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)
|
||||
|
||||
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"
|
||||
|
||||
# Send `a15` command
|
||||
# Response: "Waiting indefinitely for new command (MANUAL mode)."
|
||||
# Response: "Waiting indefinitely for new command (MANUAL mode)."
|
||||
|
||||
-182
@@ -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()
|
||||
Reference in New Issue
Block a user