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.

Friday, February 9, 2024

Micro-ROS adventure

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

Monday, January 15, 2024

Pinky: ROSberry pi controller

 


Raspberry Pi 4 setup:
sudo apt update
sudo apt upgrade
sudo raspi-config
    enable VNC 1080p resolution
    expand filesystem
    enable SPI
#Increasing Swap because this RPI has only 2gb of memory\
sudo dphys-swapfile swapoff
sudo nano /etc/dphys-swapfile
CONF_SWAPSIZE=2048
sudo dphys-swapfile setup
sudo dphys-swapfile swapon
sudo reboot
Setting up Docker:
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh ./get-docker.sh 
sudo docker run -it  ros:iron-ros-core #use --privileged to use spi and gpio
sudo docker rename e8b9c16b0544 ROS-TEST
sudo docker exec -ti ROS-TEST /bin/bash
apt update
apt install apt-utils nano whiptail apt install python3-colcon-common-extensions python3-rosdep
python3 -m pip install gpiozero
apt upgrade
mkdir -p ~/colcon_venv/src
cd ~/colcon_venv/
# Make a virtual env and activate it
#apt install python3.10-venv #from raspberry pi
#python3 -m venv env #from raspberry pi
#Modified from ROS
virtualenv -p python3 --system-site-packages ./rosvenv
source ./rosvenv/bin/activate
#Make sure that colcon doesn’t try to build the venv
touch ./rosvenv/COLCON_IGNORE
pip install gpiozero adafruit-circuitpython-mcp3xxx

Building a package:
~/colcon_venv/src# ros2 pkg create --build-type ament_python --license Apache-2.0 CustomController
#Warnings thrown:/usr/lib/python3/dist-packages/setuptools/command/install.py:34: SetuptoolsDeprecationWarning: setup.py install is deprecated. Use build and pip and other standards-based tools.
#setuptools version 59.6.0

ROS Build
#Modify /root/colcon_venv/src/control_package/control_package/control_node.py with codes below.
colcon build --packages-select control_package
source install/local_setup.bash
ros2 run control_package control_node


Docker reentry
sudo docker start existing_container_ID_or_name
sudo docker exec -it existing_container_ID_or_name /bin/bash
3D printed Controller on the workbench:


Custom Pi Hat:
The joystick has 3 analog outputs. These run through an MCP3008

ANALOG:

  • Up/down is chan1, 
  • Left/Right is chan2
  • Twist is chan0. 
#!/usr/bin/env python3

#ROS2
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Joy
from std_msgs.msg import String

#RaspberryPi
import RPi.GPIO
#from gpiozero import MCP3008, Button
import adafruit_mcp3xxx.mcp3008 as MCP
from adafruit_mcp3xxx.analog_in import AnalogIn
import busio
import digitalio
import board

import time


def gpio_callback(data, channels, pub):
    # Process GPIO inputs here

    analog_values = [mcp[i].value for i in range(3)]
    # Display the input values
    print(f"Analog Inputs (MCP3008): {analog_values}")

def main():
    rclpy.init()

    # Set up MCP3008 on SPI0 with CE0 as the chip select
    # create the spi bus
    spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)

    # create the cs (chip select)
    cs = digitalio.DigitalInOut(board.D24)

    # create the mcp object
    mcp = MCP.MCP3008(spi, cs)
    #gpio0 mcp = [MCP3008(channel=i) for i in range(3)]

    # define the channel pins
    channel_pins = [MCP.P0, MCP.P1, MCP.P2]

    # create an array of analog input channels
    channels = [AnalogIn(mcp, pin) for pin in channel_pins]

    # iterate over the channels
    for channel, chan in enumerate(channels):
        print(f'Raw ADC Value (Channel {channel}): ', chan.value)
        print(f'ADC Voltage (Channel {channel}): {chan.voltage}V')
        print()

    node = rclpy.create_node('joy_publisher')

    # create a Joy publisher
    joy_publisher = node.create_publisher(Joy, 'joy', 10)

    # set the publishing rate
    publish_rate = 1  # 1 Hz
    timer_period = 1.0 / publish_rate

    def publish_joy():
        # create a Joy message
        joy_msg = Joy()

        # iterate over the channels and add readings to the axes
        for channel, chan in enumerate(channels):
            joy_msg.axes.append(chan.value)

        # publish the Joy message
        joy_publisher.publish(joy_msg)

    timer = node.create_timer(timer_period, publish_joy)

    rclpy.spin(node)

    # shutdown when done
    node.destroy_node()
    rclpy.shutdown()

    print("DONE!")

if __name__ == '__main__':
    main()
This was the first input challenge. It took all day to get the RPI/Docker/ROS to cooperate. It looks like my twist is wired backward and it tampers with other readings when i bring it high.


DIGITAL:

  • Joystick button
    • GPIO 12
  • D-Pad
    • Up
    • Down
    • Left
    • Right
    • Center
    • Rotary Encoder
import RPi.GPIO as GPIO
import rclpy
from rclpy.node import Node
from sensor_msgs.msg import Joy
from std_msgs.msg import Header
from builtin_interfaces.msg import Time
from adafruit_mcp3xxx.analog_in import AnalogIn
import adafruit_mcp3xxx.mcp3008 as MCP
import busio
import digitalio
import board
import time

class JoyPublisherNode(Node):
    def __init__(self):
        super().__init__('joy_publisher')

        # Set up MCP3008 on SPI0 with CE0 as the chip select
        spi = busio.SPI(clock=board.SCK, MISO=board.MISO, MOSI=board.MOSI)
        cs = digitalio.DigitalInOut(board.D24)
        mcp = MCP.MCP3008(spi, cs)

        # Define the channel pins
        channel_pins = [MCP.P0, MCP.P1, MCP.P2]
        self.channels = [AnalogIn(mcp, pin) for pin in channel_pins]

        # Define GPIO pins for buttons
        self.button_pins = [12, 26, 17, 27, 22, 4]
        GPIO.setmode(GPIO.BCM)
        GPIO.setup(self.button_pins, GPIO.IN, pull_up_down=GPIO.PUD_UP)

        # Define GPIO pins for rotary encoder
        self.encoder_pin_a = 19  # CW
        self.encoder_pin_b = 13  # DT - these might need to be switched
        GPIO.setup(self.encoder_pin_a, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.setup(self.encoder_pin_b, GPIO.IN, pull_up_down=GPIO.PUD_UP)
        GPIO.add_event_detect(self.encoder_pin_a, GPIO.BOTH, callback=self.encoder_callback)

        # Create a Joy publisher
        self.joy_publisher = self.create_publisher(Joy, 'joy', 10)

        # Set the publishing rate
        publish_rate = 1  # 1 Hz
        timer_period = 1.0 / publish_rate
        self.timer = self.create_timer(timer_period, self.publish_joy)

        self.last_encoder_state = GPIO.input(self.encoder_pin_a)
        self.encoder_counter = 0 # Initialize the encoder counter

    def publish_joy(self):
        # Create a Joy message
        joy_msg = Joy()
        joy_msg.header = Header()
        joy_msg.header.stamp = self.get_clock().now().to_msg()
        joy_msg.header.frame_id = 'base_link'

        # Read analog inputs
        joy_msg.axes.extend([chan.value for chan in self.channels])

        # Read button states
        joy_msg.buttons.extend(GPIO.input(button_pin) for button_pin in self.button_pins)

        # No rotation initially
        joy_msg.axes.append(self.encoder_counter)

        # Publish the Joy message
        self.joy_publisher.publish(joy_msg)

    def encoder_callback(self, channel):
        # Callback function for rotary encoder
        a_state = GPIO.input(self.encoder_pin_a)
        b_state = GPIO.input(self.encoder_pin_b)

        if a_state != self.last_encoder_state:
            if b_state != a_state:
                self.last_encoder_state = a_state
                # Counter-clockwise rotation
                self.publish_encoder_rotation(-1.0)
            else:
                self.last_encoder_state = a_state
                # Clockwise rotation
                self.publish_encoder_rotation(1.0)

    def publish_encoder_rotation(self, rotation):
        # Create a Joy message for rotary encoder
        joy_msg = Joy()
        joy_msg.header = Header()
        joy_msg.header.stamp = self.get_clock().now().to_msg()
        joy_msg.header.frame_id = 'base_link'

        # Read analog inputs
        joy_msg.axes.extend([chan.value for chan in self.channels])

        # Read button states
        joy_msg.buttons.extend(GPIO.input(button_pin) for button_pin in self.button_pins)

       # Update the encoder counter based on rotation direction
        if rotation > 0:
            self.encoder_counter += 15
        elif rotation < 0:
            self.encoder_counter -= 15

        # Set axes for rotary encoder
        joy_msg.axes.append(self.encoder_counter)

        # Publish the Joy message
        self.joy_publisher.publish(joy_msg)

def main():
    rclpy.init()
    joy_publisher_node = JoyPublisherNode()
    rclpy.spin(joy_publisher_node)
    joy_publisher_node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()















I had problems with the twist joystick impacting the other potentiometers. This was because my particular stick had the colors off from what I expected.
As you can see, the resistance across the red-black was 5k, not 10k.


Below you can see, that I found the full resistance. I used red as 3.3V, white to ground, and black as the signal.

Saturday, May 27, 2023

Pinky's LIDAR

Hardware:

  1. N20 Miniature DC Geared Motor Encoder Geared Motor 6V 265RPM
  2. Adafruit DRV8871 DC Motor Driver Breakout Board - 3.6A Max

Driving the rotating assembly will be gears. I will try to design some herringbone gears in F360, so that there will be no axial slippage. I'll be trying out the GF Gear Generator plug-in.

Parameters:

Internal. Simple/Double Helical gear. 1.2mm Module, 15 Teeth, Pressure angle 20 degrees.

External: Simple/Double Internal Helical Gear. 1.2mm Module, 20 degrees PA. 100 Teeth.


The Module and Pressure angle have to match so that the gear system meshes.


Pinky/s top microprocessor is an esp32 devkit. It is connected via slip ring to the lower nano rp2040. Rx on the rp2040 is gpio1, tx is 0. Colored brown and green wires respectively.

Setting up the IMU:

https://learn.adafruit.com/adafruit-bno055-absolute-orientation-sensor/device-calibration

https://learn.adafruit.com/how-to-fuse-motion-sensor-data-into-ahrs-orientation-euler-quaternions/magnetic-calibration-with-motioncal

Monday, April 10, 2023

Reinforcement Learning

 Beginning some new training. For myself too!

Update 5/3/23:

I have a reduced model in Fusion 360 that only contains bodies necessary for robotic linkage.
Found a fancy URDF exporter for F360 on GitHub. I had to be sure to not have nested legs, all the joints must be in the main component level.
To import into Matlab, I needed the robotics toolbox add-on. To import the robot: 
pinky = importrobot('C:\Users\jynx4\OneDrive\Documents\Pinky\urdf\pinky_urdf_description\urdf\pinky_urdf.xacro')
That throws an error: Error using robotics.manip.internal.xacroSupport.convertXacroToURDF
Unable to process Xacro content for the following reasons: "substitution args not supported:
when processing file: C:\Users\jynx4\OneDrive\Documents\Pinky\urdf\pinky_urdf_description\urdf\pinky_urdf.xacro
". Do not use roslaunch tag attributes, which can use substitution args like $(find pkg), $(arg foo), etc. For more information on
substitution args, see https://wiki.ros.org/roslaunch/XML#substitution_args

Error in importrobot (line 172)
                [urdfString, pathToDataFile] = robotics.manip.internal.xacroSupport.convertXacroToURDF(parsedInput.Results.input);
To solve this, I had to delete the following two lines from the .xacro file.
In Matlab: "show(pinky)" command gives signs of success.

Tuesday, March 28, 2023

Upgrading Pinky's Ankle Servos

 I've decided to upgrade from sg90s to mg996Rs.

Since there is such a significant size difference, I'll have to do some redesigns. I'll start with the gearing. Since I still want to reduce the servo's natural 180-degree movement into 90-degree ankle movement, I'll implement a 2:1 gear ratio. This will limit the range of motion and increase the torque.

The servo shaft adapter requires an 8.75mm center hole, so I'll bump that up to 8.85 for clearance. 3D-printed plastic also shrinks a little. Other parameters for the 996 spur gear; Metric, 20 degree pressure angle, module = 1, #Teeth 24, backlash 0, root fillet radius .5mm, gear thickness 5mm. This is quite a large gear and the foot's gear will have to be twice as large! A 48mm foot gear is going to be too large.

OK, next attempt. Remix a servo horn from Thingiverse to mesh with the original drive gear.
The horn adapter fits!/
4/13/23 UPDATE:
Decided to move the MG996Rs to the knee joints and 995s to the ankles. For the ankles, I set the servo driver to 800 for it to be straight. The knees were set to 2200 straight. Straight means 0-degree bend.
Hips do not need recalibration. Calibration app:https://ai2.appinventor.mit.edu/b/3is9x
0. -
1. -
2. -
3. - Knee Recalibrated
4. ReCalibrated
5. 
6. 
7. Knee Recalibrated
Waiting on prints.
4/14
the foot weighs 1.8g, 2.8 with bearing.
The thigh weighs 18g with bearing.8 without.