Sunday, October 26, 2025

Isaac Sim install

 Well, omniverse launcher and isaacgym have been deprecated. So here is the journey back/forward.

Enable long path support (Since I'm now using a new PC with win 11 pro): HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem LongPathsEnabled (Type: REG_DWORD) must exist and be set to 1.



Install UV standalone https://docs.astral.sh/uv/getting-started/installation/

Move to C:\\envs
:: create a virtual environment named env_isaaclab with python3.11
uv venv --python 3.11 env_isaaclab
:: activate the virtual environment
env_isaaclab\Scripts\activate

env_isaaclab\Scripts\activate : File C:\envs\env_isaaclab\Scripts\activate.ps1 cannot be loaded because running scripts is disabled on this system. For more information, see about_Execution_Policies at
https:/go.microsoft.com/fwlink/?LinkID=135170.
At line:1 char:1
+ env_isaaclab\Scripts\activate
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : SecurityError: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
Select Y.
Environment now activates.

Next step, upgrading pip. But pip is not installed, so: 
python -m ensurepip --upgrade
then
python -m pip install --upgrade pip
uv pip install torch==2.7.0 torchvision==0.22.0 --index-url https://download.pytorch.org/whl/cu128

Thursday, May 15, 2025

Material Master

 Material Measurement Device


Materials




Display will need 2(3)/5 of the pinouts. "SDA on pad 0 and SCL on pad 1." Since we are going for low power, I'll need a transistor to control the power. This drawing is wrong: 
    "Unique pad capabilities Digital #0 / A2  - this is connected to PA08 on the ATSAMD21. This pin can be used as a digital I/O with selectable pullup or pulldown, analog input (use 'A2'),  PWM output, and is also used for I2C data (SDA) Digital #1 / A0  - this is connected to PA02 on the ATSAMD21. This pin can be used as a digital I/O with selectable pullup or pulldown, capacitive touch, analog input (use 'A0'),  and true analog (10-bit DAC) output. It cannot be used as PWM output. Digital #2 / A1  - this is connected to PA09 on the ATSAMD21. This pin can be used as a digital I/O with selectable pullup or pulldown, analog input (use 'A1'),  PWM output, and is also used for I2C clock (SCL), and hardware SPI MISO Digital #3 / A3  - this is connected to PA07 on the ATSAMD21. This pin can be used as a digital I/O with selectable pullup or pulldown, analog input (use 'A3'),  capacitive touch, PWM output, and is also used for UART RX (Serial1 in Arduino), and hardware SPI SCK Digital #4 / A4  - this is connected to PA06 on the ATSAMD21. This pin can be used as a digital I/O with selectable pullup or pulldown, analog input (use 'A4'),  capacitive touch, PWM output, and is also used for UART TX (Serial1 in Arduino), and hardware SPI MOSI"

Battery display:
I'll need to create a voltage divider that scales down the battery's max 4.2V:
Battery + (4.2V max) ---- R1 (100k) ----+---- R2 (330k) ---- GND
                                        |
                                        +----> Trinket analog input pin (e.g., A1(Pad 2)
This will result in a max battery reading of 3.22V. It will use minimal current:
I=430k4.29.8μA
"All of the I/O pins can be used for 12-bit analog input" means I'll get values from 0 to 4095. The voltage divider reduced the reading to 76.7% of the actual voltage:
Vout=Vbat×R1+R2R2=Vbat×100k+330k330k=Vbat×0.767

Pseudocode, but insert real values of R1 & R2 from DMM:
const int analogPin = A1;  // ADC pin
const float R1 = 100000.0; // 100k nominal 
const float R2 = 330000.0; // 330k nominal
const float ADC_MAX = 4095.0;
const float V_REF = 3.3;

void setup() {
  Serial.begin(115200);
  analogReadResolution(12);  // 12-bit resolution for Trinket M0
}

void loop() {
  int adcValue = analogRead(analogPin);
  
  // Convert ADC to voltage at divider output
  float V_out = (adcValue / ADC_MAX) * V_REF;

  // Calculate battery voltage
  float V_bat = V_out * (R1 + R2) / R2;

  Serial.print("ADC: ");
  Serial.print(adcValue);
  Serial.print("  V_out: ");
  Serial.print(V_out, 3);
  Serial.print(" V  Battery Voltage: ");
  Serial.print(V_bat, 3);
  Serial.println(" V");

  delay(1000);
}

Hall Effect Sensor:
Testing with 5V, I got a sense distance of around 2.5mm. This might have to be embedded in the wall of a main body with magnets very near in the outer ring. Although it looks like I was testing it incorrectly. From the datasheet: "The UA package is south pole active: Applying a south magnetic pole greater than BOP facing the branded side of the package switches the output low."

Sunday, May 11, 2025

Carson's Bowling Ball

Project Log: Building a Motorized Neopixel Light Ring with Sparkle Dynamics

Like many projects, this one started by looking at my parts bin and asking, "What can I make with this?" I had an ESP32, a ULN2003 28BYJ-48 4-Phase Stepper Motor, a handful of WS2812B neopixels (5V 3MM x 10MM), a salvaged lithium battery, a charging IC, a USB-C breakout board, and a couple of old skate bearings rolling around my drawer. Good ingredients for something kinetic and glowy.

The Vision

My goal was to build a dynamic light ring with a rich, flowing animation—something alive, not just blinking lights. I wanted motion not just in the light but physically: a rotating ring with synchronized color effects. And if I was going to make it spin, why not use herringbone gears and embed magnets for snap-on ease?

The Build

The project required a few custom 3D-printed parts:

  • A replica mounting piece embedded with magnets for quick attachment.

  • A base plate to house the core electronics and battery.

  • A ring holder to mount the neopixels in a perfect arc.

  • Internal and external herringbone gears to give it that sweet mechanical motion and hold alignment under load.

  • A sleek main body shell to hold it all together.

The skate bearings found new life supporting the rotating parts, and the gear train was driven by a small motor tucked inside the shell.

The Electronics

The ESP32 handles everything:

  • Motor control

  • Animation logic

  • Battery monitoring

  • LED timing

The LEDs (WS2812Bs) are powered directly from the 5V rail and mounted in a fixed ring. Their brightness and color are dynamically adjusted based on motor speed and position.

The Sparkle Effect

One of the standout features is the speed-reactive sparkle.

  • At low speeds, the sparkle is deeper and more frequent, adding a dreamy shimmer.

  • At high speeds, the sparkle becomes subtle and sparse, giving way to smoother transitions.

This isn't just random flickering. Each LED has a base brightness and a per-frame “target offset” that modulates its value. A bit of easing logic ensures the sparkles fade in and out smoothly, rather than flicking abruptly.

Here's the gist:

cpp

// Randomly assign a new brightness target if (abs(current - target) < 0.01) { if (chance < 15%) { target = base ± deeper sparkle } else { target = base ± mild flicker } } // Ease toward the target current += (target - current) * 0.1;

Outcome

The result? A beautifully fluid light pattern that feels like it’s responding to motion. The hues chase each other around the ring, fading seamlessly from one to the next, while sparkles add depth—like watching starlight reflect off a moving surface.

What's Next?

I'm considering:

  • Adding BLE control to change modes or colors. Update 5/11/25: The ESP32 creates a WIFI hotspot. When connected, it forces a captive portal serving a page with Java controls.

  • Making the ring modular so it can snap onto different bases (a helmet, a sculpture, etc.).

  • Porting the project into a self-contained, rechargeable kinetic sculpture.


Want a visual walkthrough or code breakdown? Drop a comment or check out the GitHub repo (coming soon!).

Thursday, June 20, 2024

Calibrating the vl5310x TOF laser distance sensor.

Here is a good starting point, however, it might need a polynomial regression.

#include <Wire.h>
#include <VL53L0X.h>
#include <Preferences.h>

VL53L0X sensor;
Preferences laserPrefs;

const int numMeasurements = 100;  // Number of measurements to average
const int numCalPoints = 7;  // Number of calibration points
uint16_t measuredDistances[numCalPoints];
uint16_t actualDistances[numCalPoints] = {10, 100, 300, 600, 900, 1000, 1050}; // Replace with your actual distances in mm

// Calibration parameters
float offset = 0.0;
float scale = 1.0;

void setup()
{
  Serial.begin(9600);
  delay(500);
  Wire.begin();

  // Initialize Preferences for laser calibration data (read-write)
  laserPrefs.begin("laser_calibration", false); // Initialize for writing
  if (!laserPrefs.isKey("offset")) {
    Serial.println("No laser calibration data found. Please calibrate and store offsets.");
    // Perform laser sensor calibration and store offsets if necessary
  } else {
    Serial.println("Laser calibration data loaded.");
    // Load laser calibration offsets if they exist
  }

  sensor.setTimeout(500);
  if (!sensor.init())
  {
    Serial.println("Failed to detect and initialize sensor!");
    while (1) {}
  }

  // Start continuous back-to-back mode (take readings as
  // fast as possible).  To use continuous timed mode
  // instead, provide a desired inter-measurement period in
  // ms (e.g. sensor.startContinuous(100)).
  sensor.startContinuous();
  // Perform calibration if no stored data found
  for (int i = 0; i < numCalPoints; i++) {
    Serial.print("Place the sensor at ");
    Serial.print(actualDistances[i]);
    Serial.println(" mm");
    delay(5000); // Wait for user to place the sensor

    measuredDistances[i] = measureDistance();
    Serial.print("Measured distance: ");
    Serial.println(measuredDistances[i]);
    // Prompt for user confirmation of measured distance
    if (!confirmDistance(actualDistances[i], measuredDistances[i])) {
      Serial.println("Measurement rejected. Please adjust sensor position and retry.");
      i--; // Retry measurement for the same distance
    } else {
      Serial.println("Measurement accepted.");
    }
  }

  // Calculate calibration parameters
  offset = calculateOffset();
  scale = calculateScale();

  // Store calibration data
  laserPrefs.putFloat("offset", offset);
  laserPrefs.putFloat("scale", scale);
  Serial.println("Calibration data stored in laserPrefs");
 
  Serial.print("Calibration offset: ");
  Serial.println(offset);
  Serial.print("Calibration scale: ");
  Serial.println(scale);

  Serial.println("Entering main loop...");
}

void loop()
{
  int tofDist = sensor.readRangeContinuousMillimeters();
  if(tofDist < 2000 && tofDist != 8191){
    float calibratedDist = tofDist * scale + offset;
    Serial.print("Raw Distance: ");
    Serial.print(tofDist);
    Serial.print(" mm, Calibrated Distance: ");
    Serial.print(calibratedDist);
    Serial.println(" mm");
    delay(1000); // Adjust delay as necessary
  }
 
  //if (sensor.timeoutOccurred()) { Serial.print(" TIMEOUT"); }

 
}

uint16_t measureDistance() {
  uint32_t sum = 0;  // Use uint32_t to avoid overflow if the sum gets large
  int validCount = 0;

  for (int i = 0; i < numMeasurements; i++) {
    uint16_t distance = sensor.readRangeContinuousMillimeters();
   
    if (distance < 4000) {  // Check if the distance is valid (assuming valid distances are < 4000 mm)
      sum += distance;
      validCount++;
      Serial.print("VM: ");
      Serial.print(validCount);
      Serial.print(" ");
      Serial.println(distance);
    } else {
      // Handle invalid measurements if necessary
      Serial.print("VM: ");
      Serial.print(validCount);
      Serial.print(" Invalid measurement: ");
      Serial.println(distance);
    }
   
    delay(50); // Small delay between measurements
  }

  Serial.print("SUM: ");
  Serial.println(sum);
  Serial.print("Valid measurements: ");
  Serial.println(validCount);

  if (validCount > 0) {
    return sum / validCount;  // Return the average of valid measurements
  } else {
    return 0;  // Return 0 or handle this case appropriately if no valid measurements were recorded
  }
}

float calculateOffset() {
  float sum = 0;
  for (int i = 0; i < numCalPoints; i++) {
    sum += (actualDistances[i] - measuredDistances[i]);
  }
  return sum / numCalPoints;
}

float calculateScale() {
  float sum = 0;
  for (int i = 0; i < numCalPoints; i++) {
    sum += (actualDistances[i] / measuredDistances[i]);
  }
  return sum / numCalPoints;
}
bool confirmDistance(uint16_t actual, uint16_t measured) {
  Serial.print("Measured distance is ");
  Serial.print(measured);
  Serial.print(" mm. Is this correct for ");
  Serial.print(actual);
  Serial.println(" mm? (y/n)");

  while (true) {
    if (Serial.available() > 0) {
      char response = Serial.read();
      if (response == 'y' || response == 'Y') {
        return true;
      } else if (response == 'n' || response == 'N') {
        return false;
      }
    }
    delay(100); // Wait for user input
  }
}

Sunday, June 2, 2024

Pinky's Lighthouse

 After getting communication set up with the Raspberry Pi controller in this post, we are off to send and interpret sensor data with ROS2.

Cartographer is a good starting point. In my ROS2 docker container:
sudo apt install ros-$ROS_DISTRO-cartographer

It installs, but it looks like CARTOGRAPHER IS NOT SUPPORTED ON IRON...    I don't know why this package is available...

Moving on to nav2.

From inside the container, I can't launch GUIs. My working container must be committed to create a new image and re-run with the following:

sudo docker run -it \

    --privileged \

    -p 8888:8888/udp \

    --name c5cont \

    --env DISPLAY=$DISPLAY \

    --env QT_X11_NO_MITSHM=1 \

    --volume /tmp/.X11-unix:/tmp/.X11-unix \

    --cpus="3" \

    c5 /bin/bash

Then create a run file that has the following:
#/bin/bash
sudo docker start <container name>
xhost +local:docker
sudo docker exec -it <container name> /bin/bash

Also look into creating a dockerfile:

I'll have to reformat the data sent from the lighthouse IMU and laser distance sensor to mimic a lidar array.


Friday, February 16, 2024

RL training using Nvidia Omniverse

 How to run Isaac gym on my computer:

C:\Users\jynx4\AppData\Local\ov\pkg\isaac_sim-2023.1.1\isaac-sim.gym.bat --ext-folder D:\isaac\

This includes default robots. In order to run training on Pinky, many files must be created along with importing a .xacro or .urdf robot description into a USD stage.

Modify this file:

by adding "omni.isaac.sensor" = {} at the end.


Gait phases:
  1. Stance: the foot is in contact with the ground
    1. Loading: the foot transitions from non-contact to contact
    2. Travel: foot follows the opposite vector of the command
    3. Pre-swing: as the foot nears the end of its range of motion, it waits for 4 feet to be in contact with the ground
  2. Swing: the food is in the air
    1. Unloading: the foot transitions from non-contact to contact
    2. Position: the foot lifts to a minimum height for clearance while maintaining minimal distance from the center of the robot.
    3. Travel: moves to maximize its stride
    4. Pre-stance: waits until only 3 feet are in contact with the ground

Friday, February 9, 2024

Micro-ROS adventure

 PS C:\Program Files\uagent_superbuild\bin> .\MicroXRCEAgent.exe udp4 -p 2018 -v 6