Debug: Database connection successful Python Computer Language (Page 7) / Science, Technology, and Astronomy / New Mars Forums

Announcement

Announcement: This forum is accepting new registrations via email. Please see Recruiting Topic for additional information. Write newmarsmember[at_symbol]gmail.com.

#151 Yesterday 10:52:29

tahanson43206
Moderator
Registered: 2018-04-27
Posts: 25,911

Re: Python Computer Language

We skipped V82 in this series. V82 was devoted to solving a problem with code to compute movement of the robot arm in a distance X without change of Z. V83 is able to perform the maneuver, but all the diagnostic printing has been consolidated in a single function which can be activated if needed. We are now moving toward configuration of hardware and software to operate as a stand-alone system without keyboard, mouse or monitor. We are not quite ready for that. V83 is a first step toward field operation.

# bridgeV83.py Prepared by ChatGPT Supervised by Tom Hanson
# ==============================================================================
# Version 83: CENTRALIZED OPTIONAL WINDOWS 7 / HYPERTERMINAL REPORTING
#             All Win7 serial output passes through report_win7().
#             Enter N or X for the Windows 7 port during setup to disable it.
#             Robot calculations, safety audits, Cokoino traffic, and SSC-32U
#             control remain active when Win7 reporting is disabled.
# ==============================================================================
# Version 82: CORRECTED JOINT/WORLD GEOMETRY + L1 ALTITUDE AUDIT
#
# PURPOSE:
# V82 corrects the mathematical relationship between the robot's local joint
# angles and the fixed X-Z world coordinate system used by the L1 Tool Advance.
#
# PHYSICAL OBSERVATION LEADING TO V82:
# Bench tests established that the Shoulder, Elbow, and Wrist motors are mounted
# with their power hubs on the PORT side of the arm.  Viewed from STARBOARD,
# the observer sees the aft/end side of all three motor shafts.  Increasing PWM
# produces the same observed motor rotation sense at these motors.
#
# The important distinction is that identical motor rotation does NOT mean that
# every joint angle is added in the world-coordinate geometry.  The Shoulder
# angle establishes the absolute world orientation of Link AB.  The Elbow angle
# is a LOCAL bend of Link BC relative to Link AB.
#
# V81 modeled the absolute orientation of BC as:
#
#       BC_world_angle = shoulder_angle + elbow_bend
#
# Physical bench observations indicate that, for the installed AL5D geometry
# and the angle conventions used by this program, the relationship should be:
#
#       BC_world_angle = shoulder_angle - elbow_bend
#
# V82 will therefore revise Forward Kinematics, Inverse Kinematics, and wrist
# counter-rotation together so that all calculations use one consistent joint-
# to-world convention.  The change will NOT be made as an isolated sign change.
#
# L1 MOTION REQUIREMENT:
# Pivot C (the wrist pivot) is commanded to advance exactly +50 mm in world X
# while maintaining its starting world Z coordinate:
#
#       C_target_X = C_start_X + 50.0 mm
#       C_target_Z = C_start_Z
#
# V82 will make this Z-altitude requirement conspicuous in the HyperTerminal
# transcript.  The audit will report:
#
#   - Starting X and Z coordinates of Pivots B and C
#   - Starting world orientation of AB and BC
#   - Starting local elbow bend
#   - Requested C target X and target Z
#   - Solved final Shoulder angle, Elbow bend, and BC world angle
#   - Calculated final B and C coordinates
#   - Starting C Z altitude
#   - Requested C Z altitude
#   - Calculated final C Z altitude
#   - Calculated Delta-Z and Z error
#   - Expected physical Shoulder, Elbow, and Wrist rotation directions
#   - Starting and final physical PWM values
#   - Final Forward-Kinematics verification of Delta-X and Delta-Z
#   - Exact SSC-32U synchronized group-move command
#
# TEST POLICY:
# V81 remains the frozen known reference version.
#
# Initial V82 testing will preserve the real SSC-32U command-generation and
# serial-write path.  After normal startup/verification and establishment of
# the READY starting pose, servo power will be removed before L1 is invoked.
# V82 can then execute the complete production calculation and report the exact
# command it would send without permitting physical arm movement.
#
# Only after the V82 transcript demonstrates correct geometry, correct motor
# direction, and constant calculated C altitude will servo power be restored
# for a live L1 test.  During that test, the physical altitude of Pivot C will
# be measured before and after L1 and compared with the mathematical Delta-Z.
#
# DESIGN PRINCIPLE:
#
#       GEOMETRY FIRST.  MOTORS SECOND.
#
# World geometry, local joint geometry, and PWM/motor coordinates are related
# but distinct.  V82 keeps those distinctions explicit so that mathematical
# self-consistency cannot conceal an incorrect physical coordinate convention.
# ==============================================================================
# # Version 81: LIVE +50 MM L1 GROUP-MOVE VERSION.
#             Retains V80 geometry, envelope, PWM, and path audits.
#             After all checks pass, one synchronized S1/S2/S3
#             endpoint command will be sent to the SSC-32U.
# # Version 80: SSC-32U GROUP-MOVE TRAJECTORY AUDIT.
#             Calculates one complete +50 mm +X endpoint.
#             Simulates the intermediate Cartesian path produced
#             by a synchronized S1/S2/S3 group move.
#             NO PHYSICAL L1 MOTION IS SENT TO LYNXMOTION.
# # Version 79: FIRST LIVE L1 TEST.
#             Executes one validated +10 mm +X (bowward) wrist advance only.
#             Retains V78 305 mm operational envelope and IK safety checks.
# Version 76: Adding code to clear Cokoino LED's after Halt
# Verions prior to 76 were prepared by Gemini as supervised by Tom Hanson
# Version 75: CALIBRATED PHYSICAL ANGLE ENGINE & START LED RESET RECTIFIED.
#             Corrected pwm_to_physical_radians() to remove trim offsets before angle conversion.
#             Ensured S1 (1386 PWM) = 90.0 deg and S2 (633 PWM) = 0.0 deg physical baseline.
#             Updated START handler to send LED:LOCK:0 clear before LED:LOCK:1 to flush red lockout buffer.
# Version 74: KINEMATIC MATH & BOUNDARY AUDIT RECTIFIED.
#             Corrected 2-DOF Inverse Kinematics (IK) motor-frame alignment.
#             Enforced strict Cartesian altitude (Z-drift) boundary checks in Step 12.
# Version 73: FULL DIAGNOSTIC TELEMETRY RESTORED.
# Version 69: Baseline physical forward kinematics engine with trim offset mapping.

# ==============================================================================
#                 STARBOARD PHYSICAL VIEW vs PORT MOTOR FRAME MAP
# ==============================================================================
# [STERN / 180 deg / 2500us] <- VERTICAL (90 deg / 1500us) -> [BOW / 0 deg / 500us]
# - INCREASING PWM moves joint toward STERN (Aft / Backward / Down in Tuck)
# - DECREASING PWM moves joint toward BOW   (Forward / Up in Tuck)
# ==============================================================================

# ==============================================================================
#                      OFFICIAL FIELD OPERATOR CARD MATRIX
# ==============================================================================
# STEP 0  | Hex x'0' | LED: ROTATING RED LOOP | Power Up: Lockout Safe Mode
# STEP 1  | Hex x'1' | LED: BLUE/WHITE BIT 1  | START Pressed: Runtime Engine Live
# STEP 2  | Hex x'2' | LED: BLUE/WHITE BIT 2  | CROSS Pressed: Query LynxMotion VER
# STEP 3  | Hex x'3' | LED: BLUE/WHITE BIT 3  | CIRCLE Pressed: Transit to TUCK
# STEP 4  | Hex x'4' | LED: BLUE/WHITE BIT 4  | TRIANGLE Pressed: Transit to HOME
# STEP 5  | Hex x'5' | LED: BLUE/WHITE BIT 5  | SQUARE Pressed: Transit to READY (Tuned)
# STEP 6  | Hex x'6' | LED: FLASH/SOLID BLUE | L3 Click: Tune Base/Shoulder (Lock & Save)
# STEP 7  | Hex x'7' | LED: FLASH/SOLID BLUE | R3 Click: Tune Elbow Reach (Lock & Save)
# STEP 8  | Hex x'8' | LED: BLUE/WHITE BIT 8  | D-Pad: Tune Wrist Pitch & Rotate (Live)
# STEP 9  | Hex x'9' | LED: BLUE/WHITE BIT 9  | L2 Pressed: Tool Retract (Transit to READY)
# STEP 10 | Hex x'A' | LED: BLUE/WHITE BIT 10 | R1 Pressed: Ready Position (Transit to READY)
# STEP 11 | Hex x'B' | LED: BLUE/WHITE BIT 11 | R2 Pressed: System Home (Transit to HOME)
# STEP 12 | Hex x'C' | LED: BLUE/WHITE BIT 12 | L1 Pressed: Tool Advance (FK Calculation Audit)
# ==============================================================================

import math
import serial
import serial.tools.list_ports
import time
import xml.etree.ElementTree as ET

# --- PHYSICAL LINK LENGTH PARAMETERS ---
L1_SHOULDER_MM = 145.0  # Link 1 (Shoulder pivot A to Elbow pivot B)
L2_ELBOW_MM = 185.0  # Link 2 (Elbow pivot B to Wrist pivot C)
L3_WRIST_MM = 120.0  # Link 3 (Wrist pivot C to Tool Tip T)

# --- PHYSICAL HARDWARE CALIBRATION OFFSETS (BENCH MEASURED) ---
# Maps ideal target PWM to actual physical PWM required to achieve true alignment.
PHYSICAL_TRIM_OFFSETS = {
    0: 0,  # Base
    1: -114,  # Shoulder (Trims nominal 1500 -> 1386 PWM forward toward Bow for true 90 deg)
    2: 133,  # Elbow (Trims nominal 500 -> 633 PWM for true 0 deg physical extension)
    3: 0,  # Wrist Pitch
    4: 0,  # Wrist Rotate
    5: 0,  # Gripper
}

# Safe operational limits for servo pulse widths (PWM)
SAFE_PWM_MIN = 600
SAFE_PWM_MAX = 2400

# Deliberate operational reach envelope for Step 12 L1 motion.
# Geometric maximum wrist reach is 330 mm, but normal operation
# is intentionally kept inside that limit.
MAX_WRIST_REACH_MM = 305.0
REACH_POLICY_TOL_MM = 1.0e-9

# --- PWM & ANGLE CONVERSION HELPERS ---
def constraint_safety_clip(pulse):
    return max(500, min(2500, pulse))


def get_physical_pwm(channel, raw_pwm):
    """Converts a nominal/commanded PWM to the true physical PWM acting on hardware."""
    calibrated_pwm = raw_pwm + PHYSICAL_TRIM_OFFSETS.get(channel, 0)
    return constraint_safety_clip(calibrated_pwm)

def get_nominal_pwm(channel, physical_pwm):
    """Converts a physical PWM back to the program's nominal/reference PWM."""
    nominal_pwm = physical_pwm - PHYSICAL_TRIM_OFFSETS.get(channel, 0)
    return int(round(nominal_pwm))

def pwm_to_physical_radians(channel, phys_pwm):
    """Converts actual physical PWM to true geometric joint angles (radians).

    Strips hardware calibration trim before angle scaling so bench positions evaluate accurately.
    """
    untrimmed_pwm = phys_pwm - PHYSICAL_TRIM_OFFSETS.get(channel, 0)
    degrees = (untrimmed_pwm - 500) * (180.0 / 2000.0)
    return math.radians(degrees)


def physical_radians_to_pwm(channel, rad):
    """Converts physical joint angle in radians back to calibrated physical PWM."""
    degrees = math.degrees(rad)
    untrimmed_pwm = 500 + (degrees * (2000.0 / 180.0))
    phys_pwm = untrimmed_pwm + PHYSICAL_TRIM_OFFSETS.get(channel, 0)
    return int(round(phys_pwm))


# --- FULL FORWARD KINEMATICS ENGINE (3 JOINTS + TIP) ---
def compute_full_kinematics(theta1_rad, theta2_rad, theta3_rad):
    """Computes Cartesian positions (X, Z) for all arm pivots.

    Origin (0,0) is fixed at Shoulder Pivot A.
    Returns:
        (x_b, z_b)       -> Elbow Pivot B
        (x_c, z_c)       -> Wrist Pivot C
        (x_tip, z_tip)   -> Tool Tip Position T
    """
    # 1. Shoulder Pivot A (Fixed Baseline Foundation)
    x_a = 0.0
    z_a = 0.0

    # 2. Elbow Joint B relative to Shoulder Pivot A
    x_b = L1_SHOULDER_MM * math.cos(theta1_rad)
    z_b = L1_SHOULDER_MM * math.sin(theta1_rad)

    # 3. Absolute world angle of Link 2 (Elbow link BC)
    #
    # V82 PHYSICAL JOINT CONVENTION:
    # theta1_rad is the absolute world angle of Link AB.
    # theta2_rad is the LOCAL elbow bend measured in the installed motor's
    # positive direction.  Bench testing established that increasing this
    # elbow bend rotates BC clockwise relative to AB when viewed from
    # starboard.
    #
    # The world mathematical frame uses counterclockwise-positive angles.
    # Therefore a positive local elbow bend SUBTRACTS from the absolute
    # world angle established by the shoulder:
    #
    #     BC_world_angle = theta1_rad - theta2_rad
    #
    absolute_elbow_angle = theta1_rad - theta2_rad

    # Wrist Joint C relative to Elbow Joint B
    x_c = x_b + L2_ELBOW_MM * math.cos(absolute_elbow_angle)
    z_c = z_b + L2_ELBOW_MM * math.sin(absolute_elbow_angle)

    # 4. Absolute world angle of Link 3 (Wrist/tool link)
    #
    # Wrist pitch remains a local joint angle referenced to its 90-degree
    # centered condition.  V82 retains this expression here; Step 12 wrist
    # counter-rotation will be revised separately to preserve the desired
    # absolute tool orientation under the corrected BC convention.
    absolute_wrist_angle = absolute_elbow_angle + (
        theta3_rad - math.pi / 2.0
    )
    # Gripper Tool Tip T relative to Wrist Joint C
    x_tip = x_c + L3_WRIST_MM * math.cos(absolute_wrist_angle)
    z_tip = z_c + L3_WRIST_MM * math.sin(absolute_wrist_angle)

    return (x_b, z_b), (x_c, z_c), (x_tip, z_tip)


# --- 2-DOF ANALYTICAL INVERSE KINEMATICS ENGINE ---
def solve_inverse_kinematics_2d(target_x, target_z):
    """Calculates required Shoulder (theta1) and Elbow (theta2) joint angles.

    theta1 and theta2 are returned as physical joint angles in radians matching
    the SSC-32 motor frame.
    """
    r_sq = target_x**2 + target_z**2
    r = math.sqrt(r_sq)

    # Reachability Check (Triangle Inequality)
    if r > (L1_SHOULDER_MM + L2_ELBOW_MM) or r < abs(
        L1_SHOULDER_MM - L2_ELBOW_MM
    ):
        return None, None

    # Law of Cosines for Interior Elbow Angle
    cos_theta2_int = (r_sq - L1_SHOULDER_MM**2 - L2_ELBOW_MM**2) / (
        2.0 * L1_SHOULDER_MM * L2_ELBOW_MM
    )
    cos_theta2_int = max(-1.0, min(1.0, cos_theta2_int))
    phi_elbow = math.acos(cos_theta2_int)

    # Base Angle to Target Vector + Angle Offset via Law of Cosines
    gamma = math.atan2(target_z, target_x)
    cos_alpha = (L1_SHOULDER_MM**2 + r_sq - L2_ELBOW_MM**2) / (
        2.0 * L1_SHOULDER_MM * r
    )
    cos_alpha = max(-1.0, min(1.0, cos_alpha))
    alpha = math.acos(cos_alpha)

    # V82 JOINT/WORLD FRAME TRANSFORM
    #
    # The Law of Cosines gives phi_elbow as the positive MAGNITUDE of the
    # local angle between AB and BC.
    #
    # V82 defines positive elbow bend as clockwise from AB when viewed from
    # starboard.  In the counterclockwise-positive world frame:
    #
    #     BC_world_angle = theta1_rad - theta2_rad
    #
    # For this physical branch of triangle A-B-C, AB lies alpha
    # counterclockwise from the radial vector AC.  Therefore:
    #
    #     shoulder_world = gamma + alpha
    #     elbow_bend     = phi_elbow
    #
    # These equations are the inverse counterpart of the V82 Forward
    # Kinematics relationship above.  They must remain paired.
    theta1_rad = gamma + alpha
    theta2_rad = phi_elbow

    return theta1_rad, theta2_rad


def build_calibrated_macro_packet(target_array, transit_ms):
    """Formats outbound serial strings by applying physical offsets to each joint."""
    packet_parts = []
    for ch in range(6):
        phys_pwm = get_physical_pwm(ch, target_array[ch])
        packet_parts.append(f"#{ch}P{phys_pwm}")
    return "".join(packet_parts) + f"T{transit_ms}\r"


# --- URDF COMPONENT DESCRIPTION PARSING ---
urdf_configuration = """<?xml version="1.0" ?>
<robot name="junction_arm">
    <controller_settings>
        <min_pulse_width>500</min_pulse_width>
        <max_pulse_width>2500</max_pulse_width>
        <center_pulse_width>1500</center_pulse_width>
    </controller_settings>
    <joint channel="0" name="Base"></joint>
    <joint channel="1" name="Shoulder"></joint>
    <joint channel="2" name="Elbow"></joint>
    <joint channel="3" name="Wrist"></joint>
    <joint channel="4" name="Gripper"></joint>
    <joint channel="5" name="Wrist Rot"></joint>
</robot>
"""

root = ET.fromstring(urdf_configuration)

# --- PORT CONFIGURATION ---
all_found_ports = serial.tools.list_ports.comports()
ports = [p for p in all_found_ports if "USB" in p.device.upper()]
ports = sorted(ports, key=lambda x: x.device)

# V83 permits operation with only the two required robot USB devices.
# Windows 7 / HyperTerminal is optional and may be disabled with N or X.
if len(ports) < 2:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

for idx, port in enumerate(ports):
    print(f"  [{idx}] {port.device}")

while True:
    try:
        win7_choice = input(
            "Enter index number for WINDOWS 7 (HyperTrm), or N/X for none: "
        ).strip()
        win7_reporting_enabled = win7_choice.upper() not in {"N", "X"}

        if win7_reporting_enabled:
            win7_idx = int(win7_choice)
            if win7_idx < 0 or win7_idx >= len(ports):
                print("[INVALID WINDOWS 7 INDEX] Try again.\n")
                continue
        else:
            win7_idx = None

        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if cokoino_idx < 0 or cokoino_idx >= len(ports):
            print("[INVALID COKOINO INDEX] Try again.\n")
            continue
        if win7_idx is not None and cokoino_idx == win7_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue

        used_indices = {cokoino_idx}
        if win7_idx is not None:
            used_indices.add(win7_idx)
        lynx_candidates = [i for i in range(len(ports)) if i not in used_indices]
        if len(lynx_candidates) != 1:
            print(
                "[PORT ASSIGNMENT ERROR] Exactly one unassigned USB port must "
                "remain for LynxMotion. Try again.\n"
            )
            continue

        lynx_idx = lynx_candidates[0]
        WIN7_PORT = ports[win7_idx].device if win7_idx is not None else None
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT = ports[lynx_idx].device
        break

    except ValueError:
        print("[INVALID] Try again.\n")


# --- DATA STATE MEMORY SPACE ---
HOME_TARGET = [1500, 1500, 500, 1500, 1500, 1500]

TUCK_TARGET = [1500, 1821, 1842, 2500, 500, 1500]

READY_TARGET = [1500, 1931, 1274, 1500, 500, 1500] # leaning back 

current_arm_positions = list(TUCK_TARGET)

LEFT_STEER_LIVE = False
RIGHT_STEER_LIVE = False

BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000
WRIST_STEP_SIZE = 25


def send_led_binary_pattern(ser_conn, count_val):
    if count_val == 0:
        ser_conn.write(b"LED:LOCK:0\n")
    else:
        hex_val = format(count_val, "X")
        ser_conn.write(f"LED:LOCK:{hex_val}\n".encode("utf-8"))


# --- V83 OPTIONAL WINDOWS 7 REPORTING GATEWAY ---
win7 = None

def report_win7(message):
    """Send diagnostic/reporting output to Windows 7 only when enabled.

    All Win7/HyperTerminal traffic must pass through this function.
    Disabling reporting must never alter robot calculations, safety checks,
    Cokoino communication, or LynxMotion commands.
    """
    if not win7_reporting_enabled or win7 is None:
        return
    if isinstance(message, str):
        message = message.encode("utf-8")
    win7.write(message)


try:
    if win7_reporting_enabled:
        win7 = serial.Serial(WIN7_PORT, BAUD_RATE, timeout=0.1)
    lynx = serial.Serial(LYNX_PORT, BAUD_RATE, timeout=0.5)
    cokoino = serial.Serial(COKOINO_PORT, BAUD_RATE, timeout=0.1)

    time.sleep(1)
    report_win7(b"\x1b[2J\x1b[H")  # Clear HyperTerminal Screen

    report_win7(b"==================================================\r\n")
    report_win7(b"Initializing Robot Junction Bridge V83...\r\n")
    report_win7(
        b"Phase 1 Forward & Inverse Kinematic Engines Active (Fully"
        b" Logged)\r\n"
    )
    report_win7(b"==================================================\r\n\r\n")

    system_state = 0
    binary_counter = 0
    last_processed_command = ""
    flash_state = False
    last_flash_time = time.time()

    report_win7(b"==================================================\r\n")
    report_win7(b"--- SYSTEM BOOT: STEP 0 (LOCKOUT SAFE MODE) ---\r\n")
    report_win7(b"==================================================\r\n\r\n")

    while True:
        current_time = time.time()

        # Step 0 Lockout Pulse Loop
        if system_state == 0:
            hex_val = format(binary_counter, "X")
            cokoino.write(f"LED:LOCK:{hex_val}\n".encode("utf-8"))
            binary_counter = (binary_counter + 1) % 16
            time.sleep(0.2)

        # Steps 6 & 7 Dynamic LED Flashing Manager
        elif system_state == 6 and LEFT_STEER_LIVE:
            if current_time - last_flash_time > 0.3:
                flash_state = not flash_state
                if flash_state:
                    send_led_binary_pattern(cokoino, 6)
                else:
                    cokoino.write(b"LED:LOCK:0\n")
                last_flash_time = current_time

        elif system_state == 7 and RIGHT_STEER_LIVE:
            if current_time - last_flash_time > 0.3:
                flash_state = not flash_state
                if flash_state:
                    send_led_binary_pattern(cokoino, 7)
                else:
                    cokoino.write(b"LED:LOCK:0\n")
                last_flash_time = current_time

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode("utf-8", errors="ignore").strip()

            if cmd and not cmd.startswith("LED:"):
                report_win7(f" [COKOINO -> BRDG]: {cmd}\r\n".encode("utf-8"))
                cmd_upper = cmd.upper()

                # --- STEP 1 INITIATION ---
                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        # Flush red lockout loop explicitly before setting Blue Bit 1
                        cokoino.write(b"LED:LOCK:0\n")
                        time.sleep(0.05)
                        send_led_binary_pattern(cokoino, 1)
                        report_win7(
                            b"[OPERATOR CARD] -> Step 1: START Detected."
                            b" System Online.\r\n\r\n"
                        )

                # --- RUNTIME CONTROLLER STATE ENGINE ---
                elif system_state >= 1:

                    # --- RUNTIME START RESET TO STEP 0 ---
                    if "START" in cmd_upper:
                        system_state = 0
                        binary_counter = 0
                        LEFT_STEER_LIVE = False
                        RIGHT_STEER_LIVE = False
                        last_processed_command = ""
                        cokoino.write(b"ANALOG:DISABLE\n")
                        cokoino.write(b"LED:LOCK:0\n")

                        report_win7(
                            b"[OPERATOR CARD] -> START Reset Detected. "
                            b"Returning to Step 0 Lockout Safe Mode.\r\n\r\n"
                        )
                        continue

                    # Real-time Analog Stream Processing
                    if cmd_upper.startswith("ANALOG:"):
                        parts = cmd_upper.split(":")
                        if len(parts) == 3:
                            axis = parts[1]
                            try:
                                val = int(parts[2])
                                offset = val - 128

                                if LEFT_STEER_LIVE:
                                    if axis == "LX":
                                        step = int(offset * 0.15)
                                        current_arm_positions[0] = (
                                            constraint_safety_clip(
                                                current_arm_positions[0] + step
                                            )
                                        )
                                    elif axis == "LY":
                                        step = int(offset * 0.15)
                                        current_arm_positions[1] = (
                                            constraint_safety_clip(
                                                current_arm_positions[1] + step
                                            )
                                        )

                                    p0 = get_physical_pwm(
                                        0, current_arm_positions[0]
                                    )
                                    p1 = get_physical_pwm(
                                        1, current_arm_positions[1]
                                    )
                                    motion_packet = f"#0P{p0}#1P{p1}T100\r"
                                    lynx.write(motion_packet.encode("utf-8"))
                                    report_win7(
                                        f" [TX -> LYNXMOTION]:"
                                        f" {motion_packet.strip()}\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                elif RIGHT_STEER_LIVE:
                                    if axis == "RY":
                                        step = int(offset * 0.15)
                                        current_arm_positions[2] = (
                                            constraint_safety_clip(
                                                current_arm_positions[2] - step
                                            )
                                        )

                                        p2 = get_physical_pwm(
                                            2, current_arm_positions[2]
                                        )
                                        motion_packet = f"#2P{p2}T100\r"
                                        lynx.write(
                                            motion_packet.encode("utf-8")
                                        )
                                        report_win7(
                                            f" [TX -> LYNXMOTION]:"
                                            f" {motion_packet.strip()}\r\n".encode(
                                                "utf-8"
                                            )
                                        )

                            except ValueError:
                                pass
                        continue

                    # --- STEP 6: L3 HANDSHAKE (BASE & SHOULDER TUNING) ---
                    elif (
                        "STICK CLICK LEFT" in cmd_upper
                        and "RELEASED" not in cmd_upper
                    ):
                        if LEFT_STEER_LIVE:
                            LEFT_STEER_LIVE = False
                            cokoino.write(b"ANALOG:DISABLE\n")
                            READY_TARGET = list(current_arm_positions)
                            send_led_binary_pattern(cokoino, 6)
                            report_win7(
                                b"[OPERATOR CARD] -> Step 6 Locked."
                                b" READY_TARGET Matrix Updated.\r\n"
                            )
                            report_win7(
                                f" -> Snapshot Committed to READY:"
                                f" {READY_TARGET}\r\n\r\n".encode("utf-8")
                            )
                        else:
                            LEFT_STEER_LIVE = True
                            RIGHT_STEER_LIVE = False
                            cokoino.write(b"ANALOG:ENABLE\n")
                            system_state = 6
                            report_win7(
                                b"[OPERATOR CARD] -> Step 6: Base & Shoulder"
                                b" Steering Live.\r\n\r\n"
                            )
                        continue

                    # --- STEP 7: R3 HANDSHAKE (ELBOW AXIS TUNING) ---
                    elif (
                        "STICK CLICK RIGHT" in cmd_upper
                        and "RELEASED" not in cmd_upper
                    ):
                        if RIGHT_STEER_LIVE:
                            RIGHT_STEER_LIVE = False
                            cokoino.write(b"ANALOG:DISABLE\n")
                            READY_TARGET = list(current_arm_positions)
                            send_led_binary_pattern(cokoino, 7)
                            report_win7(
                                b"[OPERATOR CARD] -> Step 7 Locked."
                                b" READY_TARGET Matrix Updated.\r\n"
                            )
                            report_win7(
                                f" -> Snapshot Committed to READY:"
                                f" {READY_TARGET}\r\n\r\n".encode("utf-8")
                            )
                        else:
                            RIGHT_STEER_LIVE = True
                            LEFT_STEER_LIVE = False
                            cokoino.write(b"ANALOG:ENABLE\n")
                            system_state = 7
                            report_win7(
                                b"[OPERATOR CARD] -> Step 7: Elbow Axis"
                                b" Steering Live.\r\n\r\n"
                            )
                        continue

                    # --- STEP 8: D-PAD WRIST FINE-TUNING ---
                    elif cmd_upper in [
                        "PAD UP",
                        "PAD DOWN",
                        "PAD LEFT",
                        "PAD RIGHT",
                    ]:
                        system_state = 8
                        send_led_binary_pattern(cokoino, 8)

                        if cmd_upper == "PAD UP":
                            current_arm_positions[3] = constraint_safety_clip(
                                current_arm_positions[3] + WRIST_STEP_SIZE
                            )
                        elif cmd_upper == "PAD DOWN":
                            current_arm_positions[3] = constraint_safety_clip(
                                current_arm_positions[3] - WRIST_STEP_SIZE
                            )
                        elif cmd_upper == "PAD LEFT":
                            current_arm_positions[5] = constraint_safety_clip(
                                current_arm_positions[5] - WRIST_STEP_SIZE
                            )
                        elif cmd_upper == "PAD RIGHT":
                            current_arm_positions[5] = constraint_safety_clip(
                                current_arm_positions[5] + WRIST_STEP_SIZE
                            )

                        READY_TARGET[3] = current_arm_positions[3]
                        READY_TARGET[5] = current_arm_positions[5]

                        p3 = get_physical_pwm(3, current_arm_positions[3])
                        p5 = get_physical_pwm(5, current_arm_positions[5])
                        motion_packet = f"#3P{p3}#5P{p5}T150\r"
                        lynx.write(motion_packet.encode("utf-8"))
                        report_win7(
                            f" [TX -> LYNXMOTION]:"
                            f" {motion_packet.strip()}\r\n".encode("utf-8")
                        )
                        report_win7(
                            f"[OPERATOR CARD] -> Step 8: Wrist Adjusted ->"
                            f" Pitch(S3): {current_arm_positions[3]} | Rot(S5):"
                            f" {current_arm_positions[5]}\r\n\r\n".encode(
                                "utf-8"
                            )
                        )
                        continue

                    # --- STEP 9: L2 (TOOL RETRACT -> READY TARGET) ---
                    elif "TOOL RETRACT" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "TOOL RETRACT":
                                last_processed_command = ""
                        elif last_processed_command != "TOOL RETRACT":
                            last_processed_command = "TOOL RETRACT"
                            system_state = 9
                            send_led_binary_pattern(cokoino, 9)
                            current_arm_positions = list(READY_TARGET)
                            macro_packet = build_calibrated_macro_packet(
                                READY_TARGET, TRANSIT_TIME_MS
                            )
                            lynx.write(macro_packet.encode("utf-8"))
                            report_win7(
                                f" [TX -> LYNXMOTION]:"
                                f" {macro_packet.strip()}\r\n".encode("utf-8")
                            )
                            report_win7(
                                b"[OPERATOR CARD] -> Step 9: Tool Retracted."
                                b" Returned cleanly to READY Target.\r\n\r\n"
                            )

                    # --- STEP 10: R1 (READY POSITION -> READY TARGET) ---
                    elif "READY POSITION" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "READY POSITION":
                                last_processed_command = ""
                        elif last_processed_command != "READY POSITION":
                            last_processed_command = "READY POSITION"
                            system_state = 10
                            send_led_binary_pattern(cokoino, 10)
                            current_arm_positions = list(READY_TARGET)
                            macro_packet = build_calibrated_macro_packet(
                                READY_TARGET, TRANSIT_TIME_MS
                            )
                            lynx.write(macro_packet.encode("utf-8"))
                            report_win7(
                                f" [TX -> LYNXMOTION]:"
                                f" {macro_packet.strip()}\r\n".encode("utf-8")
                            )
                            report_win7(
                                b"[OPERATOR CARD] -> Step 10: Executing"
                                b" transit to READY Target.\r\n\r\n"
                            )

                    # --- STEP 11: R2 (SYSTEM HOME -> HOME TARGET) ---
                    elif "SYSTEM HOME" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "SYSTEM HOME":
                                last_processed_command = ""
                        elif last_processed_command != "SYSTEM HOME":
                            last_processed_command = "SYSTEM HOME"
                            system_state = 11
                            send_led_binary_pattern(cokoino, 11)
                            current_arm_positions = list(HOME_TARGET)
                            macro_packet = build_calibrated_macro_packet(
                                HOME_TARGET, TRANSIT_TIME_MS
                            )
                            lynx.write(macro_packet.encode("utf-8"))
                            report_win7(
                                f" [TX -> LYNXMOTION]:"
                                f" {macro_packet.strip()}\r\n".encode("utf-8")
                            )
                            report_win7(
                                b"[OPERATOR CARD] -> Step 11: Executing"
                                b" transit to HOME Target.\r\n\r\n"
                            )

                    # --- STEP 12: L1 (V83 LIVE +50 MM GROUP MOVE) ---
                    elif "TOOL ADVANCE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "TOOL ADVANCE":
                                last_processed_command = ""

                        elif last_processed_command != "TOOL ADVANCE":
                            last_processed_command = "TOOL ADVANCE"
                            system_state = 12
                            send_led_binary_pattern(cokoino, 12)  # Hex 'C'

                            report_win7(
                                b"\r\n==================================================\r\n"
                            )
                            report_win7(
                                b"--- STEP 12: L1 TOOL ADVANCE (V83 LIVE GROUP MOVE) ---\r\n"
                            )
                            report_win7(
                                b"---  +50 MM ENDPOINT / LIVE MOTION AFTER AUDIT  ---\r\n"
                            )
                            report_win7(
                                b"==================================================\r\n"
                            )

                            # --------------------------------------------------
                            # 1. CAPTURE CURRENT PHYSICAL ARM GEOMETRY
                            # --------------------------------------------------

                            s1_phys_pwm = get_physical_pwm(
                                1, current_arm_positions[1]
                            )
                            s2_phys_pwm = get_physical_pwm(
                                2, current_arm_positions[2]
                            )
                            s3_phys_pwm = get_physical_pwm(
                                3, current_arm_positions[3]
                            )

                            th1_start = pwm_to_physical_radians(
                                1, s1_phys_pwm
                            )
                            th2_start = pwm_to_physical_radians(
                                2, s2_phys_pwm
                            )
                            th3_start = pwm_to_physical_radians(
                                3, s3_phys_pwm
                            )

                            (
                                (x_b0, z_b0),
                                (x_c0, z_c0),
                                (x_tip0, z_tip0),
                            ) = compute_full_kinematics(
                                th1_start,
                                th2_start,
                                th3_start,
                            )
                            start_reach_mm = math.hypot(x_c0, z_c0)

                            # Absolute orientation of Link 3 / tool.
                            # V81 will preserve this orientation by
                            # counter-rotating the wrist.
                            tool_angle_start = (
                                th1_start
                                - th2_start
                                + (th3_start - math.pi / 2.0)
                            )
                            report_win7(
                                f" TARGET PWMs     -> "
                                f"S1(Shoulder):{current_arm_positions[1]} | "
                                f"S2(Elbow):{current_arm_positions[2]} | "
                                f"S3(Wrist):{current_arm_positions[3]}\r\n".encode(
                                    "utf-8"
                                )
                            )

                            report_win7(
                                f" PHYSICAL PWMs   -> "
                                f"S1(Shoulder):{s1_phys_pwm} | "
                                f"S2(Elbow):{s2_phys_pwm} | "
                                f"S3(Wrist):{s3_phys_pwm}\r\n".encode(
                                    "utf-8"
                                )
                            )

                            report_win7(
                                f" PHYSICAL ANGLES -> "
                                f"S1:{th1_start:.4f} rad "
                                f"({math.degrees(th1_start):.1f}deg) | "
                                f"S2:{th2_start:.4f} rad "
                                f"({math.degrees(th2_start):.1f}deg) | "
                                f"S3:{th3_start:.4f} rad "
                                f"({math.degrees(th3_start):.1f}deg)\r\n".encode(
                                    "utf-8"
                                )
                            )

                            report_win7(
                                f" TOOL ABS ANGLE  -> "
                                f"{math.degrees(tool_angle_start):.1f}deg\r\n".encode(
                                    "utf-8"
                                )
                            )

                            report_win7(
                                f" PHYSICAL COORDS "
                                f"(relative to Pivot A (0,0)):\r\n"
                                f"    - Pivot A (Shoulder) : "
                                f"X:    0.0 mm | Z:    0.0 mm\r\n"
                                f"    - Pivot B (Elbow)    : "
                                f"X: {x_b0:+6.1f} mm | "
                                f"Z: {z_b0:+6.1f} mm\r\n"
                                f"    - Pivot C (Wrist)    : "
                                f"X: {x_c0:+6.1f} mm | "
                                f"Z: {z_c0:+6.1f} mm\r\n"
                                f"    - Tip T   (Tool)     : "
                                f"X: {x_tip0:+6.1f} mm | "
                                f"Z: {z_tip0:+6.1f} mm\r\n".encode(
                                    "utf-8"
                                )
                            )
                            report_win7(
                                f" OPERATIONAL REACH -> "
                                f"Current C radius:{start_reach_mm:.2f} mm | "
                                f"Limit:{MAX_WRIST_REACH_MM:.2f} mm\r\n".encode(
                                    "utf-8"
                                )
                            )
                            # --------------------------------------------------
                            # V82 L1 WRIST-PIVOT ALTITUDE REFERENCE
                            #
                            # Pivot C is the controlled point for L1.
                            # Its starting mathematical Z coordinate becomes the
                            # altitude that L1 must preserve while advancing C
                            # +50 mm in world X.
                            #
                            # NOTE:
                            # This Z coordinate is relative to Shoulder Pivot A
                            # at mathematical Z=0.  During the later live test,
                            # the operator may measure C above the physical deck.
                            # The absolute numbers need not share the same zero;
                            # the critical comparison is Delta-Z before/after L1.
                            # --------------------------------------------------

                            l1_target_x = x_c0 + 50.0
                            l1_target_z = z_c0

                            report_win7(
                                b"\r\n"
                                b"==================================================\r\n"
                            )
                            report_win7(
                                b" V82 L1 PIVOT-C ALTITUDE REFERENCE\r\n"
                            )
                            report_win7(
                                b"==================================================\r\n"
                            )
                            report_win7(
                                f" START C POSITION -> "
                                f"X:{x_c0:+.2f} mm | "
                                f"Z:{z_c0:+.2f} mm\r\n".encode("utf-8")
                            )
                            report_win7(
                                f" TARGET C POSITION -> "
                                f"X:{l1_target_x:+.2f} mm | "
                                f"Z:{l1_target_z:+.2f} mm\r\n".encode("utf-8")
                            )
                            report_win7(
                                b" L1 REQUIREMENT    -> "
                                b"Delta-X:+50.00 mm | Delta-Z:+0.00 mm\r\n"
                            )
                            report_win7(
                                f" ALTITUDE TO HOLD  -> "
                                f"C Z = {l1_target_z:+.2f} mm "
                                f"(relative to Pivot A)\r\n".encode("utf-8")
                            )
                            # --------------------------------------------------
                            # 2. V81 OPERATIONAL ENVELOPE + BASELINE IK SELF-CHECK
                            #
                            # Feed the CURRENT wrist coordinates back through
                            # inverse kinematics. A correct solver should
                            # reconstruct the current shoulder/elbow angles.
                            # --------------------------------------------------

                            report_win7(
                                b"--------------------------------------------------\r\n"
                            )
                            report_win7(
                                b" V83 BASELINE IK SELF-CHECK:\r\n"
                            )

                            audit_failed = False

                            # V81 policy check: the operator may establish READY anywhere
                            # appropriate, but L1 is permitted only inside the selected
                            # operational wrist-reach envelope.
                            if (
                                start_reach_mm
                                > MAX_WRIST_REACH_MM + REACH_POLICY_TOL_MM
                            ):
                                report_win7(
                                    f" [OPERATING ENVELOPE] REJECTED: "
                                    f"Current wrist radius "
                                    f"{start_reach_mm:.2f} mm exceeds "
                                    f"{MAX_WRIST_REACH_MM:.2f} mm limit.\r\n".encode(
                                        "utf-8"
                                    )
                                )
                                audit_failed = True

                            if not audit_failed:
                                check_th1, check_th2 = (
                                    solve_inverse_kinematics_2d(
                                        x_c0, z_c0
                                    )
                                )

                                if check_th1 is None or check_th2 is None:
                                    report_win7(
                                        b" [SELF-CHECK] REJECTED: "
                                        b"Current arm geometry could not be "
                                        b"reconstructed by IK.\r\n"
                                        )
                                    audit_failed = True

                                else:
                                    # Normalize shoulder angle into 0..360 degrees.
                                    # Example from TUCK:
                                    # -241.1 degrees becomes +118.9 degrees.
                                    check_th1 = check_th1 % (
                                        2.0 * math.pi
                                    )

                                    delta_th1_deg = (
                                        math.degrees(
                                            check_th1 - th1_start
                                        )
                                    )
                                    delta_th2_deg = (
                                        math.degrees(
                                            check_th2 - th2_start
                                        )
                                    )

                                    report_win7(
                                        f"   START ACTUAL -> "
                                        f"S1:{math.degrees(th1_start):.2f}deg | "
                                        f"S2:{math.degrees(th2_start):.2f}deg\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f"   IK REBUILD   -> "
                                        f"S1:{math.degrees(check_th1):.2f}deg | "
                                        f"S2:{math.degrees(check_th2):.2f}deg\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f"   ERROR        -> "
                                        f"dS1:{delta_th1_deg:+.3f}deg | "
                                        f"dS2:{delta_th2_deg:+.3f}deg\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    if (
                                        abs(delta_th1_deg) > 0.5
                                        or abs(delta_th2_deg) > 0.5
                                    ):
                                        report_win7(
                                            b" [SELF-CHECK] FAILED: "
                                            b"IK does not reproduce current geometry.\r\n"
                                        )
                                        audit_failed = True
                                    else:
                                        report_win7(
                                            b" [SELF-CHECK] PASSED: "
                                            b"IK reproduces current geometry.\r\n"
                                        )

                            # --------------------------------------------------
                            # 3. TEST COMPLETE +50 MM HORIZONTAL WRIST ADVANCE
                            #
                            # V81 validates five 10 mm mathematical waypoints
                            # to establish a safe +50 mm final endpoint.
                            # Physical movement occurs only after the full audit passes.
                            # 
                            # --------------------------------------------------

                            if not audit_failed:

                                report_win7(
                                    b"--------------------------------------------------\r\n"
                                )
                                report_win7(
                                    b" EVALUATING +50 MM (+X) ADVANCE "
                                    b"AT FIXED WRIST ALTITUDE Z:\r\n"
                                )
                                report_win7(
                                    b" WRIST COUNTER-ROTATION ENABLED "
                                    b"(CALCULATION ONLY):\r\n"
                                )

                                dz_tolerance_mm = 1.0

                                # Existing program intentionally uses S3=2500
                                # in TUCK, so V81 permits the established
                                # 500..2500 physical wrist range.
                                WRIST_PWM_MIN = 500
                                WRIST_PWM_MAX = 2500

                                for step_i in range(1, 6):

                                    req_x_c = (
                                        x_c0 + step_i * 10.0
                                    )
                                    req_z_c = z_c0

                                    req_reach_mm = math.hypot(
                                        req_x_c,
                                        req_z_c,
                                    )

                                    # V81 operational envelope check.
                                    if (
                                        req_reach_mm
                                        > MAX_WRIST_REACH_MM + REACH_POLICY_TOL_MM
                                    ):
                                        report_win7(
                                            f"\r\n [CALC STEP "
                                            f"{step_i}/5 "
                                            f"(s=+{step_i}cm)] "
                                            f"REJECTED: "
                                            f"Outside Operational Reach Envelope "
                                            f"(Radius:{req_reach_mm:.2f}mm, "
                                            f"Limit:{MAX_WRIST_REACH_MM:.2f}mm)\r\n".encode(
                                                "utf-8"
                                            )
                                        )
                                        audit_failed = True
                                        break

                                    ik_th1, ik_th2 = (
                                        solve_inverse_kinematics_2d(
                                            req_x_c,
                                            req_z_c,
                                        )
                                    )

                                    if (
                                        ik_th1 is None
                                        or ik_th2 is None
                                    ):
                                        report_win7(
                                            f"\r\n [CALC STEP "
                                            f"{step_i}/5 "
                                            f"(s=+{step_i}cm)] "
                                            f"REJECTED: "
                                            f"Geometric Singularity / "
                                            f"Out of Reach "
                                            f"(Target X:"
                                            f"{req_x_c:.1f}mm, "
                                            f"Z:{req_z_c:.1f}mm)\r\n".encode(
                                                "utf-8"
                                            )
                                        )
                                        audit_failed = True
                                        break

                                    # Normalize shoulder angle into the
                                    # physical positive representation.
                                    ik_th1 = ik_th1 % (
                                        2.0 * math.pi
                                    )
                                    # V82 WRIST COUNTER-ROTATION:
                                    # Preserve the starting absolute tool orientation while Shoulder and
                                    # Elbow change during L1.
                                    #
                                    # V82 world geometry:
                                    #
                                    #     tool_world = shoulder - elbow + (wrist - 90 deg)
                                    #
                                    # Solving for wrist:
                                    #
                                    #     wrist = tool_world - shoulder + elbow + 90 deg
                                    #
                                    # This sign convention must remain paired with the V82 Forward
                                    # Kinematics relationship BC_world = shoulder - elbow.
                                    # Counter-rotate wrist so Link 3 / tool
                                    # maintains its original absolute angle.
                                    ik_th3 = (
                                        tool_angle_start
                                        - ik_th1
                                        + ik_th2
                                        + math.pi / 2.0
                                    )
                                    s1_req_pwm = (
                                        physical_radians_to_pwm(
                                            1, ik_th1
                                        )
                                    )
                                    s2_req_pwm = (
                                        physical_radians_to_pwm(
                                            2, ik_th2
                                        )
                                    )
                                    s3_req_pwm = (
                                        physical_radians_to_pwm(
                                            3, ik_th3
                                        )
                                    )

                                    (
                                        (x_b_cur, z_b_cur),
                                        (x_c_cur, z_c_cur),
                                        (x_tip_cur, z_tip_cur),
                                    ) = compute_full_kinematics(
                                        ik_th1,
                                        ik_th2,
                                        ik_th3,
                                    )

                                    dx_c = x_c_cur - x_c0
                                    dz_c = z_c_cur - z_c0
                                    dx_tip = x_tip_cur - x_tip0
                                    dz_tip = z_tip_cur - z_tip0

                                    # Verify requested geometry and
                                    # servo envelopes.
                                    if (
                                        not (
                                            SAFE_PWM_MIN
                                            <= s1_req_pwm
                                            <= SAFE_PWM_MAX
                                        )
                                        or not (
                                            SAFE_PWM_MIN
                                            <= s2_req_pwm
                                            <= SAFE_PWM_MAX
                                        )
                                        or not (
                                            WRIST_PWM_MIN
                                            <= s3_req_pwm
                                            <= WRIST_PWM_MAX
                                        )
                                        or abs(dz_c)
                                        > dz_tolerance_mm
                                        or abs(dz_tip)
                                        > dz_tolerance_mm
                                    ):
                                        report_win7(
                                            f"\r\n [CALC STEP "
                                            f"{step_i}/5 "
                                            f"(s=+{step_i}cm)] "
                                            f"REJECTED: "
                                            f"Trajectory or PWM "
                                            f"Boundary Breach!\r\n"
                                            f"   S1:{s1_req_pwm} | "
                                            f"S2:{s2_req_pwm} | "
                                            f"S3:{s3_req_pwm} | "
                                            f"dZ(C):{dz_c:+.2f}mm | "
                                            f"dZ(Tip):"
                                            f"{dz_tip:+.2f}mm\r\n".encode(
                                                "utf-8"
                                            )
                                        )
                                        audit_failed = True
                                        break

                                    deg1 = math.degrees(
                                        ik_th1
                                    )
                                    deg2 = math.degrees(
                                        ik_th2
                                    )
                                    deg3 = math.degrees(
                                        ik_th3
                                    )

                                    report_win7(
                                        f"\r\n [CALC STEP "
                                        f"{step_i}/5 "
                                        f"(s=+{step_i}cm)] "
                                        f"VALID\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f"   ANGLES -> "
                                        f"S1:{deg1:.2f}deg | "
                                        f"S2:{deg2:.2f}deg | "
                                        f"S3:{deg3:.2f}deg\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f"   PWMs   -> "
                                        f"S1:{s1_req_pwm} | "
                                        f"S2:{s2_req_pwm} | "
                                        f"S3:{s3_req_pwm}\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f"   Pivot B -> "
                                        f"X:{x_b_cur:+7.2f} mm | "
                                        f"Z:{z_b_cur:+7.2f} mm\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f"   Pivot C -> "
                                        f"X:{x_c_cur:+7.2f} mm | "
                                        f"Z:{z_c_cur:+7.2f} mm | "
                                        f"dX:{dx_c:+6.2f} mm | "
                                        f"dZ:{dz_c:+6.2f} mm\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f"   Tool T  -> "
                                        f"X:{x_tip_cur:+7.2f} mm | "
                                        f"Z:{z_tip_cur:+7.2f} mm | "
                                        f"dX:{dx_tip:+6.2f} mm | "
                                        f"dZ:{dz_tip:+6.2f} mm\r\n".encode(
                                            "utf-8"
                                        )
                                    )
                                # ----------------------------------------------
                                # V82 FINAL PIVOT-C ALTITUDE AUDIT
                                #
                                # The fifth mathematical waypoint is the full
                                # +50 mm L1 endpoint.  Report its Z altitude
                                # separately and conspicuously before any
                                # physical command can be authorized.
                                # ----------------------------------------------

                                if not audit_failed:

                                    final_z_error = z_c_cur - l1_target_z
                                    final_x_error = x_c_cur - l1_target_x

                                    report_win7(
                                        b"\r\n"
                                        b"==================================================\r\n"
                                    )
                                    report_win7(
                                        b" V82 FINAL L1 ALTITUDE AUDIT\r\n"
                                    )
                                    report_win7(
                                        b"==================================================\r\n"
                                    )

                                    report_win7(
                                        f" C STARTING Z          -> "
                                        f"{z_c0:+.3f} mm\r\n".encode("utf-8")
                                    )

                                    report_win7(
                                        f" C REQUESTED Z         -> "
                                        f"{l1_target_z:+.3f} mm\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f" C CALCULATED FINAL Z  -> "
                                        f"{z_c_cur:+.3f} mm\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f" C CALCULATED DELTA-Z  -> "
                                        f"{dz_c:+.3f} mm\r\n".encode("utf-8")
                                    )

                                    report_win7(
                                        f" C FINAL Z ERROR       -> "
                                        f"{final_z_error:+.6f} mm\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        b"--------------------------------------------------\r\n"
                                    )

                                    report_win7(
                                        f" C REQUESTED DELTA-X   -> "
                                        f"+50.000 mm\r\n"
                                        f" C CALCULATED DELTA-X  -> "
                                        f"{dx_c:+.3f} mm\r\n"
                                        f" C FINAL X ERROR       -> "
                                        f"{final_x_error:+.6f} mm\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    if abs(final_z_error) <= dz_tolerance_mm:
                                        report_win7(
                                            b" [ALTITUDE AUDIT] PASSED: "
                                            b"Calculated Pivot C altitude "
                                            b"is maintained.\r\n"
                                        )
                                    else:
                                        report_win7(
                                            b" [ALTITUDE AUDIT] FAILED: "
                                            b"Calculated Pivot C altitude "
                                            b"is not maintained.\r\n"
                                        )
                                        audit_failed = True

                                    report_win7(
                                        b"==================================================\r\n"
                                    )
                                # ------------------------------------------
                                # 4. V83 SSC-32U GROUP-MOVE PATH PREDICTION
                                #
                                # The five Cartesian waypoints above validate the
                                # requested +50 mm endpoint.  V81 now asks a
                                # different question:
                                #
                                # If SSC-32U moves directly from the starting
                                # S1/S2/S3 pulse widths to the final +50 mm pulse
                                # widths as one synchronized group move, what
                                # Cartesian path would Pivot C and Tool T follow?
                                #
                                # NO COMMAND IS SENT DURING THIS PREDICTION PHASE.
                                # --------------------------------------------------

                                if not audit_failed:

                                    report_win7(
                                        b"\r\n"
                                        b"==================================================\r\n"
                                    )
                                    report_win7(
                                        b" V83 SSC-32U GROUP-MOVE PATH PREDICTION\r\n"
                                    )
                                    report_win7(
                                        b" PATH PREDICTION ONLY - LIVE MOVE PENDING FINAL AUDIT\r\n"
                                    )
                                    report_win7(
                                        b"==================================================\r\n"
                                    )

                                    report_win7(
                                        f" START PHYSICAL PWM -> "
                                        f"S1:{s1_phys_pwm} | "
                                        f"S2:{s2_phys_pwm} | "
                                        f"S3:{s3_phys_pwm}\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f" FINAL PHYSICAL PWM -> "
                                        f"S1:{s1_req_pwm} | "
                                        f"S2:{s2_req_pwm} | "
                                        f"S3:{s3_req_pwm}\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        b"\r\n"
                                        b" Percent |"
                                        b"      X(C) |"
                                        b"      Z(C) |"
                                        b"     dZ(C) |"
                                        b"   dZ(Tip)\r\n"
                                    )
                                    report_win7(
                                        b"---------+-----------+-----------+-----------+----------\r\n"
                                    )

                                    max_positive_dz_c = 0.0
                                    max_negative_dz_c = 0.0
                                    max_abs_dz_c = 0.0
                                    max_abs_dz_tip = 0.0

                                    # 21 samples: 0%, 5%, 10% ... 100%.
                                    for sample_i in range(21):

                                        fraction = sample_i / 20.0
                                        percent = sample_i * 5

                                        # Approximate SSC-32U synchronized group
                                        # interpolation by linearly interpolating
                                        # each physical servo pulse width between
                                        # the common start and finish positions.
                                        sim_s1_pwm = (
                                            s1_phys_pwm
                                            + fraction
                                            * (
                                                s1_req_pwm
                                                - s1_phys_pwm
                                            )
                                        )
                                        sim_s2_pwm = (
                                            s2_phys_pwm
                                            + fraction
                                            * (
                                                s2_req_pwm
                                                - s2_phys_pwm
                                            )
                                        )
                                        sim_s3_pwm = (
                                            s3_phys_pwm
                                            + fraction
                                            * (
                                                s3_req_pwm
                                                - s3_phys_pwm
                                            )
                                        )

                                        sim_th1 = pwm_to_physical_radians(
                                            1, sim_s1_pwm
                                        )
                                        sim_th2 = pwm_to_physical_radians(
                                            2, sim_s2_pwm
                                        )
                                        sim_th3 = pwm_to_physical_radians(
                                            3, sim_s3_pwm
                                        )

                                        (
                                            (sim_x_b, sim_z_b),
                                            (sim_x_c, sim_z_c),
                                            (sim_x_tip, sim_z_tip),
                                        ) = compute_full_kinematics(
                                            sim_th1,
                                            sim_th2,
                                            sim_th3,
                                        )

                                        sim_dz_c = sim_z_c - z_c0
                                        sim_dz_tip = (
                                            sim_z_tip - z_tip0
                                        )

                                        max_positive_dz_c = max(
                                            max_positive_dz_c,
                                            sim_dz_c,
                                        )
                                        max_negative_dz_c = min(
                                            max_negative_dz_c,
                                            sim_dz_c,
                                        )
                                        max_abs_dz_c = max(
                                            max_abs_dz_c,
                                            abs(sim_dz_c),
                                        )
                                        max_abs_dz_tip = max(
                                            max_abs_dz_tip,
                                            abs(sim_dz_tip),
                                        )

                                        report_win7(
                                            f" {percent:3d}%    | "
                                            f"{sim_x_c:+8.2f} | "
                                            f"{sim_z_c:+8.2f} | "
                                            f"{sim_dz_c:+8.2f} | "
                                            f"{sim_dz_tip:+8.2f}\r\n".encode(
                                                "utf-8"
                                            )
                                        )

                                    report_win7(
                                        b"--------------------------------------------------\r\n"
                                    )

                                    report_win7(
                                        f" PREDICTED PIVOT C Z RANGE -> "
                                        f"{max_negative_dz_c:+.2f} mm to "
                                        f"{max_positive_dz_c:+.2f} mm\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f" MAXIMUM ABS PIVOT C Z EXCURSION -> "
                                        f"{max_abs_dz_c:.2f} mm\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        f" MAXIMUM ABS TOOL-TIP Z EXCURSION -> "
                                        f"{max_abs_dz_tip:.2f} mm\r\n".encode(
                                            "utf-8"
                                        )
                                    )

                                    report_win7(
                                        b" [V83 AUDIT] "
                                        b"All calculations complete; live command pending.\r\n"
                                    )
                                    report_win7(
                                        b" [V83 AUDIT] "
                                        b"PATH PREDICTION COMPLETE - LIVE COMMAND NOT YET SENT.\r\n"
                                    )
                            # V81 code added after for oop and inside if not audit failed
                            # --------------------------------------------------
                            # 5. FINAL V81 AUDIT RESULT
                            # --------------------------------------------------

                            if audit_failed:
                                cokoino.write(
                                    b"ERROR:OUT_OF_BOUNDS\n"
                                )
                                report_win7(
                                    b"\r\n[OPERATOR CARD] -> "
                                    b"Step 12 V83 GROUP-MOVE Audit "
                                    b"REJECTED "
                                    b"(Boundary Alarm Sent to Cokoino).\r\n\r\n"
                                )
                            else:
                                report_win7(
                                    b"\r\n[OPERATOR CARD] -> "
                                    b"Step 12 V83 GROUP-MOVE AUDIT PASSED.\r\n"
                                )
                                report_win7(
                                    b"+50 mm endpoint is mathematically valid "
                                    b"and the predicted SSC-32U path has been "
                                    b"reported above.\r\n"
                                )

                                # ----------------------------------------------
                                # V81 LIVE +50 MM GROUP MOVE
                                #
                                # All geometry, operational-envelope, PWM,
                                # constant-Z, and predicted-path checks have
                                # passed before execution reaches this point.
                                #
                                # s1_req_pwm, s2_req_pwm, and s3_req_pwm are
                                # PHYSICAL PWM values.  Send them directly.
                                # Do NOT apply trim again.
                                # ----------------------------------------------

                                motion_packet = (
                                    f"#1P{s1_req_pwm}"
                                    f"#2P{s2_req_pwm}"
                                    f"#3P{s3_req_pwm}"
                                    f"T{TRANSIT_TIME_MS}\r"
                                )

                                report_win7(
                                    b"\r\n==================================================\r\n"
                                )
                                report_win7(
                                    b" V83 LIVE L1 GROUP MOVE AUTHORIZED\r\n"
                                )
                                report_win7(
                                    b"==================================================\r\n"
                                )
                                report_win7(
                                    f" [TX -> LYNXMOTION]: "
                                    f"{motion_packet.strip()}\r\n".encode(
                                        "utf-8"
                                    )
                                )

                                lynx.write(
                                    motion_packet.encode("utf-8")
                                )

                                # Convert the physical endpoint back into the
                                # program's nominal/reference PWM system.
                                # READY_TARGET remains unchanged so L2 can
                                # return the arm to the stored READY position.
                                current_arm_positions[1] = get_nominal_pwm(
                                    1, s1_req_pwm
                                )
                                current_arm_positions[2] = get_nominal_pwm(
                                    2, s2_req_pwm
                                )
                                current_arm_positions[3] = get_nominal_pwm(
                                    3, s3_req_pwm
                                )

                                report_win7(
                                    b"[V83 LIVE] One synchronized +50 mm "
                                    b"group-move command has been sent.\r\n"
                                )
                                report_win7(
                                    b"[V83 LIVE] No Q or VER query will be "
                                    b"sent during the movement.\r\n\r\n"
                                )

                    # --- OTHER SEQUENCED BUTTON EXECUTIONS ---

                    # STEP 2: CROSS -> Query LynxMotion VER
                    if "CROSS" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "CROSS":
                                last_processed_command = ""
                        elif last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            system_state = 2
                            send_led_binary_pattern(cokoino, 2)

                            lynx.write(b"VER\r")
                            report_win7(b" [TX -> LYNXMOTION]: VER\r\n")

                            time.sleep(0.1)
                            ver_response = ""
                            if lynx.in_waiting > 0:
                                ver_response = (
                                    lynx.readline()
                                    .decode("utf-8", errors="ignore")
                                    .strip()
                                )

                            if ver_response:
                                report_win7(
                                    f" [LYNXMOTION -> BRDG]:"
                                    f" {ver_response}\r\n".encode("utf-8")
                                )
                                report_win7(
                                    b"[OPERATOR CARD] -> Step 2: Firmware"
                                    b" Version Verified Successfully.\r\n\r\n"
                                )
                            else:
                                report_win7(
                                    b"[OPERATOR CARD] -> Step 2: VER Query"
                                    b" Sent (No direct response"
                                    b" received).\r\n\r\n"
                                )

                    # STEP 3: CIRCLE -> Travel to TUCK Position
                    elif "CIRCLE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "CIRCLE":
                                last_processed_command = ""
                        elif last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            system_state = 3
                            send_led_binary_pattern(cokoino, 3)
                            current_arm_positions = list(TUCK_TARGET)
                            macro_packet = build_calibrated_macro_packet(
                                TUCK_TARGET, TRANSIT_TIME_MS
                            )
                            lynx.write(macro_packet.encode("utf-8"))
                            report_win7(
                                f" [TX -> LYNXMOTION]:"
                                f" {macro_packet.strip()}\r\n".encode("utf-8")
                            )
                            report_win7(
                                b"[OPERATOR CARD] -> Step 3: Traveling cleanly"
                                b" to TUCK configuration.\r\n\r\n"
                            )

                    # STEP 4: TRIANGLE -> Travel to HOME Configuration
                    elif "TRIANGLE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "TRIANGLE":
                                last_processed_command = ""
                        elif last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            system_state = 4
                            send_led_binary_pattern(cokoino, 4)
                            current_arm_positions = list(HOME_TARGET)
                            macro_packet = build_calibrated_macro_packet(
                                HOME_TARGET, TRANSIT_TIME_MS
                            )
                            lynx.write(macro_packet.encode("utf-8"))
                            report_win7(
                                f" [TX -> LYNXMOTION]:"
                                f" {macro_packet.strip()}\r\n".encode("utf-8")
                            )
                            report_win7(
                                b"[OPERATOR CARD] -> Step 4: Traveling cleanly"
                                b" to HOME configuration.\r\n\r\n"
                            )

                    # STEP 5: SQUARE -> Travel to READY Configuration
                    elif "SQUARE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "SQUARE":
                                last_processed_command = ""
                        elif last_processed_command != "SQUARE":
                            last_processed_command = "SQUARE"
                            system_state = 5
                            send_led_binary_pattern(cokoino, 5)
                            current_arm_positions = list(READY_TARGET)
                            macro_packet = build_calibrated_macro_packet(
                                READY_TARGET, TRANSIT_TIME_MS
                            )
                            lynx.write(macro_packet.encode("utf-8"))
                            report_win7(
                                f" [TX -> LYNXMOTION]:"
                                f" {macro_packet.strip()}\r\n".encode("utf-8")
                            )
                            report_win7(
                                b"[OPERATOR CARD] -> Step 5: Traveling to READY"
                                b" configuration.\r\n\r\n"
                            )

        time.sleep(0.01)

except KeyboardInterrupt:
    print("\nBridge safely terminated.")
except Exception as e:
    print(f"\nFatal Runtime Intercept: {e}")

(th)

Offline

Like button can go here

Board footer

Powered by FluxBB