icon

Robot Arms for Accessibility

Things used in this project


Hardware components


FireBeetle Esp32-e - A wifi enabled microcontroller that can handle a lot of code
4 buttons
1 Linear Actuator
1 Joystick

1 L298N - This is used to get our linear actuator working


Software


Arduino IDE    

Visual Studio Code

 

Other Tools Needed


3D Printer

 

 

Intro

The idea of this project is inspired by the Travel challenge of the Build2gether contest. It seems there are missing accommodations with handicapped accessibility, so my thinking is that with an easy to use way to extend well past the range an individual in a wheelchair would normally have, we can drastically reduce the inconveniences these individuals experience. A couple of examples in the Discord involved the bathroom and, whether it's soap or paper towels, the robot arm should be able to easily grab what's needed and pass it to the user.

That said, what we're doing is ensuring we have plenty of ways to utilize a robot arm, so this one can be just fun for anyone. Whether you're looking to arm wrestle your Arduino or need a ceiling mounted candy dispenser that can drop candy in your mouth by voice command (I may or may not have considered this), you should be all set to create it by following along.

 

 

 

The Backstory

Realizing that something like a robot arm would be helpful brought me back to a project I had started years ago, before I knew much about electronics at all. I knew I wanted to learn how to make gadgets and gismos, when all the sudden a Kickstarter popped up for an inexpensive, Arduino-driven robot arm. Needless to say, I backed it and found myself unable to get it working in every way - code, hardware, etc. I assumed that it didn't work because I had no idea what I was doing, but a couple years later, I revisited it to find that the hardware did indeed just not work (I probably got unlucky), the company no longer existed, and the android app still crashed on launch. To be clear, this is for context, not to flame this company - with Kickstarter they could have just as easily not followed through at all, but instead they did provide some working servos as well as what I think is overall a very nice set of 3d models to work with as a starting point. While disappointing for it to not work, it's reasonable that if they're not making money with the product, that they have moved on and don't support it anymore.

As someone who is hot garbage at 3d modeling (me), this provides an excellent starting place for the goals of this project. In short, I'm using this LittleArm V3 setup because their 3D models are a good starting place. We don't need a little arm, though. We need a practically sized arm that can be utilized in a real life situation, so we're upscaling it and adjusting everything to accommodate more powerful servos.

Upgrading the arm in size, strength, and adding a ton of additional functionality

Upgrading the arm in size, strength, and adding a ton of additional functionality

 

I do want to emphasize, though, that this is not simply a matter of upscaling an existing robot and calling it a day. Instead of having a team iterating a 4th generation of the arm, it's just me stumbling through Blender to update the model to work correctly with different servos and setups and, more notably, I completely rewrote all the code for entirely. While this is written with the Little Arm's setup in mind, it boils down to different ways to control servos in a manner that suits robot arms, which can be easily translated to other designs. Plus we added a lot of other bells and whistles, as well as an enhancement I think is critical for this project, which I'll get to shortly.

 

 

 

3D Modeling

As hopefully anyone that follows my projects can vouch, I typically share everything I can realistically share, whether that's code or 3d models, etc. Even though I've modified all of these models, they are based on a paid, unshared model from someone else. As such, I don't think that I'm allowed to share them, so we'll instead briefly go through the setup. I will, however, share the base add-on since I created that one from scratch.

There is a base platform, which is essentially a shoulder socket. There is also open space which is used for support as well as a place to house the electronics. In my design for the base add-on, there is an opening in each corner so that a screwdriver can fit through. This gives some options for attaching both to a surface that allows for it, bolting them together, or just leaves a space for a zip tie or whatever else may fit the situation.

The upper arm holds the shoulder and elbow servos. This, I cut in half and added a slot for an actuator to go through. The base of the actuator is bolted into the shoulder side, and the extendable part slides through and the extendable part is bolted into the elbow side.

The final part is the gripper. This is the one model I only changed in terms of sizing, and I think the model is pretty clever. It uses a small piece of metal to bend that's attached to the gripper's servo to move the gripper back and forth. Conveniently, I upscaled everything by 60%, and at this stage a coat hanger wire fits perfectly. I was thinking I'd try it as a temporary solution and find something else, but for anyone following along with these specific sets of models - the coat hanger is the way to go. It fits perfectly and is just mailable enough to get in place while also being rigid enough to do exactly what you need it to do.

 

 

 

Assembly!

In regard to the arm itself, the joints connect via a section with a half-sphere on one side and a servo on the other. The servo horn slots into one side, and the half sphere slides into place on the other to form a nice, moveable joint. The base is connected by the servo itself. Break down a coat hanger to size and fold it into place such that it can go through the hole in the servo horn on one side and go through the hole for the gripper on the other side. At a scale of 160, the coat hanger will fit perfectly.

There are a lot of electronics to discuss, but it's definitely worth it to have all the extra functionality. The ESP32's are pretty particular about pin usage, but I've listed out all the pins I used below, so if you use those everything will work correctly. There are 4 servos, which control the base, shoulder, elbow, and grip of the arm. In my setup, I'm using 20kg servos, so these get connected to external power and a shared ground that then leads back to the microcontroller's GND. The easiest way to setup the buttons is with a mini breadboard, also with a shared grounding. The joystick can be powered with the VIN of the microcontroller, and the rest of the pins are listed below. The linear actuator requires external power, so for this I utilized an L298N. Power the L298N and create another common ground. Connect the actuator's wires to the OUT1 and OUT2 slots. Connect the L298N's IN1 and IN2 to the ESP32 pins (25 and 26 in our case). If the actuator extends/retracts backward, it's very easy to switch this around in the code or by flipping these wire connections.

Here's a full pinout of the setup:

Servo Motors:

Servo 1 (Base): Pin 5Servo 2 (Shoulder): Pin 27Servo 3 (Elbow): Pin 21Servo 4 (Grip): Pin 18

Button Pins:

Button 1: Pin 4Button 2: Pin 14Button 3: Pin 13Button 4: Pin 12

Joystick:

Joystick X Axis: Pin 33Joystick Y Axis: Pin 32Joystick Button: Pin 23Joystick VCC: VIN

ESP32 to L298N:

ESP32 Pin 25 (Actuator Extend) → L298N IN1ESP32 Pin 26 (Actuator Retract) → L298N IN2ESP32 GND → L298N GND (Common ground)

L298N to Linear Actuator:

L298N OUT1 → Linear Actuator Red wireL298N OUT2 → Linear Actuator Black wire

Power Supply:

External Power Supply (+V) → L298N +12V or appropriate voltage input (depends on actuator specifications)External Power Supply GND → L298N GNDPower Supply:External Power Supply (+V) → L298N +12V or appropriate voltage input (depends on actuator specifications)External Power Supply GND → L298N GND

 

 

 

Arduino Code:

I went with the ESP32 partly because it's easy to use with Wi-Fi, but also because it can handle more code than some of the other Arduinos. There's a lot going on in the code, since I wanted to provide as much functionality in the device as possible. I did rewrite it to use an array of servos to reduce the number of lines of code for those using other devices, however.

The starting point is controlling the 4 servos. We determine the numerical range on the servos that correlate with the back and forth arm movement we want. I did this by setting the servos to 90 degrees and then adding the servo horn to each. Then I ran a quick test to verify that the positioning was correct. The function handleMove takes the directional input and moves the relevant servo(s). It uses a string because we're also taking these same commands from a python program over WiFi, which we'll get to in a minute, and for added readability.

There are 4 buttons. It'd be easy to tweak what each button does at this point, but in my setup the first one toggles the grip. Pressing the joystick also does this, but since it can be a bit tricky to do that without also moving the joystick, I added a separate button for it. I added a highly optional function called preplannedMovement, which has 2 parts to it. The servo goes from its rest position to forward with an open hand. The idea is that then the user can tweak the positioning and/or extend the actuator and press the button for preplannedMovement again and it will grab the item in front of it, deliver it to the user, and go back to its rest position. The 3rd and 4th buttons move the actuator in an out to extend or retract the robot arm.

The joystick moves the arm forward, back, and side to side as well as toggle the grip.

 

 

 

Python Code:

For on the go control, it makes sense for a person to be right there controlling the arm. At home, however, it could easily be setup somewhere in a long term capacity and perform tasks that are out of reach or inconvenient for the user from anywhere. Since these may be repeatable tasks, we can record the robots movements and play them back such that, later on, we can just hit "play" and the robot arm will do whatever you've setup. This could be anything from starting the water boiling first thing in the morning, turning on a fan, or reaching into a bag of dog treats and dropping one down. Otherwise, it has all the same functionality as with the direct controls, where you can move the arm, open close the grip, etc.

 

This code is all included in the project as well.

 

 

 

Conclusion

At this point we have a fully working robot arm that can be used with intuitive joystick and button controls anywhere. We also have controls that can run via a python app to control the arm anywhere with WiFi for any number of uses - it could easily be leveraged for autonomous uses as well with the recorded playback feature. Either way, whether you're disabled and need a good solution for getting a hold of things that are out of reach, or you just think robot arms are cool and want something to throw a cat toy from across the house, hopefully you enjoyed. Have a good one.

CODE
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
#include <ArduinoJson.h>
#include <Wire.h>

const char* ssid = "< Your wifi >";
const char* password = "< Your wifi password >";
WebServer server(80);

Servo servos[4];

//For anyone wondering about the crazy pin numbering, that's apparently how ESP32's roll - they're all over the place
int servoPins[4] = { 5, 27, 21, 18 };  // Base, Shoulder, Elbow, Grip

// Button pins
const int buttonPin1 = 4;
const int buttonPin2 = 14;
const int buttonPin3 = 13;
const int buttonPin4 = 12;

const int joyButtonPin = 23;

// Linear actuator control pins
const int actuatorPin1 = 25;  // Linear actuator extend
const int actuatorPin2 = 26;  // Linear actuator retract
const int joyXPin = 33;
const int joyYPin = 32;

int joyLowThreshold = 2250;
int joyHighThreshold = 2900;

int currentPositions[4] = { 90, 100, 110, 50 };
int desiredPositions[4] = { 90, 100, 110, 110 };

bool gripperIsClosed = true;

int moveAmount = 5;  // change in degrees for each move command
int currentPreplannedStep = 0;

void setup() {
  Serial.begin(9600);
  for (int i = 0; i < 4; i++) {
    servos[i].attach(servoPins[i]);
    servos[i].write(currentPositions[i]);
  }

  //Don't indefinitely loop - we can use it with buttons and the joystick offline
  if (connectToWiFi()) {
    Serial.print("Connected to Wi-Fi with IP: ");
    Serial.println(WiFi.localIP());
    server.on("/setPosition", HTTP_GET, setPosition);
    server.on("/preplanned", HTTP_GET, preplannedMovement);
    server.on("/move", HTTP_GET, callMove);
    server.on("/controlGrip", HTTP_GET, controlGrip);
    server.on("/getCurrentPositions", HTTP_GET, getCurrentPositions);  // Add the new route

    server.onNotFound(handleNotFound);
    server.begin();
  }

  // Setup button pins
  pinMode(buttonPin1, INPUT_PULLUP);
  pinMode(buttonPin2, INPUT_PULLUP);
  pinMode(buttonPin3, INPUT_PULLUP);
  pinMode(buttonPin4, INPUT_PULLUP);

  // Setup joystick pins
  pinMode(joyXPin, INPUT);              // Set the joystick X axis as input
  pinMode(joyYPin, INPUT);              // Set the joystick Y axis as input
  pinMode(joyButtonPin, INPUT_PULLUP);  // Set the joystick button as input with pull-up resistor


  pinMode(actuatorPin1, OUTPUT);
  pinMode(actuatorPin2, OUTPUT);

  gripperToggle();  //Looks cool and makes it clear the arm is ready for use
}

void moveActuator(bool moveOut) {
  digitalWrite(actuatorPin1, moveOut ? HIGH : LOW);
  digitalWrite(actuatorPin2, moveOut ? LOW : HIGH);
  delay(2000);                      // Active movement duration
  digitalWrite(actuatorPin1, LOW);  // Stop the actuator
  digitalWrite(actuatorPin2, LOW);
}

void loop() {
  server.handleClient();

  processControls();
}

void gripperToggle() {
  moveServoToPosition(servos[3], currentPositions[3], gripperIsClosed ? 50 : 110);
  gripperIsClosed = !gripperIsClosed;
}

void processControls() {

  //All the buttons!  If you want them to do something else, just change what function they call here -
  if (digitalRead(buttonPin1) == LOW) {
    gripperToggle();
    Serial.println("Button1 pressed - toggle grip");
  }

  if (digitalRead(buttonPin2) == LOW) {
    Serial.println("Button2 pressed - preplannedMovement");
    preplannedMovement();
  }

  if (digitalRead(buttonPin3) == LOW) {
    Serial.println("Button3 pressed - actuator out");
    moveActuator(true);
  }

  if (digitalRead(buttonPin4) == LOW) {
    moveActuator(false);
    Serial.println("Button4 pressed - actuator in");
  }

  // Read joystick position
  int joyX = analogRead(joyXPin);  // Read the joystick X axis
  int joyY = analogRead(joyYPin);  // Read the joystick Y axis

  if (joyX < joyLowThreshold) {
    handleMove("left");
    Serial.println("Joystick activated - Move LEFT");
    Serial.print("Joystick X Read: ");
    Serial.println(joyX);
  } else if (joyX > joyHighThreshold) {
    handleMove("right");
    Serial.println("Joystick activated - Move RIGHT");
    Serial.print("Joystick X Read: ");
    Serial.println(joyX);
  }

  if (joyY < joyLowThreshold) {
    handleMove("up");
    Serial.println("Joystick activated - Move UP");
    Serial.print("Joystick Y Read: ");
    Serial.println(joyY);
  } else if (joyY > joyHighThreshold) {
    handleMove("down");
    Serial.println("Joystick activated - Move DOWN");
    Serial.print("Joystick Y Read: ");
    Serial.println(joyY);
  }

  if (digitalRead(joyButtonPin) == LOW) {
    gripperToggle();
    Serial.println("Joystick pressed - toggle grip");
  }

  delay(200);  // Delay to avoid flooding the serial output
}

bool connectToWiFi() {
  WiFi.begin(ssid, password);
  unsigned long startTime = millis();
  while (WiFi.status() != WL_CONNECTED) {
    if (millis() - startTime > 15000) {  // 15 seconds timeout
      Serial.println("Failed to connect to WiFi.");
      return false;
    }
    delay(100);
  }
  return true;
}

void getCurrentPositions() {
  StaticJsonDocument<200> doc;  // Create a JSON document to store position data
  doc["base"] = currentPositions[0];
  doc["shoulder"] = currentPositions[1];
  doc["elbow"] = currentPositions[2];
  doc["grip"] = currentPositions[3];

  String response;
  serializeJson(doc, response);  // Serialize the JSON document to a string

  server.send(200, "application/json", response);  // Send the JSON response
}

void controlGrip() {
  if (server.hasArg("action")) {
    String action = server.arg("action");
    if (action == "open") {
      moveServoToPosition(servos[3], currentPositions[3], 40);
      gripperIsClosed = false;
    } else if (action == "close") {
      moveServoToPosition(servos[3], currentPositions[3], 110);
      gripperIsClosed = true;
    }
    server.send(200, "text/plain", "Grip " + action);
  } else {
    server.send(400, "text/plain", "Action parameter is missing.");
  }
}

void moveServoToPosition(Servo& servo, int& currentPosition, int desiredPosition) {
  Serial.println("moveServoToPosition");
  while (currentPosition != desiredPosition) {
    Serial.println(currentPosition);
    currentPosition += (currentPosition < desiredPosition) ? 1 : -1;
    servo.write(currentPosition);
    delay(15);
  }
}

void setPosition() {
  if (server.hasArg("index") && server.hasArg("position")) {
    int index = server.arg("index").toInt();
    int position = server.arg("position").toInt();
    if (index >= 0 && index < 4) {  // Check if the index is within the range of your servos array
      moveServoToPosition(servos[index], currentPositions[index], position);
      server.send(200, "text/plain", "Servo " + String(index) + " set to position " + String(position));
    } else {
      server.send(400, "text/plain", "Invalid servo index.");
    }
  } else {
    server.send(400, "text/plain", "Missing servo index or position.");
  }
}

void handleNotFound() {
  server.send(404, "text/plain", "Command not found.");
}

void handleMove(String direction) {

  if (direction == "left") {
    desiredPositions[0] = max(0, min(180, desiredPositions[0] + moveAmount));
    Serial.println("Moving LEFT");
  } else if (direction == "right") {
    desiredPositions[0] = max(0, min(180, desiredPositions[0] - moveAmount));
    Serial.println("Moving RIGHT");
  } else if (direction == "up") {
    desiredPositions[1] = max(0, min(180, desiredPositions[1] - moveAmount));
    desiredPositions[1] = max(0, min(180, desiredPositions[1] - moveAmount));
  } else if (direction == "down") {
    desiredPositions[1] = max(0, min(180, desiredPositions[1] + moveAmount));
    desiredPositions[1] = max(0, min(180, desiredPositions[1] + moveAmount));
  }

  moveServoToPosition(servos[0], currentPositions[0], desiredPositions[0]);
  moveServoToPosition(servos[1], currentPositions[1], desiredPositions[1]);
  moveServoToPosition(servos[2], currentPositions[2], desiredPositions[2]);
}

void callMove() {
  if (server.hasArg("direction")) {
    handleMove(server.arg("direction"));
    server.send(200, "text/plain", "Move command executed.");
  } else {
    server.send(400, "text/plain", "Direction parameter is missing.");
  }
}

struct MovementStep {
  int basePos;
  int shoulderPos;
  int elbowPos;
  int gripPos;
};

void preplannedMovement() {
  if (currentPreplannedStep == 0) {
    Serial.println("Executing Step 1: Open grip, move shoulder and elbow forward.");
    // Open the grip and move the shoulder and elbow forward
    moveServoToPosition(servos[3], currentPositions[3], 0);    // Open grip
    moveServoToPosition(servos[1], currentPositions[1], 120);  // Shoulder forward
    moveServoToPosition(servos[2], currentPositions[2], 90);   // Elbow forward
    gripperIsClosed = false;
    currentPreplannedStep = 1;  // Set next step as step 2
  } else {
    Serial.println("Executing Step 2: Close grip, move base, adjust shoulder/elbow, and open grip.");
    // Close the grip and move the base to the left 90 degrees
    moveServoToPosition(servos[3], currentPositions[3], 110);  // Close grip
    gripperIsClosed = true;
    moveServoToPosition(servos[0], currentPositions[0], 150);  // Base to the left position

    // Adjust the shoulder and elbow, then open the grip
    moveServoToPosition(servos[1], currentPositions[1], 140);  // Slight shoulder down
    moveServoToPosition(servos[2], currentPositions[2], 80);   // Slight elbow down
    moveServoToPosition(servos[3], currentPositions[3], 0);    // Open grip again
    gripperIsClosed = false;

    // Immediately return to rest position after step 2
    Serial.println("Returning to rest position.");

    // Go down the list the other way so we avoid bumping into obstacles (easy to loop through the other way if you are getting uppercut or some such)
    for (int j = 3; j >= 0; j--) {
      moveServoToPosition(servos[j], currentPositions[j], 90);  // Reset to rest position
    }
    currentPreplannedStep = 0;  // Set next step as step 1
  }
  server.send(200, "text/plain", "Preplanned movement completed.");
}
CODE
import requests
import tkinter as tk
from tkinter import ttk
import time
import json
import os

# Base URL of the ESP32 server
BASE_URL = "http://192.168.86.30"

# Global variables
recordings = {}
current_recording = []  # List to store current recording steps
recording = False
selected_recording_name = None
recordings_file = 'recordings.json'  # File to store recordings

# Function to load recordings from a file
def load_recordings():
    global recordings
    if os.path.isfile(recordings_file):
        with open(recordings_file, 'r') as file:
            recordings = json.load(file)

# Function to save recordings to a file
def save_recordings():
    with open(recordings_file, 'w') as file:
        json.dump(recordings, file, indent=4)

# Function to delete a recording
def delete_selected_recording():
    global selected_recording_name
    if selected_recording_name and selected_recording_name in recordings:
        del recordings[selected_recording_name]
        selected_recording_name = None
        save_recordings()  # Save the updated recordings to the file
        update_recording_listbox()
        print(f"Deleted recording '{selected_recording_name}'.")
        
# Function to fetch and store the initial positions of all servos at the start of recording
def fetch_and_store_initial_positions():
    positions = get_initial_positions()
    if positions:
        current_recording.append(('setPosition', positions))
        print("Initial positions stored:", positions)
    else:
        print("Failed to get initial positions.")

# Function to get the initial servo positions from the Arduino
def get_initial_positions():
    url = f"{BASE_URL}/getCurrentPositions"
    try:
        response = requests.get(url)
        if response.status_code == 200:
            return response.json()  # Parse JSON response into a dictionary
        else:
            print("Failed to get initial positions. Status code:", response.status_code)
            return None
    except requests.exceptions.RequestException as e:
        print("Request failed:", e)
        return None

# Function to start/stop recording
def toggle_recording():
    global recording, current_recording
    recording = not recording
    if recording:  # Start recording
        current_recording = []  # Clear previous recording
        fetch_and_store_initial_positions()  # Fetch and store initial positions
        record_button.config(text="Stop Recording")
        print("Recording started.")
    else:  # Stop recording
        if current_recording:  # If actions were recorded, save them
            recording_name = "Recording " + str(len(recordings) + 1)
            recordings[recording_name] = current_recording
            update_recording_listbox()
            print(f"Recording '{recording_name}' saved with {len(current_recording)} actions.")
            save_recordings()
        else:
            print("No actions recorded.")
        record_button.config(text="Start Recording")
        current_recording = []
        
# Function to play the selected recording
def play_recording():
    global selected_recording_name
    if selected_recording_name and selected_recording_name in recordings:
        actions = recordings[selected_recording_name]
        if actions:
            # Set initial position
            action_type, positions = actions[0]
            if action_type == 'setPosition':
                print(f"Setting initial positions for recording {selected_recording_name}: {positions}")
                set_positions(**positions)  # Set positions
                time.sleep(2)  # Wait for servos to reach the starting positions

            # Play actions
            for action_type, action in actions[1:]:  # Skip the initial setPosition
                if action_type == 'move':
                    print(f"Moving servo {action}")
                    move_servo(action)
                elif action_type == 'grip':
                    print(f"Gripping action {action}")
                    control_grip(action)
                time.sleep(0.5)  # Delay for the servo to move
        else:
            print(f"Recording {selected_recording_name} is empty.")
    else:
        print("No recording selected or found.")

# Function to move the servo in a direction and record the action
def move_servo(direction):
    """Move the servo in the given direction and record the movement if recording is active."""
    url = f"{BASE_URL}/move"
    params = {"direction": direction}
    response = requests.get(url, params=params)
    if recording:  # Only record if we are in recording mode
        current_recording.append(('move', direction))  # Append the move action to the current recording
    print(response.text)

# Function to control the grip and record the action
def control_grip(action):
    """Send command to the ESP32 to control the grip and record the action."""
    url = f"{BASE_URL}/controlGrip"
    params = {"action": action}
    response = requests.get(url, params=params)
    if recording:  # Only record if we are in recording mode
        current_recording.append(('grip', action))  # Append the grip action to the current recording
    print(response.text)
    
# Function to set positions of servos
def set_positions(base=None, shoulder=None, elbow=None, grip=None):
    for i, position in enumerate([base, shoulder, elbow, grip]):
        if position is not None:
            url = f"{BASE_URL}/setPosition"
            params = {"index": i, "position": position}
            response = requests.get(url, params=params)
            print(response.text)
            time.sleep(0.1)  # Short delay between HTTP requests to avoid overloading the Arduino


def create_grip_control_frame(parent):
    frame = ttk.LabelFrame(parent, text="Grip Control", padding=(10, 10))

    # Create 'Open' button
    open_button = ttk.Button(frame, text="Open Grip", command=lambda: control_grip("open"))
    open_button.pack(side=tk.LEFT, padx=5, pady=5)

    # Create 'Close' button
    close_button = ttk.Button(frame, text="Close Grip", command=lambda: control_grip("close"))
    close_button.pack(side=tk.RIGHT, padx=5, pady=5)

    return frame

def create_servo_control_frame(parent):
    frame = ttk.LabelFrame(parent, text="Servo Control", padding=(10, 10))
    buttons = {'left': (0, 0), 'right': (0, 1), 'up': (0, 2), 'down': (0, 3)}
    for direction, pos in buttons.items():
        button = ttk.Button(frame, text=direction.capitalize(), command=lambda d=direction: move_servo(d))
        button.grid(row=pos[0], column=pos[1], padx=5, pady=5, sticky=(tk.W, tk.E))
    return frame

def create_record_playback_frame(parent):
    frame = ttk.Frame(parent, padding=(10, 10))
    global record_button
    record_button = ttk.Button(frame, text="Start Recording", command=toggle_recording)
    record_button.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(0, 5))

    play_button = ttk.Button(frame, text="Play Recording", command=play_recording)
    play_button.pack(side=tk.RIGHT, fill=tk.X, expand=True, padx=(5, 0))
    delete_button = ttk.Button(frame, text="Delete Recording", command=delete_selected_recording)
    delete_button.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=(5, 0))
    return frame

# Function to select a recording from the list
def select_recording(event):
    global selected_recording_name
    selection = event.widget.curselection()
    if selection:
        index = selection[0]
        selected_recording_name = event.widget.get(index)
      
# Function to update the Listbox with the recordings' names
def update_recording_listbox():
    recording_listbox.delete(0, tk.END)  # Clear the current list
    for name in recordings.keys():
        recording_listbox.insert(tk.END, name)
        

# GUI window setup
def gui():
    global window, recording_listbox, record_button
    load_recordings()  # Load recordings at the start of the program
    window = tk.Tk()
    window.title("Servo Control Panel")

    control_frame = create_servo_control_frame(window)
    control_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(10, 0))

    grip_control_frame = create_grip_control_frame(window)
    grip_control_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))

    record_playback_frame = create_record_playback_frame(window)
    record_playback_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))

    # Listbox for recordings
    recording_listbox = tk.Listbox(window)
    recording_listbox.pack(fill=tk.BOTH, expand=True, padx=10, pady=(0, 10))
    recording_listbox.bind('<<ListboxSelect>>', select_recording)

    update_recording_listbox()  # Initial update of the listbox

    # Entry for renaming recordings
    recording_name_entry = tk.Entry(window)
    recording_name_entry.pack(fill=tk.X, padx=10, pady=(0, 10))

    window.mainloop()

if __name__ == "__main__":
    gui()
License
All Rights
Reserved
licensBg
1