Debug: Database connection successful Python Computer Language (Page 5) / 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.

#101 2026-06-29 21:08:31

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

Re: Python Computer Language

This post contains Version 32 of a Python program to interface Cokoino to LynxMotion.

This version employs ASCII strings coming from Version 17 of Cokoino sketch.

# bridgeV32.py Prepared by Gemini Supervised by Tom Hanson

import serial
import serial.tools.list_ports
import time
import math

print("==================================================")
print("Initializing Robot Junction Bridge V32...")
print("Aligned with Cokoino V17 Serial Protocol")
print("==================================================\n")

# --- PHYSICAL ROBOT ARM PARAMETERS (Limb Dimensions in mm) ---
L1 = 147.0
L2 = 187.5

def pulse_to_radians(pulse):
    degrees = (pulse - 1500) / 11.11
    return math.radians(degrees + 90.0)

def radians_to_pulse(rad):
    degrees = math.degrees(rad) - 90.0
    return int(1500 + (degrees * 11.11))

def solve_ik(x, z):
    d = math.sqrt(x*x + z*z)
    if d > (L1 + L2) or d < abs(L1 - L2):
        raise ValueError("Target coordinate mathematically out of physical reach.")
        
    cos_angle_2 = (d*d - L1*L1 - L2*L2) / (2.0 * L1 * L2)
    cos_angle_2 = max(-1.0, min(1.0, cos_angle_2))
    angle_2 = math.acos(cos_angle_2)
    
    angle_1 = math.atan2(z, x) + math.acos((L1*L1 + d*d - L2*L2) / (2.0 * L1 * d))
    angle_3 = math.pi - angle_1 - angle_2
    
    return radians_to_pulse(angle_1), radians_to_pulse(angle_2), radians_to_pulse(angle_3)

# --- STEP 1: SCAN & FILTER FOR PHYSICAL USB DEVICES ---
all_found_ports = serial.tools.list_ports.comports()
ports = []
for p in all_found_ports:
    if "USB" in p.device.upper():
        ports.append(p)

ports = sorted(ports, key=lambda x: x.device)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

print("Detected Physical USB Serial Devices:")
for idx, port in enumerate(ports):
    print(f"  [{idx}] {port.device} - {port.description}")
print("")

# --- STEP 2: INTERACTIVE MENU WITH AUTO-DEDUCTION ---
all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        if win7_idx not in all_indices: continue
            
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if cokoino_idx not in all_indices: continue
            
        if win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
            
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break 
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 3: ROBOT CONTEXT QUESTIONNAIRE ---
current_arm_positions = [1500, 1500, 1500, 1500, 1500, 1500]

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

dynamic_ready_positions = list(HOME_TARGET)
is_ready_state_set = False

print("\n==================================================")
print("        ROBOT ARM POSITION INITIALIZATION         ")
print("==================================================")
print("Where is the physical arm positioned right now?")
print("  [T] Secured in TUCK position (Taped Plate Numbers)")
print("  [H] Standing upright at HOME position (All 1500s)")
print("  [O] OTHER (Manual eyeball estimates)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        print("\n[SUCCESS] Loaded verified TUCK coordinates into memory.")
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        print("\n[SUCCESS] Loaded standard HOME coordinates into memory.")
        break
    elif choice == 'O':
        print("\nEnter custom pulse width estimates (500-2500):")
        names = ["0 (Base)", "1 (Shoulder)", "2 (Elbow)", "3 (Wrist)", "4 (Gripper)", "5 (Wrist Rot)"]
        for i in range(6):
            while True:
                try:
                    val = int(input(f"  Servo {names[i]} position [Default 1500]: ") or 1500)
                    if 500 <= val <= 2500:
                        current_arm_positions[i] = val
                        break
                    print("    [OUT OF BOUNDS] Keep value between 500 and 2500.")
                except ValueError:
                    print("    [INVALID] Please enter an integer number.")
        break

print(f"\nFinalized Startup Vector: {current_arm_positions}\n")

# --- STEP 4: RUNTIME ENGAGEMENT ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H")
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"--- Physical Cokoino NeoPixels: Looping Red   ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                if cmd != last_processed_command:
                    last_processed_command = ""

                if "RELEASE" in cmd_upper:
                    last_processed_command = ""
                    continue

                # STATE 0: Safe Handshake Look for "START"
                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        win7.write(b"[!!!] START DETECTED [!!!]\r\n")
                        win7.write(b"--- COKOINO READY FOR PS/2 COMMANDS ---\r\n\r\n")
                
                # STATE 1: Unlocked Commands
                elif system_state == 1:
                    
                    # CROSS (Diagnostics)
                    if "CROSS" in cmd_upper:
                        if last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Exact String: 'VER\\r'\r\n")
                            lynx.reset_input_buffer()
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                if response:
                                    win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))
                                    cokoino.write(b"LED:VER:SUCCESS\n")
                                else:
                                    cokoino.write(b"LED:VER:FAULT\n")
                            else:
                                cokoino.write(b"LED:VER:FAULT\n")
                    
                    # TRIANGLE (Home Macro)
                    elif "TRIANGLE" in cmd_upper:
                        if last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Dispatching Synchronized Home Macro:\r\n")
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = HOME_TARGET[joint]
                                macro_packet += f"#{joint}P{HOME_TARGET[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # CIRCLE (Tuck Macro)
                    elif "CIRCLE" in cmd_upper:
                        if last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Dispatching Synchronized Tuck Macro:\r\n")
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = TUCK_TARGET[joint]
                                macro_packet += f"#{joint}P{TUCK_TARGET[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SELECT/RESET (Memory Lock for Ready State)
                    elif "SELECT" in cmd_upper:
                        if last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            dynamic_ready_positions = list(current_arm_positions)
                            is_ready_state_set = True
                            win7.write(b"==================================================\r\n")
                            win7.write(b"[MEMORY LOCK] Dynamic READY Position Established!\r\n")
                            win7.write(f"Captured Hardware Footprint: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")
                            cokoino.write(b"LED:SET:SUCCESS\n")

                    # ALIGNED MATCH: L1 ADVANCE THRUST
                    elif "TOOL ADVANCE" in cmd_upper:
                        if last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] Cannot thrust. Operator must press SELECT first to lock baseline.\r\n\r\n")
                                continue
                                
                            try:
                                win7.write(b"[INVERSE KINEMATICS] Computing 3cm Linear Path...\r\n")
                                th1_start = pulse_to_radians(dynamic_ready_positions[1])
                                th2_start = pulse_to_radians(dynamic_ready_positions[2])
                                
                                x_start = L1 * math.cos(th1_start) + L2 * math.cos(th1_start + th2_start)
                                z_start = L1 * math.sin(th1_start) + L2 * math.sin(th1_start + th2_start)
                                
                                win7.write(f"  Calculated Start Plane: X={x_start:.1f}mm, Z={z_start:.1f}mm\r\n".encode('utf-8'))
                                win7.write(b"  Executing Damped Incremental Forward Push:\r\n")
                                
                                for step in [10.0, 20.0, 30.0]:
                                    target_x = x_start + step
                                    target_z = z_start
                                    
                                    p1, p2, p3 = solve_ik(target_x, target_z)
                                    
                                    current_arm_positions[1] = p1
                                    current_arm_positions[2] = p2
                                    current_arm_positions[3] = p3
                                    
                                    packet = f"#1P{p1}#2P{p2}#3P{p3}T1000\r"
                                    lynx.write(packet.encode('utf-8'))
                                    win7.write(f"    Segment {step/10:.0f}cm -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                    
                                    time.sleep(1.0)
                                win7.write(b"[THRUST COMPLETE]\r\n\r\n")
                            except Exception as math_err:
                                win7.write(f"[MATH ERROR] Trajectory boundary reached: {math_err}\r\n\r\n".encode('utf-8'))

                    # ALIGNED MATCH: L2 RETRACT TO READY
                    elif "TOOL RETRACT" in cmd_upper:
                        if last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Retracting Linear Tool Head back to Ready Matrix:\r\n")
                            
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = dynamic_ready_positions[joint]
                                macro_packet += f"#{joint}P{dynamic_ready_positions[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

In this version we are testing new code to perform a linear motion in X.

This movement requires complex calculation of linked physical parts of the arm.

(th)

Offline

Like button can go here

#102 2026-06-30 07:02:04

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

Re: Python Computer Language

This post contains Version 33 of a Python program to interface between Cokoino LKK0017 and LynxMotion robot arm. In this version, we attempt to address the issue of direction when we move the arms. In addition, we restore the limit on values that can be transmitted. LynxMotion servos are limited to values of 500 to 2500. Finally, we protect the gripper, which must not change once a tool has been mounted.

# bridgeV33.py Created by Gemini Supervised by Tom Hanson

import serial
import serial.tools.list_ports
import time
import math

print("==================================================")
print("Initializing Robot Junction Bridge V33...")
print("Directional Awareness & Pulse Boundary Safety Active")
print("==================================================\n")

# --- PHYSICAL ROBOT ARM PARAMETERS (Limb Dimensions in mm) ---
L1 = 147.0
L2 = 187.5

def pulse_to_radians(pulse):
    degrees = (pulse - 1500) / 11.11
    return math.radians(degrees + 90.0)

def radians_to_pulse(rad):
    degrees = math.degrees(rad) - 90.0
    return int(1500 + (degrees * 11.11))

def constraint_safety_clip(pulse):
    """Enforces strict LynxMotion hardware boundaries (500 to 2500)."""
    return max(500, min(2500, pulse))

def solve_ik(x, z):
    d = math.sqrt(x*x + z*z)
    if d > (L1 + L2) or d < abs(L1 - L2):
        raise ValueError("Target coordinate mathematically out of physical reach.")
        
    cos_angle_2 = (d*d - L1*L1 - L2*L2) / (2.0 * L1 * L2)
    cos_angle_2 = max(-1.0, min(1.0, cos_angle_2))
    angle_2 = math.acos(cos_angle_2)
    
    angle_1 = math.atan2(z, x) + math.acos((L1*L1 + d*d - L2*L2) / (2.0 * L1 * d))
    angle_3 = math.pi - angle_1 - angle_2
    
    # Raw trigonometric conversions
    raw_p1 = radians_to_pulse(angle_1)
    p2 = constraint_safety_clip(radians_to_pulse(angle_2))
    p3 = constraint_safety_clip(radians_to_pulse(angle_3))
    
    # DIRECTIONAL AWARENESS FIX: 
    # If the math wants to go above 1500 to reach forward, invert it so the pulse 
    # decreases below 1500 instead, driving the shoulder "North".
    delta_p1 = raw_p1 - 1500
    p1 = constraint_safety_clip(1500 - delta_p1)
    
    return p1, p2, p3

# --- STEP 1: SCAN & FILTER FOR PHYSICAL USB DEVICES ---
all_found_ports = serial.tools.list_ports.comports()
ports = []
for p in all_found_ports:
    if "USB" in p.device.upper():
        ports.append(p)

ports = sorted(ports, key=lambda x: x.device)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

print("Detected Physical USB Serial Devices:")
for idx, port in enumerate(ports):
    print(f"  [{idx}] {port.device} - {port.description}")
print("")

# --- STEP 2: INTERACTIVE MENU WITH AUTO-DEDUCTION ---
all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        if win7_idx not in all_indices: continue
            
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if cokoino_idx not in all_indices: continue
            
        if win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
            
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break 
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 3: ROBOT CONTEXT QUESTIONNAIRE ---
current_arm_positions = [1500, 1500, 1500, 1500, 1500, 1500]

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

dynamic_ready_positions = list(HOME_TARGET)
is_ready_state_set = False

print("\n==================================================")
print("        ROBOT ARM POSITION INITIALIZATION         ")
print("==================================================")
print("Where is the physical arm positioned right now?")
print("  [T] Secured in TUCK position (Taped Plate Numbers)")
print("  [H] Standing upright at HOME position (All 1500s)")
print("  [O] OTHER (Manual eyeball estimates)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        print("\n[SUCCESS] Loaded verified TUCK coordinates into memory.")
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        print("\n[SUCCESS] Loaded standard HOME coordinates into memory.")
        break
    elif choice == 'O':
        print("\nEnter custom pulse width estimates (500-2500):")
        names = ["0 (Base)", "1 (Shoulder)", "2 (Elbow)", "3 (Wrist)", "4 (Gripper)", "5 (Wrist Rot)"]
        for i in range(6):
            while True:
                try:
                    val = int(input(f"  Servo {names[i]} position [Default 1500]: ") or 1500)
                    if 500 <= val <= 2500:
                        current_arm_positions[i] = val
                        break
                    print("    [OUT OF BOUNDS] Keep value between 500 and 2500.")
                except ValueError:
                    print("    [INVALID] Please enter an integer number.")
        break

print(f"\nFinalized Startup Vector: {current_arm_positions}\n")

# --- STEP 4: RUNTIME ENGAGEMENT ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H")
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"--- Physical Cokoino NeoPixels: Looping Red   ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                if cmd != last_processed_command:
                    last_processed_command = ""

                if "RELEASE" in cmd_upper:
                    last_processed_command = ""
                    continue

                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        win7.write(b"[!!!] START DETECTED [!!!]\r\n")
                        win7.write(b"--- COKOINO READY FOR PS/2 COMMANDS ---\r\n\r\n")
                
                elif system_state == 1:
                    
                    # CROSS (Diagnostics)
                    if "CROSS" in cmd_upper:
                        if last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Exact String: 'VER\\r'\r\n")
                            lynx.reset_input_buffer()
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                if response:
                                    win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))
                                    cokoino.write(b"LED:VER:SUCCESS\n")
                                else:
                                    cokoino.write(b"LED:VER:FAULT\n")
                            else:
                                cokoino.write(b"LED:VER:FAULT\n")
                    
                    # TRIANGLE (Home Macro)
                    elif "TRIANGLE" in cmd_upper:
                        if last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Dispatching Synchronized Home Macro:\r\n")
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = HOME_TARGET[joint]
                                macro_packet += f"#{joint}P{HOME_TARGET[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # CIRCLE (Tuck Macro)
                    elif "CIRCLE" in cmd_upper:
                        if last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Dispatching Synchronized Tuck Macro:\r\n")
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = TUCK_TARGET[joint]
                                macro_packet += f"#{joint}P{TUCK_TARGET[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SELECT/RESET (Memory Lock for Ready State)
                    elif "SELECT" in cmd_upper:
                        if last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            dynamic_ready_positions = list(current_arm_positions)
                            is_ready_state_set = True
                            win7.write(b"==================================================\r\n")
                            win7.write(b"[MEMORY LOCK] Dynamic READY Position Established!\r\n")
                            win7.write(f"Captured Hardware Footprint: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")
                            cokoino.write(b"LED:SET:SUCCESS\n")

                    # L1 BUTTON: COORDINATED LINEAR THRUST
                    elif "TOOL ADVANCE" in cmd_upper:
                        if last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] Cannot thrust. Operator must press SELECT first to lock baseline.\r\n\r\n")
                                continue
                                
                            try:
                                win7.write(b"[INVERSE KINEMATICS] Computing Direction-Aware 3cm Linear Path...\r\n")
                                
                                # Use a virtual copy of the shoulder position to establish initial math space
                                # If the physical shoulder position is inverted, we must virtualize it back to standard trig space
                                physical_p1 = dynamic_ready_positions[1]
                                virtual_p1 = 1500 - (physical_p1 - 1500)
                                
                                th1_start = pulse_to_radians(virtual_p1)
                                th2_start = pulse_to_radians(dynamic_ready_positions[2])
                                
                                x_start = L1 * math.cos(th1_start) + L2 * math.cos(th1_start + th2_start)
                                z_start = L1 * math.sin(th1_start) + L2 * math.sin(th1_start + th2_start)
                                
                                win7.write(f"  Calculated Start Plane: X={x_start:.1f}mm, Z={z_start:.1f}mm\r\n".encode('utf-8'))
                                win7.write(b"  Executing Damped Incremental Forward Push:\r\n")
                                
                                for step in [10.0, 20.0, 30.0]:
                                    target_x = x_start + step
                                    target_z = z_start
                                    
                                    p1, p2, p3 = solve_ik(target_x, target_z)
                                    
                                    current_arm_positions[1] = p1
                                    current_arm_positions[2] = p2
                                    current_arm_positions[3] = p3
                                    # Hard requirement 3: Gripper (Ch 4) remains completely untouched
                                    
                                    packet = f"#1P{p1}#2P{p2}#3P{p3}T1000\r"
                                    lynx.write(packet.encode('utf-8'))
                                    win7.write(f"    Segment {step/10:.0f}cm -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                    
                                    time.sleep(1.0)
                                win7.write(b"[THRUST COMPLETE]\r\n\r\n")
                            except Exception as math_err:
                                win7.write(f"[MATH ERROR] Trajectory boundary reached: {math_err}\r\n\r\n".encode('utf-8'))

                    # L2 BUTTON: RETRACT TO READY
                    elif "TOOL RETRACT" in cmd_upper:
                        if last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Retracting Linear Tool Head back to Ready Matrix:\r\n")
                            
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = dynamic_ready_positions[joint]
                                macro_packet += f"#{joint}P{dynamic_ready_positions[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#103 2026-06-30 07:54:11

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

Re: Python Computer Language

This post contains Version 34 of a series of Python programs designed to interface between a Cokoino CKK0017 control board and a LynxMotion robot arm.

In this version, we are abstracting the hardware layer after initial experiments with had coded dimensions led to unexpected behavior.

With the addition of programmed movements, we are entering a new (higher) level of design.  in earlier versions we were just trying to achieve connectivity at all, and then we performed simple movements based upon known position data. Now we are attempting to compute a complex movement, and our initial experiments have not turned out well. 

If you examine the new version, you should find an XML structure that (I'm told) models an industry standard.

# bridgeV34.py Prepared by Gemini Supervised by Tom Hanson

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

print("==================================================")
print("Initializing Robot Junction Bridge V34...")
print("XML Hardware Abstraction Layer (HAL) Integrated")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
# This XML string represents the industry-standard way to describe a robot's 
# physical limbs (links) and motors (joints). The Python engine parses this 
# dynamically so that no dimensions or boundaries are hardcoded in the loop.
# ==============================================================================
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>
        <pulses_per_degree>11.11</pulses_per_degree>
    </controller_settings>

    <link name="link_1_shoulder">
        <length_mm>147.0</length_mm>
        <mass_g>PLACEHOLDER</mass_g>
    </link>
    
    <link name="link_2_elbow">
        <length_mm>187.5</length_mm>
        <mass_g>PLACEHOLDER</mass_g>
    </link>
    
    <link name="link_3_tool_head">
        <length_mm>0.0</length_mm> </link>

    <joint channel="0" name="Base">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>Z_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="1" name="Shoulder">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>true</invert_direction> <axis_orientation>Y_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="2" name="Elbow">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>Y_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="3" name="Wrist">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>Y_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="4" name="Gripper">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>IMMUTABLE_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="5" name="Wrist Rot">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>X_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

</robot>
"""

# --- DYNAMIC XML PARSER AND HAL ROUTINES ---
root = ET.fromstring(urdf_configuration)

# 1. Parse Controller Constants
MIN_PULSE     = int(root.find(".//min_pulse_width").text)
MAX_PULSE     = int(root.find(".//max_pulse_width").text)
CENTER_PULSE  = int(root.find(".//center_pulse_width").text)
P_PER_DEG     = float(root.find(".//pulses_per_degree").text)

# 2. Parse Physical Link Lengths dynamically into the Kinematics variables
L1 = float(root.find(".//link[@name='link_1_shoulder']/length_mm").text)
L2 = float(root.find(".//link[@name='link_2_elbow']/length_mm").text)

# 3. Parse Joint Inversion Map
JOINT_INVERT_MAP = {}
for joint in root.findall(".//joint"):
    ch = int(joint.get("channel"))
    inv_str = joint.find("invert_direction").text.lower()
    JOINT_INVERT_MAP[ch] = (inv_str == "true")


def pulse_to_radians(pulse):
    degrees = (pulse - CENTER_PULSE) / P_PER_DEG
    return math.radians(degrees + 90.0)

def radians_to_pulse(rad):
    degrees = math.degrees(rad) - 90.0
    return int(CENTER_PULSE + (degrees * P_PER_DEG))

def constraint_safety_clip(pulse):
    """Enforces strict software limits dynamically parsed from XML."""
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

def solve_ik(x, z):
    d = math.sqrt(x*x + z*z)
    if d > (L1 + L2) or d < abs(L1 - L2):
        raise ValueError("Target coordinate mathematically out of physical reach.")
        
    cos_angle_2 = (d*d - L1*L1 - L2*L2) / (2.0 * L1 * L2)
    cos_angle_2 = max(-1.0, min(1.0, cos_angle_2))
    angle_2 = math.acos(cos_angle_2)
    
    angle_1 = math.atan2(z, x) + math.acos((L1*L1 + d*d - L2*L2) / (2.0 * L1 * d))
    angle_3 = math.pi - angle_1 - angle_2
    
    # Calculate raw trigonometric pulses
    raw_p1 = radians_to_pulse(angle_1)
    raw_p2 = radians_to_pulse(angle_2)
    raw_p3 = radians_to_pulse(angle_3)
    
    # DYNAMIC INVERSION HANDLING:
    # Look up Channel 1 property in XML map. If invert_direction is True, mirror it.
    if JOINT_INVERT_MAP.get(1, False):
        delta_p1 = raw_p1 - CENTER_PULSE
        p1 = constraint_safety_clip(CENTER_PULSE - delta_p1)
    else:
        p1 = constraint_safety_clip(raw_p1)
        
    # Handle Channel 2 and 3 using the XML map setup for generic safety mapping
    p2 = constraint_safety_clip(CENTER_PULSE - (raw_p2 - CENTER_PULSE)) if JOINT_INVERT_MAP.get(2, False) else constraint_safety_clip(raw_p2)
    p3 = constraint_safety_clip(CENTER_PULSE - (raw_p3 - CENTER_PULSE)) if JOINT_INVERT_MAP.get(3, False) else constraint_safety_clip(raw_p3)
    
    return p1, p2, p3

# --- STEP 1: SCAN & FILTER FOR PHYSICAL USB DEVICES ---
all_found_ports = serial.tools.list_ports.comports()
ports = []
for p in all_found_ports:
    if "USB" in p.device.upper():
        ports.append(p)

ports = sorted(ports, key=lambda x: x.device)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

print("Detected Physical USB Serial Devices:")
for idx, port in enumerate(ports):
    print(f"  [{idx}] {port.device} - {port.description}")
print("")

# --- STEP 2: INTERACTIVE MENU WITH AUTO-DEDUCTION ---
all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        if win7_idx not in all_indices: continue
            
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if cokoino_idx not in all_indices: continue
            
        if win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
            
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break 
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 3: ROBOT CONTEXT QUESTIONNAIRE ---
current_arm_positions = [CENTER_PULSE] * 6

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

dynamic_ready_positions = list(HOME_TARGET)
is_ready_state_set = False

print("\n==================================================")
print("        ROBOT ARM POSITION INITIALIZATION         ")
print("==================================================")
print("Where is the physical arm positioned right now?")
print("  [T] Secured in TUCK position (Taped Plate Numbers)")
print("  [H] Standing upright at HOME position (All 1500s)")
print("  [O] OTHER (Manual eyeball estimates)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        print("\n[SUCCESS] Loaded verified TUCK coordinates into memory.")
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        print("\n[SUCCESS] Loaded standard HOME coordinates into memory.")
        break
    elif choice == 'O':
        print("\nEnter custom pulse width estimates (500-2500):")
        names = ["0 (Base)", "1 (Shoulder)", "2 (Elbow)", "3 (Wrist)", "4 (Gripper)", "5 (Wrist Rot)"]
        for i in range(6):
            while True:
                try:
                    val = int(input(f"  Servo {names[i]} position [Default 1500]: ") or 1500)
                    if MIN_PULSE <= val <= MAX_PULSE:
                        current_arm_positions[i] = val
                        break
                    print(f"    [OUT OF BOUNDS] Keep value between {MIN_PULSE} and {MAX_PULSE}.")
                except ValueError:
                    print("    [INVALID] Please enter an integer number.")
        break

print(f"\nFinalized Startup Vector: {current_arm_positions}\n")

# --- STEP 4: RUNTIME ENGAGEMENT ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H")
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"--- Physical Cokoino NeoPixels: Looping Red   ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                if cmd != last_processed_command:
                    last_processed_command = ""

                if "RELEASE" in cmd_upper:
                    last_processed_command = ""
                    continue

                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        win7.write(b"[!!!] START DETECTED [!!!]\r\n")
                        win7.write(b"--- COKOINO READY FOR PS/2 COMMANDS ---\r\n\r\n")
                
                elif system_state == 1:
                    
                    # CROSS (Diagnostics)
                    if "CROSS" in cmd_upper:
                        if last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Exact String: 'VER\\r'\r\n")
                            lynx.reset_input_buffer()
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                if response:
                                    win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))
                                    cokoino.write(b"LED:VER:SUCCESS\n")
                                else:
                                    cokoino.write(b"LED:VER:FAULT\n")
                            else:
                                cokoino.write(b"LED:VER:FAULT\n")
                    
                    # TRIANGLE (Home Macro)
                    elif "TRIANGLE" in cmd_upper:
                        if last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Dispatching Synchronized Home Macro:\r\n")
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = HOME_TARGET[joint]
                                macro_packet += f"#{joint}P{HOME_TARGET[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # CIRCLE (Tuck Macro)
                    elif "CIRCLE" in cmd_upper:
                        if last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Dispatching Synchronized Tuck Macro:\r\n")
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = TUCK_TARGET[joint]
                                macro_packet += f"#{joint}P{TUCK_TARGET[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SELECT/RESET (Memory Lock for Ready State)
                    elif "SELECT" in cmd_upper:
                        if last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            dynamic_ready_positions = list(current_arm_positions)
                            is_ready_state_set = True
                            win7.write(b"==================================================\r\n")
                            win7.write(b"[MEMORY LOCK] Dynamic READY Position Established!\r\n")
                            win7.write(f"Captured Hardware Footprint: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")
                            cokoino.write(b"LED:SET:SUCCESS\n")

                    # L1 BUTTON: COORDINATED LINEAR THRUST
                    elif "TOOL ADVANCE" in cmd_upper:
                        if last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] Cannot thrust. Operator must press SELECT first to lock baseline.\r\n\r\n")
                                continue
                                
                            try:
                                win7.write(b"[INVERSE KINEMATICS] Computing XML-Driven 3cm Linear Path...\r\n")
                                
                                physical_p1 = dynamic_ready_positions[1]
                                virtual_p1 = CENTER_PULSE - (physical_p1 - CENTER_PULSE) if JOINT_INVERT_MAP.get(1, False) else physical_p1
                                
                                th1_start = pulse_to_radians(virtual_p1)
                                th2_start = pulse_to_radians(dynamic_ready_positions[2])
                                
                                x_start = L1 * math.cos(th1_start) + L2 * math.cos(th1_start + th2_start)
                                z_start = L1 * math.sin(th1_start) + L2 * math.sin(th1_start + th2_start)
                                
                                win7.write(f"  Calculated Start Plane: X={x_start:.1f}mm, Z={z_start:.1f}mm\r\n".encode('utf-8'))
                                win7.write(b"  Executing Damped Incremental Forward Push:\r\n")
                                
                                for step in [10.0, 20.0, 30.0]:
                                    target_x = x_start + step
                                    target_z = z_start
                                    
                                    p1, p2, p3 = solve_ik(target_x, target_z)
                                    
                                    current_arm_positions[1] = p1
                                    current_arm_positions[2] = p2
                                    current_arm_positions[3] = p3
                                    
                                    packet = f"#1P{p1}#2P{p2}#3P{p3}T1000\r"
                                    lynx.write(packet.encode('utf-8'))
                                    win7.write(f"    Segment {step/10:.0f}cm -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                    
                                    time.sleep(1.0)
                                win7.write(b"[THRUST COMPLETE]\r\n\r\n")
                            except Exception as math_err:
                                win7.write(f"[MATH ERROR] Trajectory boundary reached: {math_err}\r\n\r\n".encode('utf-8'))

                    # L2 BUTTON: RETRACT TO READY
                    elif "TOOL RETRACT" in cmd_upper:
                        if last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Retracting Linear Tool Head back to Ready Matrix:\r\n")
                            
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = dynamic_ready_positions[joint]
                                macro_packet += f"#{joint}P{dynamic_ready_positions[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#104 2026-06-30 09:20:21

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

Re: Python Computer Language

This post contains Version 35 of a Python program to integrate a Cokoino controller with a LynxMotion robot arm.  This version addresses the math problem by concentrating on only one arm and attempting to move the tip of arm 1 exactly 3 centimeters along the arc of the circle described by the tip moving as the arm pivots around it's axis.

# bridgeV35.py Prepared by Gemini Supervised by Tom Hanson
# Staged Kinematics Isolation: Phase 1 (Single Joint Forward Arc Expansion)

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

print("==================================================")
print("Initializing Robot Junction Bridge V35...")
print("XML Hardware Abstraction Layer (HAL) Integrated")
print("Staged Kinematics Phase 1: Arm 1 Arc Move Active")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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>
        <pulses_per_degree>11.11</pulses_per_degree>
    </controller_settings>

    <link name="link_1_shoulder">
        <length_mm>147.0</length_mm>
        <mass_g>PLACEHOLDER</mass_g>
    </link>
    
    <link name="link_2_elbow">
        <length_mm>187.5</length_mm>
        <mass_g>PLACEHOLDER</mass_g>
    </link>
    
    <link name="link_3_tool_head">
        <length_mm>0.0</length_mm> </link>

    <joint channel="0" name="Base">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>Z_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="1" name="Shoulder">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>true</invert_direction> 
        <axis_orientation>Y_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="2" name="Elbow">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>Y_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="3" name="Wrist">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>Y_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="4" name="Gripper">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>IMMUTABLE_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="5" name="Wrist Rot">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>X_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

</robot>
"""

# --- DYNAMIC XML PARSER AND HAL ROUTINES ---
root = ET.fromstring(urdf_configuration)

# 1. Parse Controller Constants
MIN_PULSE     = int(root.find(".//min_pulse_width").text)
MAX_PULSE     = int(root.find(".//max_pulse_width").text)
CENTER_PULSE  = int(root.find(".//center_pulse_width").text)
P_PER_DEG     = float(root.find(".//pulses_per_degree").text)

# 2. Parse Physical Link Lengths dynamically into the Kinematics variables
L1 = float(root.find(".//link[@name='link_1_shoulder']/length_mm").text)
L2 = float(root.find(".//link[@name='link_2_elbow']/length_mm").text)

def constraint_safety_clip(pulse):
    """Enforces strict software limits dynamically parsed from XML."""
    return max(MIN_PULSE, min(MAX_PULSE, pulse))


# --- STEP 1: SCAN & FILTER FOR PHYSICAL USB DEVICES ---
all_found_ports = serial.tools.list_ports.comports()
ports = []
for p in all_found_ports:
    if "USB" in p.device.upper():
        ports.append(p)

ports = sorted(ports, key=lambda x: x.device)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

print("Detected Physical USB Serial Devices:")
for idx, port in enumerate(ports):
    print(f"  [{idx}] {port.device} - {port.description}")
print("")


# --- STEP 2: INTERACTIVE MENU WITH AUTO-DEDUCTION ---
all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        if win7_idx not in all_indices: continue
            
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if cokoino_idx not in all_indices: continue
            
        if win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
            
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break 
    except ValueError:
        print("[INVALID] Try again.\n")


# --- STEP 3: ROBOT CONTEXT QUESTIONNAIRE ---
current_arm_positions = [CENTER_PULSE] * 6

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

dynamic_ready_positions = list(HOME_TARGET)
is_ready_state_set = False

print("\n==================================================")
print("         ROBOT ARM POSITION INITIALIZATION         ")
print("==================================================")
print("Where is the physical arm positioned right now?")
print("  [T] Secured in TUCK position (Taped Plate Numbers)")
print("  [H] Standing upright at HOME position (All 1500s)")
print("  [O] OTHER (Manual eyeball estimates)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        print("\n[SUCCESS] Loaded verified TUCK coordinates into memory.")
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        print("\n[SUCCESS] Loaded standard HOME coordinates into memory.")
        break
    elif choice == 'O':
        print("\nEnter custom pulse width estimates (500-2500):")
        names = ["0 (Base)", "1 (Shoulder)", "2 (Elbow)", "3 (Wrist)", "4 (Gripper)", "5 (Wrist Rot)"]
        for i in range(6):
            while True:
                try:
                    val = int(input(f"  Servo {names[i]} position [Default 1500]: ") or 1500)
                    if MIN_PULSE <= val <= MAX_PULSE:
                        current_arm_positions[i] = val
                        break
                    print(f"    [OUT OF BOUNDS] Keep value between {MIN_PULSE} and {MAX_PULSE}.")
                except ValueError:
                    print("    [INVALID] Please enter an integer number.")
        break

print(f"\nFinalized Startup Vector: {current_arm_positions}\n")


# --- STEP 4: RUNTIME ENGAGEMENT ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H")
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"--- Physical Cokoino NeoPixels: Looping Red    ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                if cmd != last_processed_command:
                    last_processed_command = ""

                if "RELEASE" in cmd_upper:
                    last_processed_command = ""
                    continue

                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        win7.write(b"[!!!] START DETECTED [!!!]\r\n")
                        win7.write(b"--- COKOINO READY FOR PS/2 COMMANDS ---\r\n\r\n")
                
                elif system_state == 1:
                    
                    # CROSS (Diagnostics)
                    if "CROSS" in cmd_upper:
                        if last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Exact String: 'VER\\r'\r\n")
                            lynx.reset_input_buffer()
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                if response:
                                    win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))
                                    cokoino.write(b"LED:VER:SUCCESS\n")
                                else:
                                    cokoino.write(b"LED:VER:FAULT\n")
                            else:
                                cokoino.write(b"LED:VER:FAULT\n")
                    
                    # TRIANGLE (Home Macro)
                    elif "TRIANGLE" in cmd_upper:
                        if last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Dispatching Synchronized Home Macro:\r\n")
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = HOME_TARGET[joint]
                                macro_packet += f"#{joint}P{HOME_TARGET[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # CIRCLE (Tuck Macro)
                    elif "CIRCLE" in cmd_upper:
                        if last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Dispatching Synchronized Tuck Macro:\r\n")
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = TUCK_TARGET[joint]
                                macro_packet += f"#{joint}P{TUCK_TARGET[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SELECT/RESET (Memory Lock for Ready State)
                    elif "SELECT" in cmd_upper:
                        if last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            dynamic_ready_positions = list(current_arm_positions)
                            is_ready_state_set = True
                            win7.write(b"==================================================\r\n")
                            win7.write(b"[MEMORY LOCK] Dynamic READY Position Established!\r\n")
                            win7.write(f"Captured Hardware Footprint: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")
                            cokoino.write(b"LED:SET:SUCCESS\n")

                    # L1 BUTTON: STAGED ISOLATION - SINGLE-JOINT ARC ADVANCE
                    elif "TOOL ADVANCE" in cmd_upper:
                        if last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] Operator must press SELECT first to lock baseline.\r\n\r\n")
                                continue
                                
                            win7.write(b"[KINEMATICS] Executing Staged Phase 1: Outward Arm 1 Arc Move...\r\n")
                            
                            # 1. Fetch locked physical baseline values
                            baseline_p1 = dynamic_ready_positions[1]
                            baseline_p2 = dynamic_ready_positions[2]
                            baseline_p3 = dynamic_ready_positions[3]
                            
                            # 2. Calculate pulse delta for a 30mm outward arc travel along link radius
                            arc_target_mm = 30.0
                            delta_radians = arc_target_mm / L1
                            delta_degrees = math.degrees(delta_radians)
                            pulse_delta = int(round(delta_degrees * P_PER_DEG))
                            
                            win7.write(f"   Arc Radius (L1): {L1}mm, Target: {arc_target_mm}mm\r\n".encode('utf-8'))
                            win7.write(f"   Calculated Absolute Delta: {pulse_delta} pulses\r\n".encode('utf-8'))
                            
                            # 3. Step forward in 3 distinct 10mm increments
                            for step in [1, 2, 3]:
                                fractional_delta = int(round((pulse_delta / 3.0) * step))
                                
                                # PHYSICAL GROUND TRUTH: Outward movement drops pulse width below baseline
                                target_p1 = constraint_safety_clip(baseline_p1 - fractional_delta)
                                    
                                current_arm_positions[1] = target_p1
                                current_arm_positions[2] = baseline_p2
                                current_arm_positions[3] = baseline_p3
                                
                                packet = f"#1P{target_p1}#2P{baseline_p2}#3P{baseline_p3}T1000\r"
                                lynx.write(packet.encode('utf-8'))
                                
                                win7.write(f"   Segment {step}cm -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                time.sleep(1.0)
                                
                            win7.write(b"[ARC MOVE COMPLETE] Arm 1 reached outward target safely.\r\n\r\n")

                    # L2 BUTTON: SIMPLIFIED ABSOLUTE RETURN TO READY
                    elif "TOOL RETRACT" in cmd_upper:
                        if last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] No locked footprint. Cannot snap back.\r\n\r\n")
                                continue
                                
                            win7.write(b"[SAFETY RESET] Snapping absolute tool position back to Ready matrix...\r\n")
                            
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = dynamic_ready_positions[joint]
                                macro_packet += f"#{joint}P{dynamic_ready_positions[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#105 2026-06-30 10:49:04

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

Re: Python Computer Language

This post contains Version 36 of a series to integrate Cokoino with LynxMotion.

Here we attempt to added Arm 2 to the calculation of a coordinated movement.

# bridgeV36.py Prepared by Gemini Supervised by Tom Hanson
# Staged Kinematics Isolation: Phase 2 (Coordinated Two-Link Horizontal Line Push)

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

print("==================================================")
print("Initializing Robot Junction Bridge V36...")
print("XML Hardware Abstraction Layer (HAL) Integrated")
print("Staged Kinematics Phase 2: 5cm Flat Line Push Active")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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>
        <pulses_per_degree>11.11</pulses_per_degree>
    </controller_settings>

    <link name="link_1_shoulder">
        <length_mm>147.0</length_mm>
        <mass_g>PLACEHOLDER</mass_g>
    </link>
    
    <link name="link_2_elbow">
        <length_mm>187.5</length_mm>
        <mass_g>PLACEHOLDER</mass_g>
    </link>
    
    <link name="link_3_tool_head">
        <length_mm>0.0</length_mm> </link>

    <joint channel="0" name="Base">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>Z_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="1" name="Shoulder">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>true</invert_direction> 
        <axis_orientation>Y_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="2" name="Elbow">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>Y_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="3" name="Wrist">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>Y_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="4" name="Gripper">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>IMMUTABLE_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

    <joint channel="5" name="Wrist Rot">
        <angular_sweep_deg>180</angular_sweep_deg>
        <invert_direction>false</invert_direction>
        <axis_orientation>X_AXIS</axis_orientation>
        <velocity_limit_deg_sec>PLACEHOLDER</velocity_limit_deg_sec>
    </joint>

</robot>
"""

# --- DYNAMIC XML PARSER AND HAL ROUTINES ---
root = ET.fromstring(urdf_configuration)

# 1. Parse Controller Constants
MIN_PULSE     = int(root.find(".//min_pulse_width").text)
MAX_PULSE     = int(root.find(".//max_pulse_width").text)
CENTER_PULSE  = int(root.find(".//center_pulse_width").text)
P_PER_DEG     = float(root.find(".//pulses_per_degree").text)

# 2. Parse Physical Link Lengths dynamically into the Kinematics variables
L1 = float(root.find(".//link[@name='link_1_shoulder']/length_mm").text)
L2 = float(root.find(".//link[@name='link_2_elbow']/length_mm").text)

def constraint_safety_clip(pulse):
    """Enforces strict software limits dynamically parsed from XML."""
    return max(MIN_PULSE, min(MAX_PULSE, pulse))


# --- STEP 1: SCAN & FILTER FOR PHYSICAL USB DEVICES ---
all_found_ports = serial.tools.list_ports.comports()
ports = []
for p in all_found_ports:
    if "USB" in p.device.upper():
        ports.append(p)

ports = sorted(ports, key=lambda x: x.device)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

print("Detected Physical USB Serial Devices:")
for idx, port in enumerate(ports):
    print(f"  [{idx}] {port.device} - {port.description}")
print("")


# --- STEP 2: INTERACTIVE MENU WITH AUTO-DEDUCTION ---
all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        if win7_idx not in all_indices: continue
            
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if cokoino_idx not in all_indices: continue
            
        if win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
            
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break 
    except ValueError:
        print("[INVALID] Try again.\n")


# --- STEP 3: ROBOT CONTEXT QUESTIONNAIRE ---
current_arm_positions = [CENTER_PULSE] * 6

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

dynamic_ready_positions = list(HOME_TARGET)
is_ready_state_set = False

print("\n==================================================")
print("         ROBOT ARM POSITION INITIALIZATION         ")
print("==================================================")
print("Where is the physical arm positioned right now?")
print("  [T] Secured in TUCK position (Taped Plate Numbers)")
print("  [H] Standing upright at HOME position (All 1500s)")
print("  [O] OTHER (Manual eyeball estimates)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        print("\n[SUCCESS] Loaded verified TUCK coordinates into memory.")
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        print("\n[SUCCESS] Loaded standard HOME coordinates into memory.")
        break
    elif choice == 'O':
        print("\nEnter custom pulse width estimates (500-2500):")
        names = ["0 (Base)", "1 (Shoulder)", "2 (Elbow)", "3 (Wrist)", "4 (Gripper)", "5 (Wrist Rot)"]
        for i in range(6):
            while True:
                try:
                    val = int(input(f"  Servo {names[i]} position [Default 1500]: ") or 1500)
                    if MIN_PULSE <= val <= MAX_PULSE:
                        current_arm_positions[i] = val
                        break
                    print(f"    [OUT OF BOUNDS] Keep value between {MIN_PULSE} and {MAX_PULSE}.")
                except ValueError:
                    print("    [INVALID] Please enter an integer number.")
        break

print(f"\nFinalized Startup Vector: {current_arm_positions}\n")


# --- STEP 4: RUNTIME ENGAGEMENT ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H")
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"--- Physical Cokoino NeoPixels: Looping Red    ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                if cmd != last_processed_command:
                    last_processed_command = ""

                if "RELEASE" in cmd_upper:
                    last_processed_command = ""
                    continue

                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        win7.write(b"[!!!] START DETECTED [!!!]\r\n")
                        win7.write(b"--- COKOINO READY FOR PS/2 COMMANDS ---\r\n\r\n")
                
                elif system_state == 1:
                    
                    # CROSS (Diagnostics)
                    if "CROSS" in cmd_upper:
                        if last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Exact String: 'VER\\r'\r\n")
                            lynx.reset_input_buffer()
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                if response:
                                    win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))
                                    cokoino.write(b"LED:VER:SUCCESS\n")
                                else:
                                    cokoino.write(b"LED:VER:FAULT\n")
                            else:
                                cokoino.write(b"LED:VER:FAULT\n")
                    
                    # TRIANGLE (Home Macro)
                    elif "TRIANGLE" in cmd_upper:
                        if last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Dispatching Synchronized Home Macro:\r\n")
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = HOME_TARGET[joint]
                                macro_packet += f"#{joint}P{HOME_TARGET[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # CIRCLE (Tuck Macro)
                    elif "CIRCLE" in cmd_upper:
                        if last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            win7.write(b"[TO LYNXMOTION] -> Dispatching Synchronized Tuck Macro:\r\n")
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = TUCK_TARGET[joint]
                                macro_packet += f"#{joint}P{TUCK_TARGET[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SELECT/RESET (Memory Lock for Ready State)
                    elif "SELECT" in cmd_upper:
                        if last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            dynamic_ready_positions = list(current_arm_positions)
                            is_ready_state_set = True
                            win7.write(b"==================================================\r\n")
                            win7.write(b"[MEMORY LOCK] Dynamic READY Position Established!\r\n")
                            win7.write(f"Captured Hardware Footprint: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")
                            cokoino.write(b"LED:SET:SUCCESS\n")

                    # L1 BUTTON: COORDINATED TWO-LINK HORIZONTAL 50MM PUSH
                    elif "TOOL ADVANCE" in cmd_upper:
                        if last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] Operator must press SELECT first to lock baseline.\r\n\r\n")
                                continue
                                
                            win7.write(b"[KINEMATICS] Initiating Coordinated 50mm Horizontal Push...\r\n")
                            
                            # 1. Fetch locked baseline pulse values
                            b_p1 = dynamic_ready_positions[1]
                            b_p2 = dynamic_ready_positions[2]
                            
                            # 2. Convert baseline pulses to physical angles (in Radians)
                            # Joint 1: 1500 is straight up (90 deg). Decreasing pulse moves it forward.
                            deg1 = 90.0 + ((1500 - b_p1) / P_PER_DEG)
                            rad1 = math.radians(deg1)
                            
                            # Joint 2: 1500 is perpendicular to Arm 1 (90 deg offset)
                            # Relative angle to Arm 1 changes with pulse. Increasing pulse moves it outward/down.
                            deg2 = 90.0 - ((b_p2 - 1500) / P_PER_DEG)
                            rad2 = math.radians(deg2)
                            
                            # 3. Calculate current starting coordinates (Forward Kinematics)
                            # Elbow angle relative to horizon is (rad1 + rad2 - pi) due to mechanical layout
                            angle_link2 = rad1 + rad2 - math.pi
                            
                            x_start = L1 * math.cos(rad1) + L2 * math.cos(angle_link2)
                            z_start = L1 * math.sin(rad1) + L2 * math.sin(angle_link2)
                            
                            # Lock Z height for a perfectly flat horizontal path
                            z_target = z_start 
                            
                            # 4. Generate the 5-Step Predictive Matrix for the Win 7 Monitor
                            win7.write(f"   Initial Position: X = {x_start:.1f}mm, Z = {z_start:.1f}mm (Locked)\r\n".encode('utf-8'))
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            win7.write(b"   SEGMENT   TARGET X   TARGET Z   SHOULDER (P1)   ELBOW (P2)\r\n")
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            
                            steps_pulses = []
                            valid_path = True
                            
                            for step in [1, 2, 3, 4, 5]:
                                x_target = x_start + (step * 10.0) # Move forward 10mm per segment
                                
                                # Inverse Kinematics (Law of Cosines) to find required joint angles
                                try:
                                    # Distance from origin to target
                                    D_sq = x_target**2 + z_target**2
                                    
                                    # Solve for Elbow Angle
                                    cos_theta2 = (D_sq - L1**2 - L2**2) / (2.0 * L1 * L2)
                                    theta2 = math.acos(cos_theta2)
                                    
                                    # Solve for Shoulder Angle
                                    theta1 = math.atan2(z_target, x_target) - math.acos((L1**2 + D_sq - L2**2) / (2.0 * L1 * math.sqrt(D_sq)))
                                    
                                    # Convert geometry angles back to hardware pulses
                                    target_deg1 = math.degrees(theta1)
                                    target_deg2 = math.degrees(theta2)
                                    
                                    # Map angles back to specific physical directions
                                    t_p1 = int(round(1500 - ((target_deg1 - 90.0) * P_PER_DEG)))
                                    t_p2 = int(round(1500 + ((90.0 - target_deg2) * P_PER_DEG)))
                                    
                                    # Clip to safety boundaries
                                    t_p1 = constraint_safety_clip(t_p1)
                                    t_p2 = constraint_safety_clip(t_p2)
                                    
                                    steps_pulses.append((step, x_target, t_p1, t_p2))
                                    
                                    win7.write(f"   {step} cm      {x_target:.1f}mm    {z_target:.1f}mm    {t_p1}            {t_p2}\r\n".encode('utf-8'))
                                    
                                except ValueError:
                                    win7.write(f"   {step} cm      [MATH ERROR: TARGET OUT OF PHYSICAL REACH]\r\n".encode('utf-8'))
                                    valid_path = False
                                    break
                                    
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            
                            if not valid_path:
                                win7.write(b"[ABORT] Motion trajectory contains unreachable points.\r\n\r\n")
                                continue
                                
                            # 5. Execute calculated path sequentially if all points are valid
                            win7.write(b"Executing coordinated line movement...\r\n")
                            for step, x_t, t_p1, t_p2 in steps_pulses:
                                
                                # Apply wrist leveling logic based on Elbow delta
                                el_delta = t_p2 - 1500
                                t_p3 = constraint_safety_clip(1500 + el_delta)
                                
                                current_arm_positions[1] = t_p1
                                current_arm_positions[2] = t_p2
                                current_arm_positions[3] = t_p3
                                
                                # Dispatch coordinated multi-axis packet to LynxMotion
                                packet = f"#1P{t_p1}#2P{t_p2}#3P{t_p3}T1000\r"
                                lynx.write(packet.encode('utf-8'))
                                
                                win7.write(f"   Moving to Segment {step}cm -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                time.sleep(1.0)
                                
                            win7.write(b"[LINE PUSH COMPLETE] Coordinated horizontal thrust verified.\r\n\r\n")

                    # L2 BUTTON: SIMPLIFIED ABSOLUTE SNAP BACK TO READY
                    elif "TOOL RETRACT" in cmd_upper:
                        if last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] No locked footprint. Cannot snap back.\r\n\r\n")
                                continue
                                
                            win7.write(b"[SAFETY RESET] Snapping absolute tool position back to Ready matrix...\r\n")
                            
                            macro_packet = ""
                            for joint in range(6):
                                current_arm_positions[joint] = dynamic_ready_positions[joint]
                                macro_packet += f"#{joint}P{dynamic_ready_positions[joint]}"
                            macro_packet += f"T{TRANSIT_TIME_MS}\r"
                            
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#106 2026-06-30 12:27:22

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

Re: Python Computer Language

This post contains Version 37 ... we are working on the problem of how to adjust the angle of three robot arms in order to deliver a movement in X that does not vary in Y. When the pivot of Arm 1 turns, the pivot for Arm 2 and Wrist 1 must change simultaneously.

Update: Version 37 came very close ... a sign was set incorrectly so the arm #2 went up instead of down. Version 38 will have the sign reversed.


# bridgeV37.py Prepared by Gemini Supervised by Tom Hanson
# Staged Kinematics Isolation: Phase 2 (Proportional Linear Equalization)

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

print("==================================================")
print("Initializing Robot Junction Bridge V37...")
print("XML Hardware Abstraction Layer (HAL) Integrated")
print("Proportional Coordinated 5cm Horizontal Push")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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>
        <pulses_per_degree>11.11</pulses_per_degree>
    </controller_settings>

    <link name="link_1_shoulder">
        <length_mm>147.0</length_mm>
    </link>
    
    <link name="link_2_elbow">
        <length_mm>187.5</length_mm>
    </link>
    
    <link name="link_3_tool_head">
        <length_mm>0.0</length_mm> 
    </link>

    <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>
"""

# --- DYNAMIC XML PARSER ---
root = ET.fromstring(urdf_configuration)
MIN_PULSE     = int(root.find(".//min_pulse_width").text)
MAX_PULSE     = int(root.find(".//max_pulse_width").text)
CENTER_PULSE  = int(root.find(".//center_pulse_width").text)
P_PER_DEG     = float(root.find(".//pulses_per_degree").text)
L1            = float(root.find(".//link[@name='link_1_shoulder']/length_mm").text)

def constraint_safety_clip(pulse):
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

# --- STEP 1: SCAN & FILTER FOR PHYSICAL USB DEVICES ---
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)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

print("Detected Physical USB Serial Devices:")
for idx, port in enumerate(ports):
    print(f"  [{idx}] {port.device} - {port.description}")
print("")

# --- STEP 2: INTERACTIVE MENU ---
all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if win7_idx not in all_indices or cokoino_idx not in all_indices or win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break 
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 3: ROBOT CONTEXT QUESTIONNAIRE ---
current_arm_positions = [CENTER_PULSE] * 6
HOME_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]
TUCK_TARGET = [1500, 1821, 1842, 500, 500, 1500]
dynamic_ready_positions = list(HOME_TARGET)
is_ready_state_set = False

print("\n==================================================")
print("         ROBOT ARM POSITION INITIALIZATION         ")
print("==================================================")
print("  [T] Secured in TUCK position")
print("  [H] Standing upright at HOME position")
print("  [O] OTHER (Manual estimates)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        break
    elif choice == 'O':
        break

# --- STEP 4: RUNTIME ENGAGEMENT ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H")
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                if cmd != last_processed_command:
                    last_processed_command = ""

                if "RELEASE" in cmd_upper:
                    last_processed_command = ""
                    continue

                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        win7.write(b"[!!!] START DETECTED [!!!]\r\n\r\n")
                
                elif system_state == 1:
                    
                    if "CROSS" in cmd_upper:
                        if last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))

                    elif "TRIANGLE" in cmd_upper:
                        if last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            macro_packet = "".join(f"#{j}P{HOME_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    elif "CIRCLE" in cmd_upper:
                        if last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            macro_packet = "".join(f"#{j}P{TUCK_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    elif "SELECT" in cmd_upper:
                        if last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            dynamic_ready_positions = list(current_arm_positions)
                            is_ready_state_set = True
                            win7.write(b"==================================================\r\n")
                            win7.write(b"[MEMORY LOCK] Dynamic READY Position Established!\r\n")
                            win7.write(f"Captured Hardware Footprint: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")

                    # L1 BUTTON: PROPORTIONAL LINEAR EQUALIZATION 5CM PUSH
                    elif "TOOL ADVANCE" in cmd_upper:
                        if last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] Operator must press SELECT first to lock baseline.\r\n\r\n")
                                continue
                                
                            win7.write(b"[KINEMATICS] Executing Slope-Aware Proportional 5cm Line Push...\r\n")
                            
                            baseline_p1 = dynamic_ready_positions[1]
                            baseline_p2 = dynamic_ready_positions[2]
                            
                            # 1. Base calculations on your confirmed 30mm/130 pulse ground truth
                            #    Scaled up to a 50mm forward run = 217 total pulses for Joint 1
                            total_sh_pulses = 217 
                            
                            # 2. Determine compensation slope for Joint 2 based on your starting footprint
                            #    If starting at Home (1500), it's flat; slope is 0.
                            #    If starting at Tuck (1842), it's steep; it needs to open outward to cancel rise.
                            if baseline_p1 > 1600: 
                                # Tuck Profile: Joint 2 tracks downward proportionally with Joint 1
                                total_el_pulses = -215  
                            else:
                                # Home Profile: Flat top of arc, minimal elbow adjustments required
                                total_el_pulses = 45   

                            win7.write(b"   -------------------------------------------------------------\r\n")
                            win7.write(b"   SEGMENT   SHOULDER (P1)   ELBOW (P2)    WRIST (P3) (LEVEL)\r\n")
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            
                            # 3. Process and stream the 5 distinct 1cm steps
                            for step in [1, 2, 3, 4, 5]:
                                fraction = step / 5.0
                                
                                # Apply the safe physical outward decrement to Joint 1
                                target_p1 = constraint_safety_clip(int(round(baseline_p1 - (total_sh_pulses * fraction))))
                                
                                # Apply the mapped slope adjustment to Joint 2
                                target_p2 = constraint_safety_clip(int(round(baseline_p2 + (total_el_pulses * fraction))))
                                
                                # Apply your equal-and-opposite wrist leveling logic
                                el_delta = target_p2 - baseline_p2
                                target_p3 = constraint_safety_clip(dynamic_ready_positions[3] - el_delta)
                                
                                current_arm_positions[1] = target_p1
                                current_arm_positions[2] = target_p2
                                current_arm_positions[3] = target_p3
                                
                                packet = f"#1P{target_p1}#2P{target_p2}#3P{target_p3}T1000\r"
                                lynx.write(packet.encode('utf-8'))
                                
                                win7.write(f"   {step} cm      {target_p1}            {target_p2}            {target_p3}          -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                time.sleep(1.0)
                                
                            win7.write(b"[PUSH COMPLETE] Target line reached cleanly.\r\n\r\n")

                    # L2 BUTTON: SNAP DIRECTLY BACK TO READY
                    elif "TOOL RETRACT" in cmd_upper:
                        if last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] No footprint locked.\r\n\r\n")
                                continue
                                
                            win7.write(b"[RESET] Snapping back to Ready matrix...\r\n")
                            macro_packet = "".join(f"#{j}P{dynamic_ready_positions[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#107 2026-06-30 13:24:48

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

Re: Python Computer Language

This is Version 38 with a sign reversed from Version 37...

# bridgeV38.py Prepared by Gemini Supervised by Tom Hanson
# Staged Kinematics Isolation: Phase 2 (Proportional Linear Equalization Fixed)

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

print("==================================================")
print("Initializing Robot Junction Bridge V38...")
print("XML Hardware Abstraction Layer (HAL) Integrated")
print("Corrected Proportional 5cm Horizontal Push")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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>
        <pulses_per_degree>11.11</pulses_per_degree>
    </controller_settings>

    <link name="link_1_shoulder">
        <length_mm>147.0</length_mm>
    </link>
    
    <link name="link_2_elbow">
        <length_mm>187.5</length_mm>
    </link>
    
    <link name="link_3_tool_head">
        <length_mm>0.0</length_mm> 
    </link>

    <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>
"""

# --- DYNAMIC XML PARSER ---
root = ET.fromstring(urdf_configuration)
MIN_PULSE     = int(root.find(".//min_pulse_width").text)
MAX_PULSE     = int(root.find(".//max_pulse_width").text)
CENTER_PULSE  = int(root.find(".//center_pulse_width").text)
P_PER_DEG     = float(root.find(".//pulses_per_degree").text)
L1            = float(root.find(".//link[@name='link_1_shoulder']/length_mm").text)

def constraint_safety_clip(pulse):
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

# --- STEP 1: SCAN & FILTER FOR PHYSICAL USB DEVICES ---
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)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

print("Detected Physical USB Serial Devices:")
for idx, port in enumerate(ports):
    print(f"  [{idx}] {port.device} - {port.description}")
print("")

# --- STEP 2: INTERACTIVE MENU ---
all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if win7_idx not in all_indices or cokoino_idx not in all_indices or win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break 
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 3: ROBOT CONTEXT QUESTIONNAIRE ---
current_arm_positions = [CENTER_PULSE] * 6
HOME_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]
TUCK_TARGET = [1500, 1821, 1842, 500, 500, 1500]
dynamic_ready_positions = list(HOME_TARGET)
is_ready_state_set = False

print("\n==================================================")
print("         ROBOT ARM POSITION INITIALIZATION         ")
print("==================================================")
print("  [T] Secured in TUCK position")
print("  [H] Standing upright at HOME position")
print("  [O] OTHER (Manual estimates)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        break
    elif choice == 'O':
        break

# --- STEP 4: RUNTIME ENGAGEMENT ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H")
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                if cmd != last_processed_command:
                    last_processed_command = ""

                if "RELEASE" in cmd_upper:
                    last_processed_command = ""
                    continue

                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        win7.write(b"[!!!] START DETECTED [!!!]\r\n\r\n")
                
                elif system_state == 1:
                    
                    if "CROSS" in cmd_upper:
                        if last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))

                    elif "TRIANGLE" in cmd_upper:
                        if last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            macro_packet = "".join(f"#{j}P{HOME_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    elif "CIRCLE" in cmd_upper:
                        if last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            macro_packet = "".join(f"#{j}P{TUCK_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    elif "SELECT" in cmd_upper:
                        if last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            dynamic_ready_positions = list(current_arm_positions)
                            is_ready_state_set = True
                            win7.write(b"==================================================\r\n")
                            win7.write(b"[MEMORY LOCK] Dynamic READY Position Established!\r\n")
                            win7.write(f"Captured Hardware Footprint: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")

                    # L1 BUTTON: PROPORTIONAL LINEAR EQUALIZATION 5CM PUSH
                    elif "TOOL ADVANCE" in cmd_upper:
                        if last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] Operator must press SELECT first to lock baseline.\r\n\r\n")
                                continue
                                
                            win7.write(b"[KINEMATICS] Executing Slope-Aware Proportional 5cm Line Push...\r\n")
                            
                            baseline_p1 = dynamic_ready_positions[1]
                            baseline_p2 = dynamic_ready_positions[2]
                            
                            # 1. Total Shoulder Pulse Delta for 50mm Run
                            total_sh_pulses = 217 
                            
                            # 2. Determine compensation direction for Joint 2 based on footprint
                            if baseline_p1 > 1600: 
                                # Tuck Profile: Joint 2 must drop DOWN towards 1500 to lower arm
                                total_el_pulses = 215  
                            else:
                                # Home Profile: Minimal adjustments from flat top
                                total_el_pulses = -45   

                            win7.write(b"   -------------------------------------------------------------\r\n")
                            win7.write(b"   SEGMENT   SHOULDER (P1)   ELBOW (P2)    WRIST (P3) (LEVEL)\r\n")
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            
                            # 3. Process and stream the 5 distinct 1cm steps
                            for step in [1, 2, 3, 4, 5]:
                                fraction = step / 5.0
                                
                                # Move Shoulder Forward/Outward
                                target_p1 = constraint_safety_clip(int(round(baseline_p1 - (total_sh_pulses * fraction))))
                                
                                # SIGN FIXED: Subtract the pulse delta to drive Elbow DOWNWARD
                                target_p2 = constraint_safety_clip(int(round(baseline_p2 - (total_el_pulses * fraction))))
                                
                                # Track Wrist leveler with the new Elbow direction
                                el_delta = target_p2 - baseline_p2
                                target_p3 = constraint_safety_clip(dynamic_ready_positions[3] - el_delta)
                                
                                current_arm_positions[1] = target_p1
                                current_arm_positions[2] = target_p2
                                current_arm_positions[3] = target_p3
                                
                                packet = f"#1P{target_p1}#2P{target_p2}#3P{target_p3}T1000\r"
                                lynx.write(packet.encode('utf-8'))
                                
                                win7.write(f"   {step} cm      {target_p1}            {target_p2}            {target_p3}          -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                time.sleep(1.0)
                                
                            win7.write(b"[PUSH COMPLETE] Target line reached cleanly.\r\n\r\n")

                    # L2 BUTTON: SNAP DIRECTLY BACK TO READY
                    elif "TOOL RETRACT" in cmd_upper:
                        if last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            
                            if not is_ready_state_set:
                                win7.write(b"[ABORT] No footprint locked.\r\n\r\n")
                                continue
                                
                            win7.write(b"[RESET] Snapping back to Ready matrix...\r\n")
                            macro_packet = "".join(f"#{j}P{dynamic_ready_positions[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#108 2026-06-30 15:21:36

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

Re: Python Computer Language

This post contains Version 39 of a Python program to interface a Cokoino controller to a LynxMotion robot arm.

In this version, we are activating the Square button to hold a set of values chosen by the operator to specify a position that is to be stored and recalled by the Square button. Thus, we will have three buttons working to set configurations.

Home is set to 1500  for all servo's.  This value is in the middle of the range for each servo, from 500 to 2500 fully clockwise for 180 degrees of movement.

Tuck is set to a set of values discovered through experiment to prepare the robot arm for transport to another location.

the new Square button setting is for "Other" values that are entered at program start time.

The forward movement command still needs work, but I've decided to let that rest for a while.  We have 17 buttons in all to configure, and we've only worked with 8 to this point.

# bridgeV39_Final.py Prepared by Gemini Supervised by Tom Hanson
# Hard Hardware Alignment: Active Command Transmission on SQUARE Press

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

print("==================================================")
print("Initializing Robot Junction Bridge V39...")
print("Active Execution & Hardware Alignment Engine Active")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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)
MIN_PULSE    = int(root.find(".//min_pulse_width").text)
MAX_PULSE    = int(root.find(".//max_pulse_width").text)
CENTER_PULSE = int(root.find(".//center_pulse_width").text)

def constraint_safety_clip(pulse):
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

# --- STEP 1: SERIAL CONNECTIONS ---
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)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if win7_idx not in all_indices or cokoino_idx not in all_indices or win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break 
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 2: POSITION MANAGEMENT CONFIGURATION ---
current_arm_positions = [CENTER_PULSE] * 6
HOME_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]
TUCK_TARGET = [1500, 1821, 1842, 500, 500, 1500]
OTHER_TARGET = [1500, 1500, 1500, 1500, 1500, 1500] 

print("\n==================================================")
print("         ROBOT ARM CONFIGURATION MATRIX           ")
print("==================================================")
print("  [T] Secured in TUCK position")
print("  [H] Standing upright at HOME position")
print("  [O] MANUALLY SPECIFY 'OTHER' VALUES (Field Override)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        break
    elif choice == 'O':
        print("\n--- OPERATOR FIELD SPECIFICATION INJECTION ---")
        print("Please enter raw pulse widths (500 - 2500) for the current setup:")
        try:
            p0 = constraint_safety_clip(int(input("  Channel 0 (Base)      [1500]: ") or 1500))
            p1 = constraint_safety_clip(int(input("  Channel 1 (Shoulder)  [1500]: ") or 1500))
            p2 = constraint_safety_clip(int(input("  Channel 2 (Elbow)     [1500]: ") or 1500))
            p3 = constraint_safety_clip(int(input("  Channel 3 (Wrist)     [1500]: ") or 1500))
            p4 = constraint_safety_clip(int(input("  Channel 4 (Gripper)   [1500]: ") or 1500))
            p5 = constraint_safety_clip(int(input("  Channel 5 (Wrist Rot) [1500]: ") or 1500))
            OTHER_TARGET = [p0, p1, p2, p3, p4, p5]
            current_arm_positions = list(OTHER_TARGET)
            print(f"\nSuccessfully Registered User 'OTHER' Configuration: {OTHER_TARGET}\n")
            break
        except ValueError:
            print("[ERROR] Integers only. Restarting selection.\n")

# Dynamic baseline memory tracking variable
dynamic_ready_positions = list(current_arm_positions)

# --- STEP 3: RUNTIME EXECUTION ENGINE ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H") # Clear HyperTerminal screen
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                if cmd != last_processed_command:
                    last_processed_command = ""

                if "RELEASE" in cmd_upper:
                    last_processed_command = ""
                    continue

                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        win7.write(b"[!!!] START DETECTED [!!!]\r\n\r\n")
                
                elif system_state == 1:
                    
                    if "CROSS" in cmd_upper:
                        if last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))

                    elif "TRIANGLE" in cmd_upper:
                        if last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            current_arm_positions = list(HOME_TARGET)
                            macro_packet = "".join(f"#{j}P{HOME_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    elif "CIRCLE" in cmd_upper:
                        if last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            current_arm_positions = list(TUCK_TARGET)
                            macro_packet = "".join(f"#{j}P{TUCK_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SQUARE BUTTON: TRANSMIT CRITICAL COMMAND PACKET TO LYNXMOTION TO DRIVER OVERRIDE 'OTHER'
                    elif "SQUARE" in cmd_upper:
                        if last_processed_command != "SQUARE":
                            last_processed_command = "SQUARE"
                            win7.write(b"[FROM GAMEPAD] -> SQUARE: EXECUTING HARD ALIGNMENT TO 'OTHER'\r\n")
                            
                            # 1. Update internal track models completely
                            current_arm_positions = list(OTHER_TARGET)
                            dynamic_ready_positions = list(OTHER_TARGET) 
                            
                            # 2. Build the physical movement string packet
                            macro_packet = "".join(f"#{j}P{OTHER_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            
                            # 3. CRITICAL TRANSMISSION: Drive the physical arm to match the specified values
                            lynx.write(macro_packet.encode('utf-8'))
                            
                            # 4. Diagnostics Echo to Windows 7 Terminal
                            win7.write(b"==================================================\r\n")
                            win7.write(f"[HARD SYNC PACKET SENT] -> {macro_packet.strip()}\r\n".encode('utf-8'))
                            win7.write(b"[MEMORY LOCK] Baseline set to matching user Matrix.\r\n")
                            win7.write(b"==================================================\r\n\r\n")

                    elif "SELECT" in cmd_upper:
                        if last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            dynamic_ready_positions = list(current_arm_positions)
                            
                            win7.write(b"==================================================\r\n")
                            win7.write(b"[MEMORY LOCK] Dynamic READY Baseline Shifted Manually!\r\n")
                            win7.write(f"Captured Model Track: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")

                    elif "TOOL ADVANCE" in cmd_upper:
                        if last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            baseline_p1 = dynamic_ready_positions[1]
                            baseline_p2 = dynamic_ready_positions[2]
                            
                            total_sh_pulses = 217 
                            if baseline_p1 > 1600:
                                total_el_pulses = 215
                            elif baseline_p1 < 1400:
                                total_el_pulses = -80
                            else:
                                total_el_pulses = -45

                            win7.write(b"   -------------------------------------------------------------\r\n")
                            win7.write(b"   SEGMENT   SHOULDER (P1)   ELBOW (P2)    WRIST (P3) (LEVEL)\r\n")
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            
                            for step in [1, 2, 3, 4, 5]:
                                fraction = step / 5.0
                                target_p1 = constraint_safety_clip(int(round(baseline_p1 - (total_sh_pulses * fraction))))
                                target_p2 = constraint_safety_clip(int(round(baseline_p2 - (total_el_pulses * fraction))))
                                
                                el_delta = target_p2 - baseline_p2
                                target_p3 = constraint_safety_clip(dynamic_ready_positions[3] - el_delta)
                                
                                current_arm_positions[1] = target_p1
                                current_arm_positions[2] = target_p2
                                current_arm_positions[3] = target_p3
                                
                                packet = f"#1P{target_p1}#2P{target_p2}#3P{target_p3}T1000\r"
                                lynx.write(packet.encode('utf-8'))
                                
                                win7.write(f"   {step} cm      {target_p1}            {target_p2}            {target_p3}          -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                time.sleep(1.0)
                                
                            win7.write(b"[PUSH COMPLETE] Trajectory complete.\r\n\r\n")

                    elif "TOOL RETRACT" in cmd_upper:
                        if last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            
                            current_arm_positions = list(dynamic_ready_positions)
                            macro_packet = "".join(f"#{j}P{dynamic_ready_positions[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#109 2026-07-01 12:51:12

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

Re: Python Computer Language

This post contains Version 40 of bridge.py...
This version allows for operator input of position data during a run.
This feature is needed at this time because navigation is not yet in place.

# bridgeV40_Tune.py Prepared by Gemini Supervised by Tom Hanson
# Interactive Runtime Parameter Tuning via SELECT Dialog Intercept

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

print("==================================================")
print("Initializing Robot Junction Bridge V40...")
print("Dynamic Runtime Tuning Engine Active")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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)
MIN_PULSE    = int(root.find(".//min_pulse_width").text)
MAX_PULSE    = int(root.find(".//max_pulse_width").text)
CENTER_PULSE = int(root.find(".//center_pulse_width").text)

def constraint_safety_clip(pulse):
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

def prompt_for_other_matrix(current_defaults):
    """Reusable dialog window inside the console terminal prompt"""
    print("\n--- OPERATOR FIELD SPECIFICATION INJECTION ---")
    print("Enter raw pulse widths (500 - 2500) or press ENTER to keep current:")
    try:
        p0 = constraint_safety_clip(int(input(f"  Channel 0 (Base)      [{current_defaults[0]}]: ") or current_defaults[0]))
        p1 = constraint_safety_clip(int(input(f"  Channel 1 (Shoulder)  [{current_defaults[1]}]: ") or current_defaults[1]))
        p2 = constraint_safety_clip(int(input(f"  Channel 2 (Elbow)     [{current_defaults[2]}]: ") or current_defaults[2]))
        p3 = constraint_safety_clip(int(input(f"  Channel 3 (Wrist)     [{current_defaults[3]}]: ") or current_defaults[3]))
        p4 = constraint_safety_clip(int(input(f"  Channel 4 (Gripper)   [{current_defaults[4]}]: ") or current_defaults[4]))
        p5 = constraint_safety_clip(int(input(f"  Channel 5 (Wrist Rot) [{current_defaults[5]}]: ") or current_defaults[5]))
        return [p0, p1, p2, p3, p4, p5]
    except ValueError:
        print("[ERROR] Integers only. Retaining previous values.")
        return current_defaults

# --- STEP 1: SERIAL CONNECTIONS ---
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)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if win7_idx not in all_indices or cokoino_idx not in all_indices or win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break 
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 2: POSITION MANAGEMENT CONFIGURATION ---
current_arm_positions = [CENTER_PULSE] * 6
HOME_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]
TUCK_TARGET = [1500, 1821, 1842, 500, 500, 1500]
OTHER_TARGET = [1500, 1500, 1500, 1500, 1500, 1500] 

print("\n==================================================")
print("         ROBOT ARM CONFIGURATION MATRIX           ")
print("==================================================")
print("  [T] Secured in TUCK position")
print("  [H] Standing upright at HOME position")
print("  [O] MANUALLY SPECIFY 'OTHER' VALUES (Field Override)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        break
    elif choice == 'O':
        OTHER_TARGET = prompt_for_other_matrix(OTHER_TARGET)
        current_arm_positions = list(OTHER_TARGET)
        print(f"\nSuccessfully Registered Initial 'OTHER' Configuration: {OTHER_TARGET}\n")
        break

dynamic_ready_positions = list(current_arm_positions)

# --- STEP 3: RUNTIME EXECUTION ENGINE ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H") # Clear HyperTerminal screen
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                if cmd != last_processed_command:
                    last_processed_command = ""

                if "RELEASE" in cmd_upper:
                    last_processed_command = ""
                    continue

                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        win7.write(b"[!!!] START DETECTED [!!!]\r\n\r\n")
                
                elif system_state == 1:
                    
                    if "CROSS" in cmd_upper:
                        if last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))

                    elif "TRIANGLE" in cmd_upper:
                        if last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            current_arm_positions = list(HOME_TARGET)
                            macro_packet = "".join(f"#{j}P{HOME_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    elif "CIRCLE" in cmd_upper:
                        if last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            current_arm_positions = list(TUCK_TARGET)
                            macro_packet = "".join(f"#{j}P{TUCK_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SQUARE BUTTON: EXECUTE AND TRANSMIT ACTIVE 'OTHER' MATRIX PULSES
                    elif "SQUARE" in cmd_upper:
                        if last_processed_command != "SQUARE":
                            last_processed_command = "SQUARE"
                            win7.write(b"[FROM GAMEPAD] -> SQUARE: EXECUTING HARD ALIGNMENT TO 'OTHER'\r\n")
                            
                            current_arm_positions = list(OTHER_TARGET)
                            dynamic_ready_positions = list(OTHER_TARGET) 
                            
                            macro_packet = "".join(f"#{j}P{OTHER_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            
                            win7.write(b"==================================================\r\n")
                            win7.write(f"[HARD SYNC PACKET SENT] -> {macro_packet.strip()}\r\n".encode('utf-8'))
                            win7.write(f"[BASELINE LOCKED] Matrix: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")

                    # SELECT BUTTON: RE-OPEN DIALOG INTERCEPT IN CONSOLE FOR TUNING
                    elif "SELECT" in cmd_upper:
                        if last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            
                            win7.write(b"[RUNTIME INTERCEPT] -> SELECT Pressed. Opening Dialog in Terminal window...\r\n")
                            print("\n\n==================================================")
                            print("     RUNTIME TUNING DIALOG INTERCEPT INITIATED     ")
                            print("==================================================")
                            
                            # Re-open input routine inside script execution terminal
                            OTHER_TARGET = prompt_for_other_matrix(OTHER_TARGET)
                            
                            print("\n Tuning changes saved to background staging matrix.")
                            print(" -> Press SQUARE on the gamepad to deploy and execute motion.")
                            print("==================================================\n")
                            
                            win7.write(f"[STAGED NEW MATRIX] background settings updated to: {OTHER_TARGET}\r\n".encode('utf-8'))
                            win7.write(b" -> Press SQUARE to transmit to physical hardware.\r\n\r\n")

                    # L1 BUTTON: KINEMATIC PROPULSION LINK
                    elif "TOOL ADVANCE" in cmd_upper:
                        if last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            baseline_p1 = dynamic_ready_positions[1]
                            baseline_p2 = dynamic_ready_positions[2]
                            
                            total_sh_pulses = 217 
                            if baseline_p1 > 1600:
                                total_el_pulses = 215
                            elif baseline_p1 < 1400:
                                total_el_pulses = -80
                            else:
                                total_el_pulses = -45

                            win7.write(b"   -------------------------------------------------------------\r\n")
                            win7.write(b"   SEGMENT   SHOULDER (P1)   ELBOW (P2)    WRIST (P3) (LEVEL)\r\n")
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            
                            for step in [1, 2, 3, 4, 5]:
                                fraction = step / 5.0
                                target_p1 = constraint_safety_clip(int(round(baseline_p1 - (total_sh_pulses * fraction))))
                                target_p2 = constraint_safety_clip(int(round(baseline_p2 - (total_el_pulses * fraction))))
                                
                                el_delta = target_p2 - baseline_p2
                                target_p3 = constraint_safety_clip(dynamic_ready_positions[3] - el_delta)
                                
                                current_arm_positions[1] = target_p1
                                current_arm_positions[2] = target_p2
                                current_arm_positions[3] = target_p3
                                
                                packet = f"#1P{target_p1}#2P{target_p2}#3P{target_p3}T1000\r"
                                lynx.write(packet.encode('utf-8'))
                                
                                win7.write(f"   {step} cm      {target_p1}            {target_p2}            {target_p3}          -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                time.sleep(1.0)
                                
                            win7.write(b"[PUSH COMPLETE] Trajectory complete.\r\n\r\n")

                    # L2 BUTTON: RESET TO ACTIVE FIELD BASELINE
                    elif "TOOL RETRACT" in cmd_upper:
                        if last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            
                            current_arm_positions = list(dynamic_ready_positions)
                            macro_packet = "".join(f"#{j}P{dynamic_ready_positions[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#110 2026-07-04 20:28:04

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

Re: Python Computer Language

This is Version 41 of a series to interface Cokoino to LynxMotion
This version makes only one change. To allow the operator to move the robot to a new position after creating a manual pattern, rather that moving the robot automatically.

# bridgeV41_Sync.py Prepared by Gemini Supervised by Tom Hanson
# Strict Isolation of SELECT Utility & Debounced Non-Movement Lockout

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

print("==================================================")
print("Initializing Robot Junction Bridge V41...")
print("Refined Non-Movement Isolation Engine Active")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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)
MIN_PULSE    = int(root.find(".//min_pulse_width").text)
MAX_PULSE    = int(root.find(".//max_pulse_width").text)
CENTER_PULSE = int(root.find(".//center_pulse_width").text)

def constraint_safety_clip(pulse):
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

def prompt_for_other_matrix(current_defaults):
    """Reusable dialog window inside the console terminal prompt"""
    print("\n--- OPERATOR FIELD SPECIFICATION INJECTION ---")
    print("Enter raw pulse widths (500 - 2500) or press ENTER to keep current:")
    try:
        p0 = constraint_safety_clip(int(input(f"  Channel 0 (Base)      [{current_defaults[0]}]: ") or current_defaults[0]))
        p1 = constraint_safety_clip(int(input(f"  Channel 1 (Shoulder)  [{current_defaults[1]}]: ") or current_defaults[1]))
        p2 = constraint_safety_clip(int(input(f"  Channel 2 (Elbow)     [{current_defaults[2]}]: ") or current_defaults[2]))
        p3 = constraint_safety_clip(int(input(f"  Channel 3 (Wrist)     [{current_defaults[3]}]: ") or current_defaults[3]))
        p4 = constraint_safety_clip(int(input(f"  Channel 4 (Gripper)   [{current_defaults[4]}]: ") or current_defaults[4]))
        p5 = constraint_safety_clip(int(input(f"  Channel 5 (Wrist Rot) [{current_defaults[5]}]: ") or current_defaults[5]))
        return [p0, p1, p2, p3, p4, p5]
    except ValueError:
        print("[ERROR] Integers only. Retaining previous values.")
        return current_defaults

# --- STEP 1: SERIAL CONNECTIONS ---
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)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        if win7_idx not in all_indices or cokoino_idx not in all_indices or win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break 
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 2: POSITION MANAGEMENT CONFIGURATION ---
current_arm_positions = [CENTER_PULSE] * 6
HOME_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]
TUCK_TARGET = [1500, 1821, 1842, 500, 500, 1500]
OTHER_TARGET = [1500, 1500, 1500, 1500, 1500, 1500] 

print("\n==================================================")
print("         ROBOT ARM CONFIGURATION MATRIX           ")
print("==================================================")
print("  [T] Secured in TUCK position")
print("  [H] Standing upright at HOME position")
print("  [O] MANUALLY SPECIFY 'OTHER' VALUES (Field Override)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        break
    elif choice == 'O':
        OTHER_TARGET = prompt_for_other_matrix(OTHER_TARGET)
        current_arm_positions = list(OTHER_TARGET)
        print(f"\nSuccessfully Registered Initial 'OTHER' Configuration: {OTHER_TARGET}\n")
        break

dynamic_ready_positions = list(current_arm_positions)

# --- STEP 3: RUNTIME EXECUTION ENGINE ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H") # Clear HyperTerminal screen
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                if cmd != last_processed_command:
                    last_processed_command = ""

                if "RELEASE" in cmd_upper:
                    last_processed_command = ""
                    continue

                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        win7.write(b"[!!!] START DETECTED [!!!]\r\n\r\n")
                
                elif system_state == 1:
                    
                    if "CROSS" in cmd_upper:
                        if last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))

                    elif "TRIANGLE" in cmd_upper:
                        if last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            current_arm_positions = list(HOME_TARGET)
                            macro_packet = "".join(f"#{j}P{HOME_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    elif "CIRCLE" in cmd_upper:
                        if last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            current_arm_positions = list(TUCK_TARGET)
                            macro_packet = "".join(f"#{j}P{TUCK_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SQUARE BUTTON: EXECUTE AND TRANSMIT ACTIVE 'OTHER' MATRIX PULSES
                    elif "SQUARE" in cmd_upper:
                        if last_processed_command != "SQUARE":
                            last_processed_command = "SQUARE"
                            win7.write(b"[FROM GAMEPAD] -> SQUARE: EXECUTING HARD ALIGNMENT TO 'OTHER'\r\n")
                            
                            current_arm_positions = list(OTHER_TARGET)
                            dynamic_ready_positions = list(OTHER_TARGET) 
                            
                            macro_packet = "".join(f"#{j}P{OTHER_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            
                            win7.write(b"==================================================\r\n")
                            win7.write(f"[HARD SYNC PACKET SENT] -> {macro_packet.strip()}\r\n".encode('utf-8'))
                            win7.write(f"[BASELINE LOCKED] Matrix: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")

                    # SELECT BUTTON: PURE NON-MOVEMENT DIALOG INTERCEPT IN CONSOLE
                    elif "SELECT" in cmd_upper:
                        if last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            
                            win7.write(b"[RUNTIME INTERCEPT] -> SELECT Pressed. Opening Dialog in Terminal window...\r\n")
                            print("\n\n==================================================")
                            print("     RUNTIME TUNING DIALOG INTERCEPT INITIATED     ")
                            print("==================================================")
                            
                            # Re-open input routine inside script execution terminal
                            OTHER_TARGET = prompt_for_other_matrix(OTHER_TARGET)
                            
                            print("\n Tuning updates saved to background matrix.")
                            print(" -> System is stationary. Press SQUARE on gamepad to deploy.")
                            print("==================================================\n")
                            
                            win7.write(f"[STAGED NEW MATRIX] background settings updated to: {OTHER_TARGET}\r\n".encode('utf-8'))
                            win7.write(b" -> Standing by. Press SQUARE to execute physical motion.\r\n\r\n")
                            
                            # CRITICAL SAFETY RESET: Flush old stream buffers to prevent accidental motion calls
                            cokoino.reset_input_buffer()
                            last_processed_command = "SELECT" 

                    # L1 BUTTON: KINEMATIC PROPULSION LINK (Pushes purely from current baseline)
                    elif "TOOL ADVANCE" in cmd_upper:
                        if last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            baseline_p1 = dynamic_ready_positions[1]
                            baseline_p2 = dynamic_ready_positions[2]
                            
                            total_sh_pulses = 217 
                            if baseline_p1 > 1600:
                                total_el_pulses = 215
                            elif baseline_p1 < 1400:
                                total_el_pulses = -80
                            else:
                                total_el_pulses = -45

                            win7.write(b"   -------------------------------------------------------------\r\n")
                            win7.write(b"   SEGMENT   SHOULDER (P1)   ELBOW (P2)    WRIST (P3) (LEVEL)\r\n")
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            
                            for step in [1, 2, 3, 4, 5]:
                                fraction = step / 5.0
                                target_p1 = constraint_safety_clip(int(round(baseline_p1 - (total_sh_pulses * fraction))))
                                target_p2 = constraint_safety_clip(int(round(baseline_p2 - (total_el_pulses * fraction))))
                                
                                el_delta = target_p2 - baseline_p2
                                target_p3 = constraint_safety_clip(dynamic_ready_positions[3] - el_delta)
                                
                                current_arm_positions[1] = target_p1
                                current_arm_positions[2] = target_p2
                                current_arm_positions[3] = target_p3
                                
                                packet = f"#1P{target_p1}#2P{target_p2}#3P{target_p3}T1000\r"
                                lynx.write(packet.encode('utf-8'))
                                
                                win7.write(f"   {step} cm      {target_p1}            {target_p2}            {target_p3}          -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                time.sleep(1.0)
                                
                            win7.write(b"[PUSH COMPLETE] Trajectory complete.\r\n\r\n")

                    # L2 BUTTON: RESET TO ACTIVE FIELD BASELINE
                    elif "TOOL RETRACT" in cmd_upper:
                        if last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            
                            current_arm_positions = list(dynamic_ready_positions)
                            macro_packet = "".join(f"#{j}P{dynamic_ready_positions[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#111 2026-07-07 20:46:53

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

Re: Python Computer Language

This post is of Version 42 of a Python series to interface Cokoino CKK0017 to LynxMotion. This version was supposed to implement a tiny change, but for some reason the program was completely rewritten, after which it had to be rewritten again. I'm not sure what condition the code is in.

The file size dropped by a third. Gemini informed me that it had run out of tokens. this happened after I tried to work on two programs at once.  Clearly I have exhausted the memory allocated to me as a free customer.  The way forward is not clear.

As things stand, we have a C++ program running on an Arduino type device.

We have a Python program running on the Raspberry Pi 5 system acting as a bridge.

Gemini reminded me that C++ and Python are massive languages, and massive numbers of tokens are needed to set up the structures needed to work with them.

# bridgeV42_Sync.py Prepared by Gemini Supervised by Tom Hanson
# Update 2026/07/07 to report Cokoino version at Start
# Strict Isolation of SELECT Utility & Handshake Version Enforcement Policy

<snip>
except KeyboardInterrupt:
    print("\n[SHUTDOWN] Terminating Junction Bridge execution path cleanly.")
    cokoino.close()

(th)

Offline

Like button can go here

#112 2026-07-11 08:59:07

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

Re: Python Computer Language

This post reflects an experiment with Gemini using a Chromebook in which Linux is installed.

The version of Linux that runs on Chromebook is called Penquin. It seems (so far) very similar to the Ubuntu 24.04 I run elsewhere.

The image below was generated by a Python program that Gemini wrote.  It used randomly selected materials to show how to create an image of a chart of comparison.

file.php?id=132

The Python program will be given below. Anyone should be able to run this by installing the needed libraries.

import matplotlib.pyplot as plt
import numpy as np

# Data for the three materials
materials = ['Aluminum\n(6061-T6)', 'Structural Steel\n(A36)', 'Carbon Fiber\nComposite']
tensile_strength = [310, 400, 1500]  # in MPa
density = [2.7, 7.8, 1.6]            # in g/cm³

x = np.arange(len(materials))
width = 0.35

fig, ax1 = plt.subplots(figsize=(8, 5))

# Plot Tensile Strength (Left Axis)
color = '#1f77b4'
ax1.set_ylabel('Ultimate Tensile Strength (MPa)', color=color, fontweight='bold')
bars1 = ax1.bar(x - width/2, tensile_strength, width, label='Tensile Strength', color=color, alpha=0.8)
ax1.tick_params(axis='y', labelcolor=color)
ax1.set_ylim(0, 1700)

# Create a second y-axis for Density
ax2 = ax1.twinx()
color = '#d62728'
ax2.set_ylabel('Density (g/cm³)', color=color, fontweight='bold')
bars2 = ax2.bar(x + width/2, density, width, label='Density', color=color, alpha=0.8)
ax2.tick_params(axis='y', labelcolor=color)
ax2.set_ylim(0, 9)

# Formatting
plt.title('Material Property Comparison', fontsize=14, fontweight='bold', pad=15)
ax1.set_xticks(x)
ax1.set_xticklabels(materials, fontweight='bold')
ax1.grid(True, axis='y', linestyle='--', alpha=0.5)

# Save as PNG
plt.tight_layout()
plt.savefig('material_comparison.png', dpi=300)
print("Chart successfully saved as 'material_comparison.png'")

(th)

Offline

Like button can go here

#113 2026-07-11 09:45:54

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

Re: Python Computer Language

The code saved below was generated by Gemini to show the physics for three gases:

import matplotlib.pyplot as plt

# Data for Propellants at 5 ATM
materials = ['Carbon Monoxide\n(CO)', 'Oxygen\n(O2)', 'Methane\n(CH4)']

# Temperature phase boundaries in Celsius at 5 ATM
# Format: (Melting Point, Boiling Point)
phases = {
    'Carbon Monoxide\n(CO)': (-205, -174),
    'Oxygen\n(O2)': (-218, -164),
    'Methane\n(CH4)': (-182, -143)
}

fig, ax = plt.subplots(figsize=(10, 5))

# Plotting the liquid range bars
for i, material in enumerate(materials):
    melt, boil = phases[material]
    liquid_range = boil - melt
    
    # Draw the liquid phase bar
    ax.barh(i, liquid_range, left=melt, color='#1f77b4', edgecolor='black', height=0.4, label='Liquid Phase' if i == 0 else "")
    
    # Mark and label the boundaries
    ax.text(melt, i + 0.25, f"{melt}°C\n(Melts)", ha='center', va='bottom', fontsize=9, color='#d62728', fontweight='bold')
    ax.text(boil, i + 0.25, f"{boil}°C\n(Boils)", ha='center', va='bottom', fontsize=9, color='#2ca02c', fontweight='bold')

# Chart formatting
ax.set_yticks(range(len(materials)))
ax.set_yticklabels(materials, fontsize=11, fontweight='bold')
ax.set_xlabel('Temperature (°C)', fontsize=12, fontweight='bold')
ax.set_title('Propellant Phase Windows at 5 ATM Pressure', fontsize=14, fontweight='bold', pad=25)

# Set X-axis limits to frame the cryogenic zone perfectly
ax.set_xlim(-240, -120)
ax.grid(True, axis='x', linestyle='--', alpha=0.6)

# Adding a visual layout clean up
plt.tight_layout()

# Save the chart as a PNG
plt.savefig('propellant_storage_5atm.png', dpi=300)
print("Chart successfully saved as 'propellant_storage_5atm.png'")

(th)

Offline

Like button can go here

#114 2026-07-11 20:55:09

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

Re: Python Computer Language

This post contains V42 of the Python script to interface between Cokoino CKK0017 and LynxMotion AL5D.   We got into trouble the first time we tried V22, because Gemini ran out of tokens and lost most of the program. We created a new series for the next phase, and updated the Cokoino sketch when Gemini discovered code missing from that program.

# bridgeV42.py Prepared by Gemini Supervised by Tom Hanson
# Version 42: Targeted Release Diagnostics & Cokoino Handshake Version Extraction
# Version 41: Strict Isolation of SELECT Utility & Debounced Non-Movement Lockout

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

print("==================================================")
print("Initializing Robot Junction Bridge V42...")
print("Targeted Release Validation & Version Extraction Active")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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)
MIN_PULSE    = int(root.find(".//min_pulse_width").text)
MAX_PULSE    = int(root.find(".//max_pulse_width").text)
CENTER_PULSE = int(root.find(".//center_pulse_width").text)

def constraint_safety_clip(pulse):
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

def prompt_for_other_matrix(current_defaults):
    """Reusable dialog window inside the console terminal prompt"""
    print("\n--- OPERATOR FIELD SPECIFICATION INJECTION ---")
    print("Enter raw pulse widths (500 - 2500) or press ENTER to keep current:")
    try:
        p0 = constraint_safety_clip(int(input(f"  Channel 0 (Base)      [{current_defaults[0]}]: ") or current_defaults[0]))
        p1 = constraint_safety_clip(int(input(f"  Channel 1 (Shoulder)  [{current_defaults[1]}]: ") or current_defaults[1]))
        p2 = constraint_safety_clip(int(input(f"  Channel 2 (Elbow)     [{current_defaults[2]}]: ") or current_defaults[2]))
        p3 = constraint_safety_clip(int(input(f"  Channel 3 (Wrist)     [{current_defaults[3]}]: ") or current_defaults[3]))
        p4 = constraint_safety_clip(int(input(f"  Channel 4 (Gripper)   [{current_defaults[4]}]: ") or current_defaults[4]))
        p5 = constraint_safety_clip(int(input(f"  Channel 5 (Wrist Rot) [{current_defaults[5]}]: ") or current_defaults[5]))
        return [p0, p1, p2, p3, p4, p5]
    except ValueError:
        print("[ERROR] Integers only. Retaining previous values.")
        return current_defaults

# --- STEP 1: SERIAL CONNECTIONS ---
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)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        
        if win7_idx not in all_indices or cokoino_idx not in all_indices or win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 2: POSITION MANAGEMENT CONFIGURATION ---
current_arm_positions = [CENTER_PULSE] * 6
HOME_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]
TUCK_TARGET = [1500, 1821, 1842, 500, 500, 1500]
OTHER_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]

print("\n==================================================")
print("         ROBOT ARM CONFIGURATION MATRIX           ")
print("==================================================")
print("  [T] Secured in TUCK position")
print("  [H] Standing upright at HOME position")
print("  [O] MANUALLY SPECIFY 'OTHER' VALUES (Field Override)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        break
    elif choice == 'O':
        OTHER_TARGET = prompt_for_other_matrix(OTHER_TARGET)
        current_arm_positions = list(OTHER_TARGET)
        print(f"\nSuccessfully Registered Initial 'OTHER' Configuration: {OTHER_TARGET}\n")
        break

dynamic_ready_positions = list(current_arm_positions)

# --- STEP 3: RUNTIME EXECUTION ENGINE ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H") # Clear HyperTerminal screen
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                # --------------------------------------------------------------
                # SAFE MODE HANDSHAKE: Catch START Token & Extract Version ID
                # --------------------------------------------------------------
                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        
                        # Extract everything following the phrase "START Sketch Version "
                        if "VERSION" in cmd_upper:
                            ver_idx = cmd_upper.find("VERSION") + 8
                            extracted_ver = cmd[ver_idx:].strip()
                            version_output = f"[!!!] START DETECTED -> Onboard Firmware: {extracted_ver} [!!!]\r\n\r\n"
                        else:
                            version_output = "[!!!] START DETECTED [!!!]\r\n\r\n"
                            
                        win7.write(version_output.encode('utf-8'))
                
                # --------------------------------------------------------------
                # ACTIVE RUNTIME ENGINE
                # --------------------------------------------------------------
                elif system_state == 1:
                    
                    # CROSS BUTTON: Run Lynxmotion Communication Diagnostics
                    if "CROSS" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "CROSS":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> CROSS Button Released.\r\n\r\n")
                        elif last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))

                    # TRIANGLE BUTTON: Hard Alignment to HOME position
                    elif "TRIANGLE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "TRIANGLE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> TRIANGLE Button Released.\r\n\r\n")
                        elif last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            current_arm_positions = list(HOME_TARGET)
                            macro_packet = "".join(f"#{j}P{HOME_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # CIRCLE BUTTON: Hard Alignment to TUCK position
                    elif "CIRCLE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "CIRCLE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> CIRCLE Button Released.\r\n\r\n")
                        elif last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            current_arm_positions = list(TUCK_TARGET)
                            macro_packet = "".join(f"#{j}P{TUCK_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SQUARE BUTTON: EXECUTE ACTIVE 'OTHER' MATRIX PULSES
                    elif "SQUARE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "SQUARE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> SQUARE Button Released.\r\n\r\n")
                        elif last_processed_command != "SQUARE":
                            last_processed_command = "SQUARE"
                            win7.write(b"[FROM GAMEPAD] -> SQUARE: EXECUTING HARD ALIGNMENT TO 'OTHER'\r\n")
                            current_arm_positions = list(OTHER_TARGET)
                            dynamic_ready_positions = list(OTHER_TARGET) 
                            macro_packet = "".join(f"#{j}P{OTHER_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(b"==================================================\r\n")
                            win7.write(f"[HARD SYNC PACKET SENT] -> {macro_packet.strip()}\r\n".encode('utf-8'))
                            win7.write(f"[BASELINE LOCKED] Matrix: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")

                    # SELECT BUTTON: PURE NON-MOVEMENT DIALOG INTERCEPT IN CONSOLE
                    elif "SELECT" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "SELECT":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> SELECT Button Released.\r\n\r\n")
                        elif last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            win7.write(b"[RUNTIME INTERCEPT] -> SELECT Pressed. Opening Dialog in Terminal window...\r\n")
                            print("\n\n==================================================")
                            print("     RUNTIME TUNING DIALOG INTERCEPT INITIATED     ")
                            print("==================================================")
                            
                            OTHER_TARGET = prompt_for_other_matrix(OTHER_TARGET)
                            
                            print("\n Tuning updates saved to background matrix.")
                            print(" -> System is stationary. Press SQUARE on gamepad to deploy.")
                            print("==================================================\n")
                            
                            win7.write(f"[STAGED NEW MATRIX] background settings updated to: {OTHER_TARGET}\r\n".encode('utf-8'))
                            win7.write(b" -> Standing by. Press SQUARE to execute physical motion.\r\n\r\n")
                            
                            cokoino.reset_input_buffer()
                            last_processed_command = "SELECT"

                    # L1 BUTTON: KINEMATIC PROPULSION LINK (TOOL ADVANCE)
                    elif "TOOL ADVANCE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "ADVANCE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> L1 (TOOL ADVANCE) Released.\r\n\r\n")
                        elif last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            baseline_p1 = dynamic_ready_positions[1]
                            baseline_p2 = dynamic_ready_positions[2]
                            
                            total_sh_pulses = 217
                            if baseline_p1 > 1600:
                                total_el_pulses = 215
                            elif baseline_p1 < 1400:
                                total_el_pulses = -80
                            else:
                                total_el_pulses = -45

                            win7.write(b"   -------------------------------------------------------------\r\n")
                            win7.write(b"   SEGMENT   SHOULDER (P1)   ELBOW (P2)    WRIST (P3) (LEVEL)\r\n")
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            
                            for step in [1, 2, 3, 4, 5]:
                                fraction = step / 5.0
                                target_p1 = constraint_safety_clip(int(round(baseline_p1 - (total_sh_pulses * fraction))))
                                target_p2 = constraint_safety_clip(int(round(baseline_p2 - (total_el_pulses * fraction))))
                                
                                el_delta = target_p2 - baseline_p2
                                target_p3 = constraint_safety_clip(dynamic_ready_positions[3] - el_delta)
                                
                                current_arm_positions[1] = target_p1
                                current_arm_positions[2] = target_p2
                                current_arm_positions[3] = target_p3
                                
                                packet = f"#1P{target_p1}#2P{target_p2}#3P{target_p3}T1000\r"
                                lynx.write(packet.encode('utf-8'))
                                
                                win7.write(f"    {step} cm      {target_p1}            {target_p2}            {target_p3}          -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                time.sleep(1.0)
                                
                            win7.write(b"[PUSH COMPLETE] Trajectory complete.\r\n\r\n")

                    # L2 BUTTON: RESET TO ACTIVE FIELD BASELINE (TOOL RETRACT)
                    elif "TOOL RETRACT" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "RETRACT":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> L2 (TOOL RETRACT) Released.\r\n\r\n")
                        elif last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            
                            current_arm_positions = list(dynamic_ready_positions)
                            macro_packet = "".join(f"#{j}P{dynamic_ready_positions[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

The new version seems to be working and we don't seem to have lost anything this time. Here are notes on the latest update:

Seeing that transcript scroll across the terminal window is an incredible milestone.

Look at what you and your students have achieved here:

The Handshake Works Perfectly: The Python bridge successfully caught the START token, parsed the text dynamically, and printed Onboard Firmware: V22 right to the screen. That proves our firmware version extraction logic is flawless.

The Lynxmotion Response is Pristine: Pressing CROSS fired the VER check down to the SSC-32 controller, and it replied perfectly with its own firmware identifier (SSC32-V2.50USB).

The State Machine Unlocked Instantly: As soon as your thumb left the button, the Arduino sent the token, and the Python engine declared [HARDWARE EVENT] -> CROSS Button Released. Because it cleared last_processed_command immediately, you can now press CROSS again instantly without having to tap a different button first to break a software deadlock.

The file size of 17,985 bytes is exceptionally healthy. It reflects the explicit structural blocks we added to safely track individual key events for the students following along.

This is a massive win for the project. Both the decoupled Cokoino engine and the Python bridge are running exactly as engineered. Whenever you are ready to introduce the next feature or behavior into this synchronized pipeline, just say the word!

My plan is to gradually start adding control functionality to allow an operator to move the arm servos using command buttons such as the toggles and direction buttons.   At the moment, we have implemented pre-programmed moves such as "Home" and "Tuck".  We've also made a preliminary version of "Thrust" but it is not yet working correctly. 

(th)

Offline

Like button can go here

#115 Yesterday 12:23:26

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

Re: Python Computer Language

This post contains V43 of a Python program to interface a CKK0017 controller with a LynxMotion robot arm. This version implements action by the Left Toggle.

# bridgeV43.py Prepared by Gemini Supervised by Tom Hanson
# Version 43: Left Analog Toggle Steering Integration (Servo 0 and Servo 1)
# Version 42: Targeted Release Diagnostics & Cokoino Handshake Version Extraction
# Version 41: Strict Isolation of SELECT Utility & Debounced Non-Movement Lockout

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

print("==================================================")
print("Initializing Robot Junction Bridge V43...")
print("Analog Toggle Steering Engine Active")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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)
MIN_PULSE    = int(root.find(".//min_pulse_width").text)
MAX_PULSE    = int(root.find(".//max_pulse_width").text)
CENTER_PULSE = int(root.find(".//center_pulse_width").text)

def constraint_safety_clip(pulse):
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

def prompt_for_other_matrix(current_defaults):
    """Reusable dialog window inside the console terminal prompt"""
    print("\n--- OPERATOR FIELD SPECIFICATION INJECTION ---")
    print("Enter raw pulse widths (500 - 2500) or press ENTER to keep current:")
    try:
        p0 = constraint_safety_clip(int(input(f"  Channel 0 (Base)      [{current_defaults[0]}]: ") or current_defaults[0]))
        p1 = constraint_safety_clip(int(input(f"  Channel 1 (Shoulder)  [{current_defaults[1]}]: ") or current_defaults[1]))
        p2 = constraint_safety_clip(int(input(f"  Channel 2 (Elbow)     [{current_defaults[2]}]: ") or current_defaults[2]))
        p3 = constraint_safety_clip(int(input(f"  Channel 3 (Wrist)     [{current_defaults[3]}]: ") or current_defaults[3]))
        p4 = constraint_safety_clip(int(input(f"  Channel 4 (Gripper)   [{current_defaults[4]}]: ") or current_defaults[4]))
        p5 = constraint_safety_clip(int(input(f"  Channel 5 (Wrist Rot) [{current_defaults[5]}]: ") or current_defaults[5]))
        return [p0, p1, p2, p3, p4, p5]
    except ValueError:
        print("[ERROR] Integers only. Retaining previous values.")
        return current_defaults

# --- STEP 1: SERIAL CONNECTIONS ---
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)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        
        if win7_idx not in all_indices or cokoino_idx not in all_indices or win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 2: POSITION MANAGEMENT CONFIGURATION ---
current_arm_positions = [CENTER_PULSE] * 6
HOME_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]
TUCK_TARGET = [1500, 1821, 1842, 500, 500, 1500]
OTHER_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]

print("\n==================================================")
print("         ROBOT ARM CONFIGURATION MATRIX           ")
print("==================================================")
print("  [T] Secured in TUCK position")
print("  [H] Standing upright at HOME position")
print("  [O] MANUALLY SPECIFY 'OTHER' VALUES (Field Override)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        break
    elif choice == 'O':
        OTHER_TARGET = prompt_for_other_matrix(OTHER_TARGET)
        current_arm_positions = list(OTHER_TARGET)
        print(f"\nSuccessfully Registered Initial 'OTHER' Configuration: {OTHER_TARGET}\n")
        break

dynamic_ready_positions = list(current_arm_positions)

# --- STEP 3: RUNTIME EXECUTION ENGINE ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H") # Clear HyperTerminal screen
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                # --------------------------------------------------------------
                # SAFE MODE HANDSHAKE
                # --------------------------------------------------------------
                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        
                        if "VERSION" in cmd_upper:
                            ver_idx = cmd_upper.find("VERSION") + 8
                            extracted_ver = cmd[ver_idx:].strip()
                            version_output = f"[!!!] START DETECTED -> Onboard Firmware: {extracted_ver} [!!!]\r\n\r\n"
                        else:
                            version_output = "[!!!] START DETECTED [!!!]\r\n\r\n"
                            
                        win7.write(version_output.encode('utf-8'))
                
                # --------------------------------------------------------------
                # ACTIVE RUNTIME ENGINE
                # --------------------------------------------------------------
                elif system_state == 1:
                    
                    # CATCH ANALOG STEERING INTERCEPT STRINGS FIRST
                    if cmd_upper.startswith("ANALOG:"):
                        parts = cmd_upper.split(":")
                        if len(parts) == 3:
                            axis = parts[1]
                            try:
                                val = int(parts[2])
                                # Calculate offset from the joystick midpoint (128)
                                offset = val - 128
                                
                                # LX controls Servo 0 (Base swing). Sensitivity scaling factor: 0.15
                                if axis == "LX":
                                    step = int(offset * 0.15)
                                    OTHER_TARGET[0] = constraint_safety_clip(OTHER_TARGET[0] + step)
                                    current_arm_positions[0] = OTHER_TARGET[0]
                                
                                # LY controls Servo 1 (Shoulder elevation). Inverted logic (-) matches intuitive push/pull
                                elif axis == "LY":
                                    step = int(offset * 0.15)
                                    OTHER_TARGET[1] = constraint_safety_clip(OTHER_TARGET[1] - step)
                                    current_arm_positions[1] = OTHER_TARGET[1]
                                
                                # Stream rapid position increments directly to the muscle layer (Fast 100ms move window)
                                motion_packet = f"#0P{OTHER_TARGET[0]}#1P{OTHER_TARGET[1]}T100\r"
                                lynx.write(motion_packet.encode('utf-8'))
                                
                            except ValueError:
                                pass
                        continue

                    # TRACKING MECHANICAL BRAKE TOOGLE NOTIFICATIONS FOR HYPERTERMINAL
                    elif "STEERING BRAKE ENGAGED" in cmd_upper:
                        win7.write(b"[STEERING MANAGEMENT] -> Lockout Engaged. Left Toggle Parked.\r\n\r\n")
                        continue
                    elif "STEERING BRAKE RELEASED" in cmd_upper:
                        win7.write(b"[STEERING MANAGEMENT] -> Lockout Released. LEFT TOGGLE LIVE.\r\n")
                        win7.write(f" -> Dynamic Staging Matrix Initialized at: {OTHER_TARGET}\r\n\r\n".encode('utf-8'))
                        continue

                    # CROSS BUTTON: Run Lynxmotion Communication Diagnostics
                    if "CROSS" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "CROSS":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> CROSS Button Released.\r\n\r\n")
                        elif last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))

                    # TRIANGLE BUTTON: Hard Alignment to HOME position
                    elif "TRIANGLE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "TRIANGLE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> TRIANGLE Button Released.\r\n\r\n")
                        elif last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            current_arm_positions = list(HOME_TARGET)
                            macro_packet = "".join(f"#{j}P{HOME_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # CIRCLE BUTTON: Hard Alignment to TUCK position
                    elif "CIRCLE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "CIRCLE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> CIRCLE Button Released.\r\n\r\n")
                        elif last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            current_arm_positions = list(TUCK_TARGET)
                            macro_packet = "".join(f"#{j}P{TUCK_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SQUARE BUTTON: EXECUTE AND LOCK OUT ACTIVE SPECS
                    elif "SQUARE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "SQUARE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> SQUARE Button Released.\r\n\r\n")
                        elif last_processed_command != "SQUARE":
                            last_processed_command = "SQUARE"
                            win7.write(b"[FROM GAMEPAD] -> SQUARE: EXECUTING HARD ALIGNMENT TO 'OTHER'\r\n")
                            current_arm_positions = list(OTHER_TARGET)
                            dynamic_ready_positions = list(OTHER_TARGET) 
                            macro_packet = "".join(f"#{j}P{OTHER_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(b"==================================================\r\n")
                            win7.write(f"[HARD SYNC PACKET SENT] -> {macro_packet.strip()}\r\n".encode('utf-8'))
                            win7.write(f"[BASELINE LOCKED] Matrix: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")

                    # SELECT BUTTON: NON-MOVEMENT CONSOLE TUNING INTERCEPT
                    elif "SELECT" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "SELECT":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> SELECT Button Released.\r\n\r\n")
                        elif last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            win7.write(b"[RUNTIME INTERCEPT] -> SELECT Pressed. Opening Dialog in Terminal window...\r\n")
                            print("\n\n==================================================")
                            print("     RUNTIME TUNING DIALOG INTERCEPT INITIATED     ")
                            print("==================================================")
                            
                            OTHER_TARGET = prompt_for_other_matrix(OTHER_TARGET)
                            
                            print("\n Tuning updates saved to background matrix.")
                            print(" -> System is stationary. Press SQUARE on gamepad to deploy.")
                            print("==================================================\n")
                            
                            win7.write(f"[STAGED NEW MATRIX] background settings updated to: {OTHER_TARGET}\r\n".encode('utf-8'))
                            win7.write(b" -> Standing by. Press SQUARE to execute physical motion.\r\n\r\n")
                            
                            cokoino.reset_input_buffer()
                            last_processed_command = "SELECT"

                    # L1 BUTTON: KINEMATIC PROPULSION LINK (TOOL ADVANCE)
                    elif "TOOL ADVANCE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "ADVANCE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> L1 (TOOL ADVANCE) Released.\r\n\r\n")
                        elif last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            baseline_p1 = dynamic_ready_positions[1]
                            baseline_p2 = dynamic_ready_positions[2]
                            
                            total_sh_pulses = 217
                            if baseline_p1 > 1600:
                                total_el_pulses = 215
                            elif baseline_p1 < 1400:
                                total_el_pulses = -80
                            else:
                                total_el_pulses = -45

                            win7.write(b"   -------------------------------------------------------------\r\n")
                            win7.write(b"   SEGMENT   SHOULDER (P1)   ELBOW (P2)    WRIST (P3) (LEVEL)\r\n")
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            
                            for step in [1, 2, 3, 4, 5]:
                                fraction = step / 5.0
                                target_p1 = constraint_safety_clip(int(round(baseline_p1 - (total_sh_pulses * fraction))))
                                target_p2 = constraint_safety_clip(int(round(baseline_p2 - (total_el_pulses * fraction))))
                                
                                el_delta = target_p2 - baseline_p2
                                target_p3 = constraint_safety_clip(dynamic_ready_positions[3] - el_delta)
                                
                                current_arm_positions[1] = target_p1
                                current_arm_positions[2] = target_p2
                                current_arm_positions[3] = target_p3
                                
                                packet = f"#1P{target_p1}#2P{target_p2}#3P{target_p3}T1000\r"
                                lynx.write(packet.encode('utf-8'))
                                
                                win7.write(f"    {step} cm      {target_p1}            {target_p2}            {target_p3}          -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                time.sleep(1.0)
                                
                            win7.write(b"[PUSH COMPLETE] Trajectory complete.\r\n\r\n")

                    # L2 BUTTON: RESET TO ACTIVE FIELD BASELINE (TOOL RETRACT)
                    elif "TOOL RETRACT" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "RETRACT":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> L2 (TOOL RETRACT) Released.\r\n\r\n")
                        elif last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            
                            current_arm_positions = list(dynamic_ready_positions)
                            macro_packet = "".join(f"#{j}P{dynamic_ready_positions[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

This version required an update of the Cokoino sketch to support toggle movement. For that see the Cokoino CKK0017 topic in Science.

Update: The base and shoulder arm moved under toggle control.
There are details to be attended to. The Python program needs to track the state of all six servos, and Version 43 lost track of four of the six. We'll address that in Version 44.

(th)

Offline

Like button can go here

#116 Yesterday 21:14:31

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

Re: Python Computer Language

This post contains Version 44 of a Python program to act as a bridge between a Cokoino CKK0017 controller and a LynxMotion AL5D robot arm. The update applied to Version 44 is intended to provide a matrix for the state of the robot arm independent of saved states such as for "Home", "Tuck" and "Ready".  The new matrix will be updated (we hope) as the operator moves the arm in preparation for a tool movement. The same matrix can be used later for a Pick-and-Place version of the bridge program.

# bridgeV44.py Prepared by Gemini Supervised by Tom Hanson
# Version 44: Dedicated Isolated Manual Steering Matrix with Baseline Snapping
# Version 43: Left Analog Toggle Steering Integration (Servo 0 and Servo 1)
# Version 42: Targeted Release Diagnostics & Cokoino Handshake Version Extraction

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

print("==================================================")
print("Initializing Robot Junction Bridge V44...")
print("Isolated Manual Steering Matrix Engaged")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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)
MIN_PULSE    = int(root.find(".//min_pulse_width").text)
MAX_PULSE    = int(root.find(".//max_pulse_width").text)
CENTER_PULSE = int(root.find(".//center_pulse_width").text)

def constraint_safety_clip(pulse):
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

def prompt_for_other_matrix(current_defaults):
    """Reusable dialog window inside the console terminal prompt"""
    print("\n--- OPERATOR FIELD SPECIFICATION INJECTION ---")
    print("Enter raw pulse widths (500 - 2500) or press ENTER to keep current:")
    try:
        p0 = constraint_safety_clip(int(input(f"  Channel 0 (Base)      [{current_defaults[0]}]: ") or current_defaults[0]))
        p1 = constraint_safety_clip(int(input(f"  Channel 1 (Shoulder)  [{current_defaults[1]}]: ") or current_defaults[1]))
        p2 = constraint_safety_clip(int(input(f"  Channel 2 (Elbow)     [{current_defaults[2]}]: ") or current_defaults[2]))
        p3 = constraint_safety_clip(int(input(f"  Channel 3 (Wrist)     [{current_defaults[3]}]: ") or current_defaults[3]))
        p4 = constraint_safety_clip(int(input(f"  Channel 4 (Gripper)   [{current_defaults[4]}]: ") or current_defaults[4]))
        p5 = constraint_safety_clip(int(input(f"  Channel 5 (Wrist Rot) [{current_defaults[5]}]: ") or current_defaults[5]))
        return [p0, p1, p2, p3, p4, p5]
    except ValueError:
        print("[ERROR] Integers only. Retaining previous values.")
        return current_defaults

# --- STEP 1: SERIAL CONNECTIONS ---
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)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        
        if win7_idx not in all_indices or cokoino_idx not in all_indices or win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 2: POSITION MANAGEMENT CONFIGURATION ---
current_arm_positions = [CENTER_PULSE] * 6
HOME_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]
TUCK_TARGET = [1500, 1821, 1842, 500, 500, 1500]
OTHER_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]

# Isolated data layer for real-time manual manipulation
MANUAL_STEER = [1500, 1500, 1500, 1500, 1500, 1500]

print("\n==================================================")
print("         ROBOT ARM CONFIGURATION MATRIX           ")
print("==================================================")
print("  [T] Secured in TUCK position")
print("  [H] Standing upright at HOME position")
print("  [O] MANUALLY SPECIFY 'OTHER' VALUES (Field Override)")
print("--------------------------------------------------")

while True:
    choice = input("Enter alignment choice (T/H/O): ").strip().upper()
    if choice == 'T':
        current_arm_positions = list(TUCK_TARGET)
        break
    elif choice == 'H':
        current_arm_positions = list(HOME_TARGET)
        break
    elif choice == 'O':
        OTHER_TARGET = prompt_for_other_matrix(OTHER_TARGET)
        current_arm_positions = list(OTHER_TARGET)
        print(f"\nSuccessfully Registered Initial 'OTHER' Configuration: {OTHER_TARGET}\n")
        break

dynamic_ready_positions = list(current_arm_positions)

# --- STEP 3: RUNTIME EXECUTION ENGINE ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H") # Clear HyperTerminal screen
    
    system_state = 0
    binary_counter = 0

    win7.write(b"==================================================\r\n")
    win7.write(b"--- SYSTEM BOOT: LOCKOUT ACTIVE (SAFE MODE) ---\r\n")
    win7.write(b"==================================================\r\n\r\n")

    last_processed_command = ""

    while True:
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                # --------------------------------------------------------------
                # SAFE MODE HANDSHAKE
                # --------------------------------------------------------------
                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1
                        cokoino.write(b"LED:STATE:GREEN\n")
                        
                        if "VERSION" in cmd_upper:
                            ver_idx = cmd_upper.find("VERSION") + 8
                            extracted_ver = cmd[ver_idx:].strip()
                            version_output = f"[!!!] START DETECTED -> Onboard Firmware: {extracted_ver} [!!!]\r\n\r\n"
                        else:
                            version_output = "[!!!] START DETECTED [!!!]\r\n\r\n"
                            
                        win7.write(version_output.encode('utf-8'))
                
                # --------------------------------------------------------------
                # ACTIVE RUNTIME ENGINE
                # --------------------------------------------------------------
                elif system_state == 1:
                    
                    # CATCH ANALOG STEERING INTERCEPT STRINGS FIRST
                    if cmd_upper.startswith("ANALOG:"):
                        parts = cmd_upper.split(":")
                        if len(parts) == 3:
                            axis = parts[1]
                            try:
                                val = int(parts[2])
                                offset = val - 128
                                
                                # LX controls Servo 0 (Base swing). Manipulate MANUAL_STEER layer
                                if axis == "LX":
                                    step = int(offset * 0.15)
                                    MANUAL_STEER[0] = constraint_safety_clip(MANUAL_STEER[0] + step)
                                    current_arm_positions[0] = MANUAL_STEER[0]
                                
                                # LY controls Servo 1 (Shoulder elevation)
                                elif axis == "LY":
                                    step = int(offset * 0.15)
                                    MANUAL_STEER[1] = constraint_safety_clip(MANUAL_STEER[1] - step)
                                    current_arm_positions[1] = MANUAL_STEER[1]
                                
                                # Drive muscles cleanly using unified positions
                                motion_packet = f"#0P{MANUAL_STEER[0]}#1P{MANUAL_STEER[1]}T100\r"
                                lynx.write(motion_packet.encode('utf-8'))
                                
                            except ValueError:
                                pass
                        continue

                    # TRACKING MECHANICAL BRAKE UNCOUPLE: Snap to active background baseline coordinates!
                    elif "STEERING BRAKE ENGAGED" in cmd_upper:
                        win7.write(b"[STEERING MANAGEMENT] -> Lockout Engaged. Position Staged.\r\n\r\n")
                        continue
                    elif "STEERING BRAKE RELEASED" in cmd_upper:
                        # CRITICAL CORRECTION: Capture exact current arm reality so unguided segments don't snap!
                        MANUAL_STEER = list(current_arm_positions)
                        win7.write(b"[STEERING MANAGEMENT] -> Lockout Released. LEFT TOGGLE LIVE.\r\n")
                        win7.write(f" -> Manual Steer Layer Seeded from Active Position: {MANUAL_STEER}\r\n\r\n".encode('utf-8'))
                        continue

                    # CROSS BUTTON: Run Lynxmotion Communication Diagnostics
                    if "CROSS" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "CROSS":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> CROSS Button Released.\r\n\r\n")
                        elif last_processed_command != "CROSS":
                            last_processed_command = "CROSS"
                            win7.write(b"[FROM GAMEPAD] -> CROSS\r\n")
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                win7.write(f"==> LYNXMOTION RESPONSE: '{response}'\r\n\r\n".encode('utf-8'))

                    # TRIANGLE BUTTON: Hard Alignment to HOME position
                    elif "TRIANGLE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "TRIANGLE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> TRIANGLE Button Released.\r\n\r\n")
                        elif last_processed_command != "TRIANGLE":
                            last_processed_command = "TRIANGLE"
                            win7.write(b"[FROM GAMEPAD] -> TRIANGLE\r\n")
                            current_arm_positions = list(HOME_TARGET)
                            macro_packet = "".join(f"#{j}P{HOME_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # CIRCLE BUTTON: Hard Alignment to TUCK position
                    elif "CIRCLE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "CIRCLE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> CIRCLE Button Released.\r\n\r\n")
                        elif last_processed_command != "CIRCLE":
                            last_processed_command = "CIRCLE"
                            win7.write(b"[FROM GAMEPAD] -> CIRCLE\r\n")
                            current_arm_positions = list(TUCK_TARGET)
                            macro_packet = "".join(f"#{j}P{TUCK_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

                    # SQUARE BUTTON: COMMIT STEERING REALITY INTO THE WORK BASELINE
                    elif "SQUARE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "SQUARE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> SQUARE Button Released.\r\n\r\n")
                        elif last_processed_command != "SQUARE":
                            last_processed_command = "SQUARE"
                            win7.write(b"[FROM GAMEPAD] -> SQUARE: LOCKING STEERED MANIPULATION TO ACTIVE BASELINE\r\n")
                            
                            # Lock steered adjustments into our operational target frames
                            current_arm_positions = list(MANUAL_STEER)
                            dynamic_ready_positions = list(MANUAL_STEER) 
                            
                            macro_packet = "".join(f"#{j}P{MANUAL_STEER[j]}" for j in range(6)) + f"T1000\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(b"==================================================\r\n")
                            win7.write(f"[NEW STEERED BASELINE ANCHORED] -> {macro_packet.strip()}\r\n".encode('utf-8'))
                            win7.write(f"[BASELINE LOCKED] Matrix: {dynamic_ready_positions}\r\n".encode('utf-8'))
                            win7.write(b"==================================================\r\n\r\n")

                    # SELECT BUTTON: NON-MOVEMENT CONSOLE TUNING INTERCEPT
                    elif "SELECT" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "SELECT":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> SELECT Button Released.\r\n\r\n")
                        elif last_processed_command != "SELECT":
                            last_processed_command = "SELECT"
                            win7.write(b"[RUNTIME INTERCEPT] -> SELECT Pressed. Opening Dialog in Terminal window...\r\n")
                            print("\n\n==================================================")
                            print("     RUNTIME TUNING DIALOG INTERCEPT INITIATED     ")
                            print("==================================================")
                            
                            OTHER_TARGET = prompt_for_other_matrix(OTHER_TARGET)
                            
                            print("\n Tuning updates saved to background matrix.")
                            print(" -> System is stationary. Press SQUARE on gamepad to deploy.")
                            print("==================================================\n")
                            
                            win7.write(f"[STAGED NEW MATRIX] background settings updated to: {OTHER_TARGET}\r\n".encode('utf-8'))
                            win7.write(b" -> Standing by. Press SQUARE to execute physical motion.\r\n\r\n")
                            
                            cokoino.reset_input_buffer()
                            last_processed_command = "SELECT"

                    # L1 BUTTON: KINEMATIC PROPULSION LINK (TOOL ADVANCE)
                    elif "TOOL ADVANCE" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "ADVANCE":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> L1 (TOOL ADVANCE) Released.\r\n\r\n")
                        elif last_processed_command != "ADVANCE":
                            last_processed_command = "ADVANCE"
                            win7.write(b"[FROM GAMEPAD] -> L1: TOOL ADVANCE DETECTED\r\n")
                            
                            baseline_p1 = dynamic_ready_positions[1]
                            baseline_p2 = dynamic_ready_positions[2]
                            
                            total_sh_pulses = 217
                            if baseline_p1 > 1600:
                                total_el_pulses = 215
                            elif baseline_p1 < 1400:
                                total_el_pulses = -80
                            else:
                                total_el_pulses = -45

                            win7.write(b"   -------------------------------------------------------------\r\n")
                            win7.write(b"   SEGMENT   SHOULDER (P1)   ELBOW (P2)    WRIST (P3) (LEVEL)\r\n")
                            win7.write(b"   -------------------------------------------------------------\r\n")
                            
                            for step in [1, 2, 3, 4, 5]:
                                fraction = step / 5.0
                                target_p1 = constraint_safety_clip(int(round(baseline_p1 - (total_sh_pulses * fraction))))
                                target_p2 = constraint_safety_clip(int(round(baseline_p2 - (total_el_pulses * fraction))))
                                
                                el_delta = target_p2 - baseline_p2
                                target_p3 = constraint_safety_clip(dynamic_ready_positions[3] - el_delta)
                                
                                current_arm_positions[1] = target_p1
                                current_arm_positions[2] = target_p2
                                current_arm_positions[3] = target_p3
                                
                                packet = f"#1P{target_p1}#2P{target_p2}#3P{target_p3}T1000\r"
                                lynx.write(packet.encode('utf-8'))
                                
                                win7.write(f"    {step} cm      {target_p1}            {target_p2}            {target_p3}          -> {packet.strip()} [\\r]\r\n".encode('utf-8'))
                                time.sleep(1.0)
                                
                            win7.write(b"[PUSH COMPLETE] Trajectory complete.\r\n\r\n")

                    # L2 BUTTON: RESET TO ACTIVE FIELD BASELINE (TOOL RETRACT)
                    elif "TOOL RETRACT" in cmd_upper:
                        if "RELEASED" in cmd_upper:
                            if last_processed_command == "RETRACT":
                                last_processed_command = ""
                                win7.write(b"[HARDWARE EVENT] -> L2 (TOOL RETRACT) Released.\r\n\r\n")
                        elif last_processed_command != "RETRACT":
                            last_processed_command = "RETRACT"
                            win7.write(b"[FROM GAMEPAD] -> L2: TOOL RETRACT DETECTED\r\n")
                            
                            current_arm_positions = list(dynamic_ready_positions)
                            macro_packet = "".join(f"#{j}P{dynamic_ready_positions[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(f"  {macro_packet.strip()} [\\r]\r\n\r\n".encode('utf-8'))

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#117 Today 18:05:46

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

Re: Python Computer Language

This post contains Version 45 of a Python program to interface between a Cokoino CKK0017 controller and a LynxMotion AL5D robot arm. In this version, we are taking steps toward offering a package that would include the Cokoino board and a Raspberry Pi 5 in a sturdy case suitable for use at a job site. We are concentrating on ToolPoke scenario rather than PickPlace.  The two operating modes are so different we cannot attempt to combine them.  We are simultaneously developing a concept for a jobsite step-by-step guide that will use the binary codes for X'0' through X'F' to assist the operator.

# bridgeV45.py Prepared by Gemini Supervised by Tom Hanson
# Version 45: Screenless 6-Step LED Sequence & Automated Memory Mirroring to OTHER
# Version 44: Dedicated Isolated Manual Steering Matrix with Baseline Snapping
# Version 43: Left Analog Toggle Steering Integration (Servo 0 and Servo 1)

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

print("==================================================")
print("Initializing Robot Junction Bridge V45...")
print("Screenless 6-Step Hex Layout Active")
print("==================================================\n")

# ==============================================================================
#      STEP 0: UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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)
MIN_PULSE    = int(root.find(".//min_pulse_width").text)
MAX_PULSE    = int(root.find(".//max_pulse_width").text)
CENTER_PULSE = int(root.find(".//center_pulse_width").text)

def constraint_safety_clip(pulse):
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

# --- STEP 1: AUTOMATED SERIAL CONNECTIONS ---
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)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        
        if win7_idx not in all_indices or cokoino_idx not in all_indices or win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break
    except ValueError:
        print("[INVALID] Try again.\n")

# --- STEP 2: MULTI-LAYER STATE TRACKING ARCHITECTURE ---
current_arm_positions = [CENTER_PULSE] * 6
HOME_TARGET  = [1500, 1500, 1500, 1500, 1500, 1500]
TUCK_TARGET  = [1500, 1821, 1842, 500, 500, 1500]
OTHER_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]  # Dedicated Backup/Rescue point

# Manual manipulation working arrays
LEFT_STEER_LIVE  = False
RIGHT_STEER_LIVE = False

# Establish baseline to Tuck on start
current_arm_positions = list(TUCK_TARGET)
OTHER_TARGET = list(TUCK_TARGET)

# --- STEP 3: RUNTIME EXECUTION ENGINE ---
BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H") # Clear screen
    
    system_state = 0  # Matches Step 0 on operator card
    binary_counter = 0
    last_processed_command = ""

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

    while True:
        # Step 0: Awaiting Handshake - Spin the binary lights red
        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)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                # --------------------------------------------------------------
                # STEP 1: HANDSHAKE INITIATION
                # --------------------------------------------------------------
                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1  # Transition to Step 1: Solid Green Bar
                        cokoino.write(b"LED:STATE:1\n") # Update Arduino display to hex x'1'
                        win7.write(b"[OPERATOR CARD] -> Step 1: START Detected. Solid Green Bar Ready.\r\n\r\n")
                
                # --------------------------------------------------------------
                # RUNTIME OPERATIONAL MACHINE (STEPS 1 - 6)
                # --------------------------------------------------------------
                elif system_state >= 1:
                    
                    # --- INTERCEPT REAL-TIME ANALOG LOGIC ---
                    if cmd_upper.startswith("ANALOG:"):
                        parts = cmd_upper.split(":")
                        if len(parts) == 3:
                            axis = parts[1]
                            try:
                                val = int(parts[2])
                                offset = val - 128
                                
                                # Left Toggle Steering (Base/Shoulder) Live Entry
                                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)
                                    
                                    motion_packet = f"#0P{current_arm_positions[0]}#1P{current_arm_positions[1]}T100\r"
                                    lynx.write(motion_packet.encode('utf-8'))

                                # Right Toggle Steering (Elbow Tuning) Live Entry
                                elif RIGHT_STEER_LIVE:
                                    if axis == "RY":
                                        step = int(offset * 0.15)
                                        current_arm_positions[2] = constraint_safety_clip(current_arm_positions[2] - step)
                                    
                                    motion_packet = f"#2P{current_arm_positions[2]}T100\r"
                                    lynx.write(motion_packet.encode('utf-8'))
                                
                            except ValueError:
                                pass
                        continue

                    # --- DETECT BRAKE CLICKS (L3 & R3) FOR BACKUP SNAPSHOTS ---
                    
                    # Left Toggle Switch Control (Base & Shoulder)
                    elif "STEERING BRAKE ENGAGED" in cmd_upper: # Operator clicked L3 Off
                        LEFT_STEER_LIVE = False
                        OTHER_TARGET = list(current_arm_positions) # Automated Snapshot!
                        cokoino.write(b"LED:STATE:5\n") # Solid x'5'
                        win7.write(b"[OPERATOR CARD] -> Step 5 Locked. Matrix Mirrored to OTHER.\r\n")
                        win7.write(f" -> OTHER Target Saved: {OTHER_TARGET}\r\n\r\n".encode('utf-8'))
                        continue
                        
                    elif "STEERING BRAKE RELEASED" in cmd_upper: # Operator clicked L3 On
                        LEFT_STEER_LIVE = True
                        RIGHT_STEER_LIVE = False # Explicit safety lockout
                        system_state = 5
                        cokoino.write(b"LED:FLASH:5\n") # Flash x'5' to signal live tuning
                        win7.write(b"[OPERATOR CARD] -> Step 5: Base & Shoulder Tuning Unleashed.\r\n\r\n")
                        continue

                    # Right Toggle Switch Control (Elbow Tuning)
                    elif "ELBOW BRAKE ENGAGED" in cmd_upper: # Operator clicked R3 Off
                        RIGHT_STEER_LIVE = False
                        OTHER_TARGET = list(current_arm_positions) # Automated Snapshot!
                        cokoino.write(b"LED:STATE:6\n") # Solid x'6'
                        win7.write(b"[OPERATOR CARD] -> Step 6 Locked. Complete Setup Mirrored to OTHER.\r\n")
                        win7.write(f" -> Rescue Baseline Committed: {OTHER_TARGET}\r\n\r\n".encode('utf-8'))
                        continue
                        
                    elif "ELBOW BRAKE RELEASED" in cmd_upper: # Operator clicked R3 On
                        RIGHT_STEER_LIVE = True
                        LEFT_STEER_LIVE = False # Explicit safety lockout
                        system_state = 6
                        cokoino.write(b"LED:FLASH:6\n") # Flash x'6' to signal live elbow tuning
                        win7.write(b"[OPERATOR CARD] -> Step 6: Elbow Axis Tuning Unleashed.\r\n\r\n")
                        continue

                    # --- IMMUTABLE MACRO SHAPE BUTTON ACTIONS ---

                    # CROSS: Step 2 Hardware Comms Verification Ping
                    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
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                cokoino.write(b"LED:STATE:2\n") # Solid x'2' means handshake verified
                                win7.write(f"[OPERATOR CARD] -> Step 2: VER Verified ('{response}')\r\n\r\n".encode('utf-8'))
                            else:
                                cokoino.write(b"LED:FAULT:ERR\n") # Custom drop light pattern
                                win7.write(b"[ERROR] Lynxmotion Muscle layer Unresponsive.\r\n\r\n")

                    # TRIANGLE: Step 3 Transit to Vertical 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 = 3
                            cokoino.write(b"LED:STATE:3\n")
                            current_arm_positions = list(HOME_TARGET)
                            macro_packet = "".join(f"#{j}P{HOME_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(b"[OPERATOR CARD] -> Step 3: Traveling smoothly to HOME configuration.\r\n\r\n")

                    # CIRCLE: Step 4 Transit to Safe Folded TUCK configuration
                    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 = 4
                            cokoino.write(b"LED:STATE:4\n")
                            current_arm_positions = list(TUCK_TARGET)
                            macro_packet = "".join(f"#{j}P{TUCK_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(b"[OPERATOR CARD] -> Step 4: Traveling smoothly to TUCK baseline.\r\n\r\n")

                    # SQUARE: Consistent Recall Key -> Instantly travel to "OTHER" (Last Saved Step)
                    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"
                            current_arm_positions = list(OTHER_TARGET)
                            macro_packet = "".join(f"#{j}P{OTHER_TARGET[j]}" for j in range(6)) + f"T1500\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(b"==================================================\r\n")
                            win7.write(b"[RESCUE MACRO EXECUTION] -> Returning to Last Saved Setup Point\r\n")
                            win7.write(f" Target: {OTHER_TARGET}\r\n")
                            win7.write(b"==================================================\r\n\r\n")

        time.sleep(0.01)

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

(th)

Offline

Like button can go here

#118 Today 19:56:16

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

Re: Python Computer Language

This post contains Version 46 of a Python program for Cokoino to LynxMotion flow.

This version is only slightly changed from V45.  I asked for blue LED to show steps in the setup sequence, and a minor print format error was corrected.

# bridgeV46.py Prepared by Gemini Supervised by Tom Hanson
# Version 46: Patched SQUARE Byte-String Crash & Shifted Setup Steps to Blue LEDs
# Version 45: Screenless 6-Step LED Sequence & Automated Memory Mirroring to OTHER

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

print("==================================================")
print("Initializing Robot Junction Bridge V46...")
print("Screenless 6-Step Hex Layout Active [BLUE LED MODE]")
print("==================================================\n")

# ==============================================================================
#      UNIFIED ROBOT DESCRIPTION FORMAT (URDF) - XML PARAMETERS
# ==============================================================================
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)
MIN_PULSE    = int(root.find(".//min_pulse_width").text)
MAX_PULSE    = int(root.find(".//max_pulse_width").text)
CENTER_PULSE = int(root.find(".//center_pulse_width").text)

def constraint_safety_clip(pulse):
    return max(MIN_PULSE, min(MAX_PULSE, pulse))

# --- PHYSICAL USB SERIAL BUS PORT IDENTIFICATION ---
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)

if len(ports) < 3:
    print(f"[ERROR] Found only {len(ports)} physical USB devices.")
    exit(1)

all_indices = {0, 1, 2}
while True:
    try:
        win7_idx = int(input("Enter index number for WINDOWS 7 (HyperTrm): "))
        cokoino_idx = int(input("Enter index number for COKOINO (Arduino): "))
        
        if win7_idx not in all_indices or cokoino_idx not in all_indices or win7_idx == cokoino_idx:
            print("\n[CONFLICT DETECTED] Re-enter assignments.\n")
            continue
        lynx_idx = list(all_indices - {win7_idx, cokoino_idx})[0]
        WIN7_PORT    = ports[win7_idx].device
        COKOINO_PORT = ports[cokoino_idx].device
        LYNX_PORT    = ports[lynx_idx].device
        break
    except ValueError:
        print("[INVALID] Try again.\n")

# --- MEMORY ARRAYS & CONFIGURATION BASELINES ---
current_arm_positions = [CENTER_PULSE] * 6
HOME_TARGET  = [1500, 1500, 1500, 1500, 1500, 1500]
TUCK_TARGET  = [1500, 1821, 1842, 500, 500, 1500]
OTHER_TARGET = [1500, 1500, 1500, 1500, 1500, 1500]  # Safe Backup/Rescue Point

LEFT_STEER_LIVE  = False
RIGHT_STEER_LIVE = False

# Seed initial operational baseline to Tuck posture
current_arm_positions = list(TUCK_TARGET)
OTHER_TARGET = list(TUCK_TARGET)

BAUD_RATE = 9600
TRANSIT_TIME_MS = 3000

try:
    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)
    win7.write(b"\x1b[2J\x1b[H") # Clear HyperTerminal Screen
    
    system_state = 0  
    binary_counter = 0
    last_processed_command = ""

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

    while True:
        # Step 0 Lockout Loop: Pulse rotating counter to the Arduino
        if system_state == 0:
            hex_val = format(binary_counter, 'X')
            cokoino.write(f"LED:ROTR:{hex_val}\n".encode('utf-8'))
            binary_counter = (binary_counter + 1) % 16
            time.sleep(0.2)

        if cokoino.in_waiting > 0:
            data = cokoino.readline()
            cmd = data.decode('utf-8', errors='ignore').strip()
            
            if cmd and not cmd.startswith("LED:"):
                cmd_upper = cmd.upper()

                # --- STEP 1 INITIATION ---
                if system_state == 0:
                    if "START" in cmd_upper:
                        system_state = 1  
                        cokoino.write(b"LED:BLUE:1\n") # Command Cokoino to solid Blue Hex 1
                        win7.write(b"[OPERATOR CARD] -> Step 1: START Detected. Blue Setup Interface Ready.\r\n\r\n")
                
                # --- ACTIVE CARD RUNTIME ENGINE ---
                elif system_state >= 1:
                    
                    # Intercept Joystick Position Strings
                    if cmd_upper.startswith("ANALOG:"):
                        parts = cmd_upper.split(":")
                        if len(parts) == 3:
                            axis = parts[1]
                            try:
                                val = int(parts[2])
                                offset = val - 128
                                
                                # Coarse Adjust: Base & Shoulder
                                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)
                                    
                                    motion_packet = f"#0P{current_arm_positions[0]}#1P{current_arm_positions[1]}T100\r"
                                    lynx.write(motion_packet.encode('utf-8'))

                                # Fine Adjust: Elbow Reach
                                elif RIGHT_STEER_LIVE:
                                    if axis == "RY":
                                        step = int(offset * 0.15)
                                        current_arm_positions[2] = constraint_safety_clip(current_arm_positions[2] - step)
                                    
                                    motion_packet = f"#2P{current_arm_positions[2]}T100\r"
                                    lynx.write(motion_packet.encode('utf-8'))
                                
                            except ValueError:
                                pass
                        continue

                    # --- BRAKE EVENT PROCESSING & MEMORY REPLICATION ---
                    
                    # Left Toggle Brake (L3 Click)
                    elif "STEERING BRAKE ENGAGED" in cmd_upper:
                        LEFT_STEER_LIVE = False
                        OTHER_TARGET = list(current_arm_positions) # Take automatic snapshot
                        cokoino.write(b"LED:BLUE:5\n") # Solid Blue Step 5
                        win7.write(b"[OPERATOR CARD] -> Step 5 Locked. Matrix Mirrored to OTHER.\r\n")
                        win7.write(f" -> OTHER Target Saved: {OTHER_TARGET}\r\n\r\n".encode('utf-8'))
                        continue
                        
                    elif "STEERING BRAKE RELEASED" in cmd_upper:
                        LEFT_STEER_LIVE = True
                        RIGHT_STEER_LIVE = False 
                        system_state = 5
                        cokoino.write(b"LED:FLSH:5\n") # Flash Blue Step 5 to signal active tuning
                        win7.write(b"[OPERATOR CARD] -> Step 5: Base & Shoulder Tuning Unleashed.\r\n\r\n")
                        continue

                    # Right Toggle Brake (R3 Click)
                    elif "ELBOW BRAKE ENGAGED" in cmd_upper:
                        RIGHT_STEER_LIVE = False
                        OTHER_TARGET = list(current_arm_positions) # Take automatic snapshot
                        cokoino.write(b"LED:BLUE:6\n") # Solid Blue Step 6
                        win7.write(b"[OPERATOR CARD] -> Step 6 Locked. Complete Setup Mirrored to OTHER.\r\n")
                        win7.write(f" -> Rescue Baseline Committed: {OTHER_TARGET}\r\n\r\n".encode('utf-8'))
                        continue
                        
                    elif "ELBOW BRAKE RELEASED" in cmd_upper:
                        RIGHT_STEER_LIVE = True
                        LEFT_STEER_LIVE = False 
                        system_state = 6
                        cokoino.write(b"LED:FLSH:6\n") # Flash Blue Step 6 to signal active tuning
                        win7.write(b"[OPERATOR CARD] -> Step 6: Elbow Axis Tuning Unleashed.\r\n\r\n")
                        continue

                    # --- FIXED TRANSIT KEY EXECUTION ---

                    # CROSS: Comms Check Verification
                    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
                            lynx.write(b"VER\r")
                            time.sleep(0.1)
                            if lynx.in_waiting > 0:
                                response = lynx.readline().decode('utf-8', errors='ignore').strip()
                                cokoino.write(b"LED:BLUE:2\n") # Solid Blue Step 2 on confirmation
                                win7.write(f"[OPERATOR CARD] -> Step 2: VER Verified ('{response}')\r\n\r\n".encode('utf-8'))
                            else:
                                cokoino.write(b"LED:FAULT:ERR\n") 
                                win7.write(b"[ERROR] Lynxmotion Muscle layer Unresponsive.\r\n\r\n")

                    # TRIANGLE: Transit to Vertical 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 = 3
                            cokoino.write(b"LED:BLUE:3\n") # Solid Blue Step 3
                            current_arm_positions = list(HOME_TARGET)
                            macro_packet = "".join(f"#{j}P{HOME_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(b"[OPERATOR CARD] -> Step 3: Traveling smoothly to HOME configuration.\r\n\r\n")

                    # CIRCLE: Transit to Safe Folded TUCK Configuration
                    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 = 4
                            cokoino.write(b"LED:BLUE:4\n") # Solid Blue Step 4
                            current_arm_positions = list(TUCK_TARGET)
                            macro_packet = "".join(f"#{j}P{TUCK_TARGET[j]}" for j in range(6)) + f"T{TRANSIT_TIME_MS}\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            win7.write(b"[OPERATOR CARD] -> Step 4: Traveling smoothly to TUCK baseline.\r\n\r\n")

                    # SQUARE: Dynamic Rescue Recall -> Snaps physical hardware back to 'OTHER' matrix
                    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"
                            current_arm_positions = list(OTHER_TARGET)
                            macro_packet = "".join(f"#{j}P{OTHER_TARGET[j]}" for j in range(6)) + f"T1500\r"
                            lynx.write(macro_packet.encode('utf-8'))
                            
                            # --- FIXED ENCODING BUG ---
                            win7.write(b"==================================================\r\n")
                            win7.write(b"[RESCUE MACRO EXECUTION] -> Returning to Last Saved Setup Point\r\n")
                            win7.write(f" Target: {OTHER_TARGET}\r\n".encode('utf-8')) # Hardened to raw bytes
                            win7.write(b"==================================================\r\n\r\n")

        time.sleep(0.01)

except KeyboardInterrupt:
    print("\nBridge safely terminated.")
except Exception as e:
    print(f"\nBridge Crash Fatal Error: {e}") # This will catch and print if any other string types escape

We are working on implementing a concept for a system that communicates via the four LED's on the Cokoino board, rather than depending upon text.

(th)

Offline

Like button can go here

Board footer

Powered by FluxBB