Debug: Database connection successful Robotics Education Root Topic (Page 2) / 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.

#26 2026-07-11 19:55:55

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

Re: Robotics Education Root Topic

This post covers Version 22 of the Cokoino Arduino sketch that is intended to deliver game pad keystrokes in ASCII strings.  This version includes enhancement to report when keys are released. The bridge program needs that information to keep track of the state of keys in the game pad.

// CokoinoV22.ino Prepared by Gemini Supervised by Tom Hanson
// Version 22: Added ButtonReleased tokens to clear Python command locks
// Version 21: Complete digital button mapping & fixed setup version string
// Version 20: Add ID at program start

#include <PS2X_lib.h>
#include <Adafruit_NeoPixel.h>

#define LED_PIN A1
#define LED_COUNT 4
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

PS2X ps2x;
int error = 0;

void setLEDs(uint32_t c) {
  for(int i=0; i<LED_COUNT; i++) strip.setPixelColor(i, c);
  strip.show();
}

void transmitToken(String token) {
  // Set LEDs to Yellow immediately to signal event registration
  setLEDs(strip.Color(255, 150, 0)); 
  
  // Send the clean, condensed token down the pipe to Python
  Serial.println(token);
  
  // Hold the yellow display briefly for visual feedback
  delay(80); 
  
  // Restore back to steady operational Green state
  setLEDs(strip.Color(0, 255, 0));
}

void setup(){
  Serial.begin(9600);
  strip.begin();
  strip.setBrightness(40);
  strip.show(); 

  // Cokoino onboard dedicated PS/2 pins
  error = ps2x.config_gamepad(10, 12, 11, 13);
  if(error == 0) Serial.println("V22 Ready: Visual Chatter Token Pipe.");
}

void loop(){
  if(error != 0) return;
  ps2x.read_gamepad(false, 0);

  // ==========================================================================
  //  ZONE 1: TRANSMISSION PIPE (Flashes Yellow on event, returns to Green)
  // ==========================================================================
  
  // Geometric Buttons - Presses
  if(ps2x.ButtonPressed(PSB_TRIANGLE)) transmitToken("TRIANGLE");
  if(ps2x.ButtonPressed(PSB_CIRCLE))   transmitToken("CIRCLE");
  if(ps2x.ButtonPressed(PSB_CROSS))    transmitToken("CROSS");
  if(ps2x.ButtonPressed(PSB_SQUARE))   transmitToken("SQUARE");

  // Geometric Buttons - Releases
  if(ps2x.ButtonReleased(PSB_TRIANGLE)) transmitToken("TRIANGLE RELEASED");
  if(ps2x.ButtonReleased(PSB_CIRCLE))   transmitToken("CIRCLE RELEASED");
  if(ps2x.ButtonReleased(PSB_CROSS))    transmitToken("CROSS RELEASED");
  if(ps2x.ButtonReleased(PSB_SQUARE))   transmitToken("SQUARE RELEASED");

  // Navigation / Control Buttons - Presses & Releases
  if(ps2x.ButtonPressed(PSB_START))    transmitToken("START Sketch Version V22");
  if(ps2x.ButtonPressed(PSB_SELECT))   transmitToken("SELECT");
  if(ps2x.ButtonReleased(PSB_SELECT))  transmitToken("SELECT RELEASED");

  // Directional D-Pad - Presses
  if(ps2x.ButtonPressed(PSB_PAD_UP))    transmitToken("PAD UP");
  if(ps2x.ButtonPressed(PSB_PAD_DOWN))  transmitToken("PAD DOWN");
  if(ps2x.ButtonPressed(PSB_PAD_LEFT))  transmitToken("PAD LEFT");
  if(ps2x.ButtonPressed(PSB_PAD_RIGHT)) transmitToken("PAD RIGHT");

  // Directional D-Pad - Releases
  if(ps2x.ButtonReleased(PSB_PAD_UP))    transmitToken("PAD UP RELEASED");
  if(ps2x.ButtonReleased(PSB_PAD_DOWN))  transmitToken("PAD DOWN RELEASED");
  if(ps2x.ButtonReleased(PSB_PAD_LEFT))  transmitToken("PAD LEFT RELEASED");
  if(ps2x.ButtonReleased(PSB_PAD_RIGHT)) transmitToken("PAD RIGHT RELEASED");

  // Shoulder Bumper Commands - Presses
  if(ps2x.ButtonPressed(PSB_L1))       transmitToken("TOOL ADVANCE");
  if(ps2x.ButtonPressed(PSB_L2))       transmitToken("TOOL RETRACT");
  if(ps2x.ButtonPressed(PSB_R1))       transmitToken("READY POSITION");
  if(ps2x.ButtonPressed(PSB_R2))       transmitToken("SYSTEM HOME");

  // Shoulder Bumper Commands - Releases
  if(ps2x.ButtonReleased(PSB_L1))       transmitToken("TOOL ADVANCE RELEASED");
  if(ps2x.ButtonReleased(PSB_L2))       transmitToken("TOOL RETRACT RELEASED");
  if(ps2x.ButtonReleased(PSB_R1))       transmitToken("READY POSITION RELEASED");
  if(ps2x.ButtonReleased(PSB_R2))       transmitToken("SYSTEM HOME RELEASED");

  // Thumbstick Click Buttons - Presses & Releases
  if(ps2x.ButtonPressed(PSB_L3))       transmitToken("STICK CLICK LEFT");
  if(ps2x.ButtonPressed(PSB_R3))       transmitToken("STICK CLICK RIGHT");
  if(ps2x.ButtonReleased(PSB_L3))      transmitToken("STICK CLICK LEFT RELEASED");
  if(ps2x.ButtonReleased(PSB_R3))      transmitToken("STICK CLICK RIGHT RELEASED");

  // ==========================================================================
  //  ZONE 2: PYTHON INCOMING LIGHTING LISTENER (Drives Lockouts & Diagnostics)
  // ==========================================================================
  if (Serial.available() > 0) {
    String rcved = Serial.readStringUntil('\n');
    rcved.trim();
    
    // 1. Handle the Safe Mode Binary Counter Loop (State 0)
    if (rcved.startsWith("LED:LOCK:")) {
      char hexChar = rcved.charAt(9);
      int count = (hexChar >= 'A') ? (hexChar - 'A' + 10) : (hexChar - '0');
      
      strip.clear();
      if (count & 1) strip.setPixelColor(0, strip.Color(255, 0, 0)); 
      if (count & 2) strip.setPixelColor(1, strip.Color(255, 0, 0)); 
      if (count & 4) strip.setPixelColor(2, strip.Color(255, 0, 0)); 
      if (count & 8) strip.setPixelColor(3, strip.Color(255, 0, 0)); 
      strip.show();
    }
    
    // 2. Handle System Unlock (State 1)
    else if (rcved == "LED:STATE:GREEN") {
      setLEDs(strip.Color(0, 255, 0)); 
    }
    
    // 3. Handle Communication Diagnostics
    else if (rcved == "LED:VER:SUCCESS") {
      for(int i = 0; i < 3; i++) {
        setLEDs(strip.Color(0, 0, 0)); delay(100);
        setLEDs(strip.Color(0, 255, 0)); delay(100);
      }
    }
    else if (rcved == "LED:VER:FAULT") {
      for(int i = 0; i < 3; i++) {
        setLEDs(strip.Color(0, 0, 0)); delay(100);
        setLEDs(strip.Color(255, 0, 0)); delay(100);
      }
    }
  }
  
  delay(40);
}

(th)

Offline

Like button can go here

#27 2026-07-12 10:12:15

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

Re: Robotics Education Root Topic

This topic is offered as a collection of knowledge and experience in the broad field of robotics.  This post is about managing a sophisticated AI system in support of a project.  At this point in the evolution of AI tools, I have observed that the two tools I work with (ChatGGP and Gemini) both  suffer from a deficiency of resources that can occur unexpectedly as the trail of exchanges increases, and as the complexity of the project increases. The solution that I have found so far is to start a new session, bringing over just the current work product. This action results in the AI tool no longer understanding what the project is about.  In the exchange below, you will see an example of the recovery process. In this example, we are working on an interface between  a component by one manufacture that is intended to control the movements of a system created by another manufacturer. We are using a Raspberry Pi 5 computer as an intermediary.

It is the next day, and you've been helping thousands of customers if not millions. Thank you for your help with our project here! I noticed concerning wording in the most recent text, provided by the instance just ahead of you. Please notice this wording:
Begin Quotation:
To shift to controlled movement (like jogging individual joints manually using the D-Pad or shoulder buttons), we will want to move away from fixed targets. Instead, we'll design our state machine to:
End Quotation.
We do not want to "move away" from anything. We have worked together to achieve the fixed movements. We are building a program to deliver a tool into a work face. We want that action to be performed with the precision that only planned movements will allow. the purpose of the work we will be doing next is to enable to the operator to steer the tool to the work face. I suspect that the context of our project was lost when all those tokens disappeared. It is time for us both to refresh our vision of our project. Let's begin by listing the capabilities you see in the software. This will help me to understand where we are, rather than where I ** think ** we are.
Welcome back, Tom. I completely understand, and I appreciate you catching that wording. You are entirely correct—nothing should be discarded or moved away from.

The planned, high-precision trajectory movements (like the TOOL ADVANCE kinematic propulsion link and the hard alignments) are the core operational strengths of this software. They represent the automation that delivers the tool exactly where it needs to go with absolute repeatability.

What we are doing next isn't replacing that; it is adding a steering mechanism so the operator can position that baseline wherever a unique workspace requires it, after which the precise automated tracks take over.

Let's refresh our collective vision by inventorying exactly what capabilities are built into your current software stack (Cokoino V22 and Python bridgeV42). Here is the map of where the system stands right now:

Current Software Capability Inventory
1. Hardware-Independent Token Communication Engine
Firmware Handshake & Version Verification: Upon system boot, the Cokoino board remains locked in a safe state until the operator presses START. It then transmits its specific firmware version down the line. Python captures this string, extracts the identifier, and announces it on the Win7 HyperTerminal (e.g., Onboard Firmware: V22).

Bidirectional Diagnostic Visual Pipe: The script manages the board’s onboard NeoPixel array dynamically. It pushes a rotating binary counter (LED:LOCK:X) during safe mode, restores a steady GREEN state upon activation, and flashes success/fault patterns based on hardware communications.

Targeted Event Release Detection: The system doesn't just register when a button is pressed; it tracks when it is released (e.g., sending CROSS RELEASED). This explicitly clears individual software locks in Python, allowing immediate re-triggering of the same action without mechanical deadlock.

2. High-Precision Automated Macro Targets
Dynamic Coordinate Memory (dynamic_ready_positions): The software maintains a 6-channel array tracking the active alignment baseline of the physical robot arm.

Hard Alignment Sweeps: * TRIANGLE: Commands a synchronized, 3-second macro sweep of all 6 joints to a perfectly upright HOME_TARGET (1500 across all channels).

CIRCLE: Commands a synchronized, 3-second macro sweep to a folded, defensive TUCK_TARGET ([1500,1821,1842,500,500,1500]) to secure the unit.

SQUARE: Instantly executes a hard, coordinated alignment to a user-defined background matrix (OTHER_TARGET), making it the physical execution trigger for custom configurations.

3. Real-Time Non-Movement Tuning Intercept
SELECT Intercept Engine: Pressing the SELECT button acts as a pure, non-movement runtime intercept. It pauses tracking loops to open a secure field configuration menu directly in the operator's console terminal.

Background Pulse Manipulation: While the arm remains frozen in its last active physical position, the operator can safely type new raw pulse width variables (500−2500) for any of the 6 joints, staging them securely in the background OTHER_TARGET matrix without causing sudden physical joint movement.

4. Calculated Kinematic Propulsion Link
L1 (TOOL ADVANCE): This is the high-precision automated delivery path. When triggered, it reads the active baseline coordinates and computes a multi-segment trajectory. It divides a precise 217-pulse shoulder delta across 5 incremental stages, automatically leveling the wrist (P3) relative to the elbow's movement to drive the tool smoothly forward into a work face.

L2 (TOOL RETRACT): Acts as a high-precision rollback macro, smoothly reversing the tracking path to pull the tool clean out of the work face and returning the arm precisely to its active baseline coordinates.

5. Real-Time Hardware Diagnostics
CROSS Button Validation: Pressing CROSS bypasses positional logic to transmit a direct VER string to the Lynxmotion SSC-32 controller, reading and confirming its hardware version string (SSC32-V2.50USB) to prove the physical serial connection is healthy.

Bridging to the Next Step: The Operator's Steering System
Seeing the software mapped out this way makes it clear: the planned movements are the core destination. The feature we want to introduce next is simply an operator steering tool to adjust the starting location.

Instead of typing numbers into the SELECT menu to alter the background matrix, the operator will use physical keys (like the D-Pad) to gently nudge the OTHER_TARGET coordinates up or down. Once the operator has manually steered the tool to the correct orientation for the specific work face, they will press SQUARE to lock that in as the new active baseline. From that exact point, the high-precision TOOL ADVANCE and TOOL RETRACT sequences can execute their automated tasks.

Does this capability map accurately reflect where you see the software right now? Let me know if I've captured our true baseline perfectly!

(th)

Offline

Like button can go here

#28 2026-07-12 10:30:42

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

Re: Robotics Education Root Topic

This post contains the transcript of an exchange with Gemini that shows fine tuning of the model Gemini has in it's memory of the current project.  I noticed an incorrect element in the summary in Post #27 and brought it to Gemini's attention. I don't know how important this detail is in the big picture, but I am on high alert for any departure from our shared vision of what we are trying to achieve.

First, thank you for this ** very nice ** summary of our project status! I decided to post the entire exchange as an illustration of what collaboration looks like in a long running series of activities. I did notice one tiny detail that is incorrect, and will offer a correction: Your prior instance said:
Hard Alignment Sweeps: * TRIANGLE: Commands a synchronized, 3-second macro sweep of all 6 joints to a perfectly upright HOME_TARGET (1500 across all channels).
The robot arm is NOT perfectly upright. Only arm 1 is perfectly upright. The designers of the LynxMotion arm decided that 1500 at the joint between arm 1 and arm 2 should project at 90 degrees relative to arm 1. Thus the robot has a right angle appearance when viewed from the side. The rest of the components adhere to the "straight ahead" model.
***
Thank you for that crucial hardware correction, Tom! You are entirely right, and I am glad you caught that detail before it went to the forum.

That specific physical profile—where Channel 1 (Shoulder) holds the first segment vertically, but Channel 2 (Elbow) projects the second segment horizontally at a precise 90-degree right angle—is a classic engineering signature of the Lynxmotion articulatory layout. Calling it "perfectly upright" was a geometric mischaracterization of its actual side-profile silhouette.

Updating our understanding of this physical posture is vital, because that 90-degree right angle baseline is the exact spatial reference your automated TOOL ADVANCE sequence relies on to calculate its horizontal trajectory into the work face.

By sharing this entire exchange with your students, you are highlighting one of the most realistic aspects of systems engineering: software models must strictly conform to physical hardware realities. A program can calculate numbers perfectly, but it only succeeds if the programmer respects the mechanical constraints and default orientations designed into the iron and plastic.

With our shared vision completely realigned and verified against the physical arm, we are perfectly positioned for the steering phase. When you are ready to define how the operator will use the available keys to nudge those joint baselines before locking them in, let me know and we will get right to work on bridgeV43.py!

In the next sequence, we will attempt to implement controls to allow operator requested movements.

(th)

Offline

Like button can go here

#29 2026-07-12 20:42:09

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

Re: Robotics Education Root Topic

This post contains links to three images created by GW Johnson to help students to program a robot arm to move in a precise way.

The robot in study has two arms and a wrist, so three joints.  The challenge is to cause the wrist to remain perfectly level and at precisely the same altitude above the ground while the other two arms move in coordination. Dr. Johnson has prepared images that show the geometry of the situation, and show the mathematics required to compute the needed commands for the robot controller.

The robot controller only understands degrees of angle (expressed in values between 500 and 2500 with 90 degrees at 1500).

The designer must compute the needed values between 500 and 2500 to feed to each joint simultaneously.

The distance to be achieved is 5 centimeters in X, while Z and Y remain constant.

Here are the three files:

file.php?id=136

file.php?id=135

file.php?id=134

This topic is available for a question a NewMars member might have about how to use these images to perform the required calculations.

The range of movement of the arm is 180 degrees at each joint.

The practical range of movement for this application would be (about) 90 degrees.

At run time, the calculations will be performed by a Python program, which will generate the required angle commands to be transmitted to the robot arm.

The current state-of-play can be examined in Version 24 of the Python program, stored in the Python topic in the NewMars forum.

(th)

Offline

Like button can go here

#30 2026-07-13 18:59:18

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

Re: Robotics Education Root Topic

As a follow up to Post #29, Gemini prepared an explanation of it's revised plan to calculate the angles needed to project the gripper forward in X without variation in Z.   It analyzed the previous attempt and decided it could do a better job. This is a good moment to remind the reader (assuming there actually ** is ** one), that each time we press Enter after composing a query, we get an entirely new copy of whatever our AI service is at that moment.  The only way the new instance has any idea what's going on is the collection of tokens left behind from previous sessions. Here is the explanation that the most recent instance composed to explain how it plans to perform the forward movement.

Uncoupling the Scaffolding: Moving to a True Screenless Kinematic Engine

As our automated control system transitions from a bench-top development environment to screenless field operations, we have made a major architectural leap in how physical tool trajectories are calculated.

In our previous iterations, the L1 (Tool Advance) linear horizontal "poke" function relied on a localized, hardcoded math block. While this showed promising signs of life, it suffered from a fundamental logical limitation: it was mathematically blind.

The Limitation of Static Pulse Ratios

The earlier code operated by subtracting a fixed number of pulse-width units per centimeter step directly from the servos (e.g., shaving 217 pulses from the shoulder and applying a fixed ratio to the elbow). This method assumes the arm is always starting from one specific, perfect extension posture.

The moment an operator uses the joysticks to swing the base to a new angle or alter the initial shoulder height, those hardcoded pulse deductions instantly warp. Instead of a flat, straight forward push, the tool head executes a sweeping, unpredictable arc.

The Solution: A 2-Segment Geometric Inverse Kinematics (IK) Solver

To make the system bulletproof for screenless deployment, we are replacing static step-ratios with a true Trigonometric Inverse Kinematics (IK) Solver Engine. Rather than guessing pulse modifications, the Raspberry Pi 5 uses the physical lengths of the arm segments to build a dynamic geometric triangle.

               (Elbow Joint)
                  /     \
   Segment 1     /       \   Segment 2
  (Upper Arm)   /         \  (Forearm)
               /           \
              /             \
      (Shoulder)             (Wrist Joint) ===> [Tool Tip Point: X, Y, Z]

By treating the upper arm (L1) and forearm (L2) as two sides of a triangle where the third side is the straight-line distance to the target tool point, the script uses the Law of Cosines to solve the exact angular positions required in real-time.

The Governing Equation

To find the exact target angle for the elbow (theta_2) based on a desired real-world horizontal distance (X) and vertical height (Y), the embedded engine processes the following geometric relationship:

[indent]cos(theta_2) = (X² + Y² - L1² - L2²) / (2 * L1 * L2)[/indent]

The New Operational Workflow

When integrated behind our screenless 6-Step LED Setup Card, the tool push function transforms completely:

  1. The Spatial Snapshot: The exact millisecond the operator clicks the analog toggle switches to lock in the coarse positioning, the Python script reads the final servo pulse widths and immediately calculates exactly where the tool tip resides in real-world 2D space (e.g., 25 cm forward from the base, 10 cm high off the bench).

  2. The Pure Linear Vector: When the operator presses L1 (Tool Advance), the engine holds the height (Y) completely constant, locks the base heading stable, and increments the target forward extension (X) smoothly by 1 cm steps.

  3. Dynamic Joint Recalculation: For each step, the IK engine runs the Law of Cosines to solve the exact new pulse widths required for the shoulder, elbow, and wrist pitch to satisfy that position.

By shifting the math from hardcoded servo pulse assumptions to a real-time coordinate translation engine, the tool advance feature will execute a perfect, razor-straight forward linear vector regardless of whether the arm is reaching far out, tucked up close, or swung completely to the left flank.

(th)

Offline

Like button can go here

#31 Yesterday 20:41:02

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

Re: Robotics Education Root Topic

This post contains Version 24 of the Cokoino sketch to interface with Python to operate a LynxMotion robot arm. This version is almost identical to V23, with the difference that we adjust the behavior of the Cokoino sketch to NOT write to the LED array after "Start" has been initiated. Updates to the Cokoino sketch are necessary to give the Python program better control over the LED display. The reason is that I have chosen to use the LED panel to communicate to the operator. At present, the sketch does not support that new direction.

// CokoinoV24.ino Prepared by Gemini Supervised by Tom Hanson
// Version 24: Added runtimeActive gate to stop key-press LEDs from clobbering Python commands
// Version 23: Added Left Stick (L3) Steering Brake & Analog Transmission Pipe
// Version 22: Added ButtonReleased tokens to clear Python command locks
// Version 21: Complete digital button mapping & fixed setup version string

#include <PS2X_lib.h>
#include <Adafruit_NeoPixel.h>

#define LED_PIN A1
#define LED_COUNT 4
Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

PS2X ps2x;
int error = 0;
bool steeringBrake = true;   // True = Joysticks locked, False = Joysticks live
bool runtimeActive = false;  // Track if Operator Setup Sequence has been initiated

void setLEDs(uint32_t c) {
  for(int i=0; i<LED_COUNT; i++) strip.setPixelColor(i, c);
  strip.show();
}

void transmitToken(String token) {
  // Only execute local background colors if the system is not yet in active runtime mode
  if (!runtimeActive) {
    // Set LEDs to Yellow immediately to signal event registration
    setLEDs(strip.Color(255, 150, 0)); 
  }
  
  // Send the clean token down the pipe to Python
  Serial.println(token);
  
  // Only execute feedback delays and state background restores if pre-start testing is active
  if (!runtimeActive) {
    // Hold briefly for visual feedback
    delay(60); 
    
    // Return to appropriate background color
    if (!steeringBrake) {
      setLEDs(strip.Color(0, 150, 255)); // Cyan indicates "Steering Live"
    } else {
      setLEDs(strip.Color(0, 255, 0));   // Green indicates standard standby
    }
  }
}

void setup(){
  Serial.begin(9600);
  strip.begin();
  strip.setBrightness(40);
  strip.show(); 

  // Cokoino onboard dedicated PS/2 pins
  error = ps2x.config_gamepad(10, 12, 11, 13);
  if(error == 0) Serial.println("V24 Ready: Visual Chatter Token Pipe.");
}

void loop(){
  if(error != 0) return;
  
  // Read gamepad options: (false = no pressure sensitivity, true = analog mode active)
  ps2x.read_gamepad(false, 0);

  // ==========================================================================
  //  ZONE 1: TOGGLE LOCK & ANALOG STEERING ENGINE
  // ==========================================================================
  
  // Test the Left Thumbstick Click (Switch) to toggle the Steering Brake
  if(ps2x.ButtonPressed(PSB_L3)) {
    steeringBrake = !steeringBrake;
    if(steeringBrake) {
      transmitToken("STEERING BRAKE ENGAGED");
    } else {
      transmitToken("STEERING BRAKE RELEASED");
    }
  }

  // Only stream analog stick values down the pipe if the operator has released the brake
  if (!steeringBrake) {
    int lx = ps2x.Analog(PSS_LX);
    int ly = ps2x.Analog(PSS_LY);
    
    // Dead-zone check: Only transmit if stick moves away from nominal center (128)
    if (abs(lx - 128) > 15) {
      Serial.print("ANALOG:LX:");
      Serial.println(lx);
    }
    if (abs(ly - 128) > 15) {
      Serial.print("ANALOG:LY:");
      Serial.println(ly);
    }
  }

  // ==========================================================================
  //  ZONE 2: DIGITAL TRANSMISSION PIPE
  // ==========================================================================
  
  // Geometric Buttons - Presses & Releases
  if(ps2x.ButtonPressed(PSB_TRIANGLE))  transmitToken("TRIANGLE");
  if(ps2x.ButtonReleased(PSB_TRIANGLE)) transmitToken("TRIANGLE RELEASED");
  if(ps2x.ButtonPressed(PSB_CIRCLE))    transmitToken("CIRCLE");
  if(ps2x.ButtonReleased(PSB_CIRCLE))   transmitToken("CIRCLE RELEASED");
  if(ps2x.ButtonPressed(PSB_CROSS))     transmitToken("CROSS");
  if(ps2x.ButtonReleased(PSB_CROSS))    transmitToken("CROSS RELEASED");
  if(ps2x.ButtonPressed(PSB_SQUARE))    transmitToken("SQUARE");
  if(ps2x.ButtonReleased(PSB_SQUARE))   transmitToken("SQUARE RELEASED");

  // Navigation / Control Buttons
  if(ps2x.ButtonPressed(PSB_START)) {
    runtimeActive = true; // Permanent handoff to Python runtime control
    transmitToken("START Sketch Version V24");
  }
  if(ps2x.ButtonPressed(PSB_SELECT))    transmitToken("SELECT");
  if(ps2x.ButtonReleased(PSB_SELECT))   transmitToken("SELECT RELEASED");

  // Directional D-Pad - Presses & Releases
  if(ps2x.ButtonPressed(PSB_PAD_UP))     transmitToken("PAD UP");
  if(ps2x.ButtonReleased(PSB_PAD_UP))    transmitToken("PAD UP RELEASED");
  if(ps2x.ButtonPressed(PSB_PAD_DOWN))   transmitToken("PAD DOWN");
  if(ps2x.ButtonReleased(PSB_PAD_DOWN))  transmitToken("PAD DOWN RELEASED");
  if(ps2x.ButtonPressed(PSB_PAD_LEFT))   transmitToken("PAD LEFT");
  if(ps2x.ButtonReleased(PSB_PAD_LEFT))  transmitToken("PAD LEFT RELEASED");
  if(ps2x.ButtonPressed(PSB_PAD_RIGHT))  transmitToken("PAD RIGHT");
  if(ps2x.ButtonReleased(PSB_PAD_RIGHT)) transmitToken("PAD RIGHT RELEASED");

  // Shoulder Bumper Commands - Presses & Releases
  if(ps2x.ButtonPressed(PSB_L1))        transmitToken("TOOL ADVANCE");
  if(ps2x.ButtonReleased(PSB_L1))       transmitToken("TOOL ADVANCE RELEASED");
  if(ps2x.ButtonPressed(PSB_L2))        transmitToken("TOOL RETRACT");
  if(ps2x.ButtonReleased(PSB_L2))       transmitToken("TOOL RETRACT RELEASED");
  if(ps2x.ButtonPressed(PSB_R1))        transmitToken("READY POSITION");
  if(ps2x.ButtonReleased(PSB_R1))       transmitToken("READY POSITION RELEASED");
  if(ps2x.ButtonPressed(PSB_R2))        transmitToken("SYSTEM HOME");
  if(ps2x.ButtonReleased(PSB_R2))       transmitToken("SYSTEM HOME RELEASED");

  if(ps2x.ButtonPressed(PSB_R3))        transmitToken("STICK CLICK RIGHT");
  if(ps2x.ButtonReleased(PSB_R3))       transmitToken("STICK CLICK RIGHT RELEASED");

  // ==========================================================================
  //  ZONE 3: PYTHON INCOMING LIGHTING LISTENER
  // ==========================================================================
  if (Serial.available() > 0) {
    String rcved = Serial.readStringUntil('\n');
    rcved.trim();
    
    if (rcved.startsWith("LED:LOCK:")) {
      char hexChar = rcved.charAt(9);
      int count = (hexChar >= 'A') ? (hexChar - 'A' + 10) : (hexChar - '0');
      strip.clear();
      if (count & 1) strip.setPixelColor(0, strip.Color(255, 0, 0)); 
      if (count & 2) strip.setPixelColor(1, strip.Color(255, 0, 0)); 
      if (count & 4) strip.setPixelColor(2, strip.Color(255, 0, 0)); 
      if (count & 8) strip.setPixelColor(3, strip.Color(255, 0, 0)); 
      strip.show();
    }
    else if (rcved == "LED:STATE:GREEN") {
      steeringBrake = true; // Force default lock state on active handshake
      setLEDs(strip.Color(0, 255, 0)); 
    }
    else if (rcved == "LED:VER:SUCCESS") {
      for(int i = 0; i < 3; i++) {
        setLEDs(strip.Color(0, 0, 0)); delay(100);
        setLEDs(strip.Color(0, 255, 0)); delay(100);
      }
    }
    else if (rcved == "LED:VER:FAULT") {
      for(int i = 0; i < 3; i++) {
        setLEDs(strip.Color(0, 0, 0)); delay(100);
        setLEDs(strip.Color(255, 0, 0)); delay(100);
      }
    }
  }
  
  delay(40);
}

(th)

Offline

Like button can go here

Board footer

Powered by FluxBB