Posts: 4
Threads: 1
Joined: Nov 2025
Nov-10-2025, 09:15 PM
(This post was last modified: Nov-10-2025, 09:15 PM by racingsubby.)
Hello.
I'm new to python. I'm in a robotics class and I need to be able to control a 6-servo robotic arm. I'm using Python for the bulk of the work, then sending data via pySerial to an Ardunio Uno which will parse the message and command the servos. I can currently send a string with the 6 servo positions separated by space and parse it in the Arduino. For example, I can send "20 150 200 69 4 220 /n" and parse it in the Arduino.
Now I need to figure out how to programmatically update each position, create this message, and send it at a set update rate. This is where I help. If I have 6 independent integer variables: pos1, pos2, .....pos6, I can update their values easy enough but I'm struggling to figure out how to then assemble these into the message format above and send it out. I know Serial.write() requires data to be in a specific format. I've seen different YouTube videos but nothing quite specific to what I need. Again, I'm new to python, so its a bit confusing.
I can post what I have later tonight. Any guidance is appreciated!
Posts: 12,137
Threads: 496
Joined: Sep 2016
- What is the exact format that you are looking for on the arduino?
- What do the various parts of the command stand for?
- Looks like a series of single byte commands, as i see no value greater than 256. Is that true?
- There's no reason why you shouldn't be able to send the command string in exact format.
- Is the arduino software something that's supplied by the robotic arm company, and whats the make and model of the arm.
- Give us as much and as many details as you can, the more the better.
Posts: 4
Threads: 1
Joined: Nov 2025
Larz60+ - My apologies for the vagueness of my first post. I didn't have all the information I should have but I do now. Here's the answers to your questions with some extra details.:
What is the exact format that you are looking for on the arduino? - I need to send one string with all 6 desired positions in it at once to match what I have in the Arduino. Per help from their forum, I am looking for this string where each position is a 1-byte number from 0-256. They are separated with a space and there's a newline character at the end like the following:
"pos1 pos2 pos3 pos4 pos5 pos6 \n"
The following code snippet in the Arduino. It parses out the positions as integers. This works well because I can get the positions regardless if they are a 1,2, or 3 digit number which I then write to the individual servos.
while (Serial.available())
{
c = Serial.read();
if (c == '\n')
{
Serial.println(buf);
sscanf(buf, "%d %d %d %d %d %d", &pos1, &pos2, &pos3, &pos4, &pos5, &pos6);
else if (pos < sizeof(buf) - 1)
{
buf[pos++] = c;
}
What do the various parts of the command stand for? pos1.....pos6 are the angular positions I am commanding the servos to go to. I need to update these at a given rate, probably around 500ms or so, and certain increments. and send them out to the Arduino. I need to do this in order to control the not only the direction but also the rate in which they move.
Looks like a series of single byte commands, as i see no value greater than 256. Is that true? Correct. I'll most likely limit the travel from 0 to 200deg so 1-byte for each is all I need.
There's no reason why you shouldn't be able to send the command string in exact format. I can send out a hard-coded string with the desired positions such as the example below:
"23 40 180 6 122 40\n"
The robot servos immediately go to those positions. As noted above, my goal is to continuously update the values in an some sort of incrementing fashion so I can control the speed of the robot as well as direction.
So in the python code, I will also have integers for pos1, po2...pos6 which I can programmatically change. What don't know how to do is once I've updated them, how do I get them assembled into the string with spaces and the newline return added? That's my problem!
Is the arduino software something that's supplied by the robotic arm company, and whats the make and model of the arm. The robot is a 'Ardunio TinkerKit Braccio Robotic Arm". Here's a link: https://www.amazon.com/dp/B08PCB7CJM?ref...asin_title
I'm not using the control shield that came with it but controlling the servos myself using Arduino's Servo library.
I hope that helps. Again, I'm doing this for a robotics class. I need to be able to control the speed and position. We've been using python in the class. Hence I'm using it here. I need the level of control so I can hopefully implement some Forward and Reverse Kinematics and Jacobians, etc.
Thanks!
Posts: 12,137
Threads: 496
Joined: Sep 2016
Nov-11-2025, 09:41 AM
(This post was last modified: Nov-11-2025, 10:11 AM by Larz60+.)
It's morning here, so just getting strarted, haven't had my coffee yet.
Important:
- what is the make and model of the arm?
- would you like the code to manipulate individual bits (or combinations of bits) of each 'pos' of the command string,
or is each byte a single angular position with no other information embedded in the command?
- If just angular position, and each byte is one command, how do you accomidate all 360 degrees?
- There is python software available for robotic arm control for various makes and models, perhaps you could use one of these, for example:
-
{*}pymycobot 4.0.3 - PyPi, pymycobot 4.0.3 GitHub
from pymycobot.mycobot import MyCobot
# Initialize arm
mc = MyCobot("com7", 115200)
mc.send_angles([0, 0, 0, 0, 0, 0], 100)
- This code was developed for ElephantRobotics arm. there are many more available like this,
see:
If you'd like to go that route, functions can be built for sending and receiving data, to make it easy.
Talk to me as if I am your assistant and you're telling me what you want me to write.
Posts: 4
Threads: 1
Joined: Nov 2025
Lars60+, All,
First of all, to answer the question of what robot I got, i'm using the Braccio Tinker Kit Robot from Arduino. https://store-usa.arduino.cc/products/ti...yW3dOawDUA
I just logged on so I haven't looked at your links yet. [ b]However, using Gemini, I was able to get something working.[/b]
Below is my code. This basically shows you what I'm trying to do. You'll see that I define initial angles for pos1-pos6 which correlate to the 6 servos on the arm. Then, in the for loop, I increment them by 1deg. This is currently a test setup just to get the communication working well.
The end goal is to be able to pick a point, figure out what angles the servos have to go to and the delta for each, and then slowly increment them similar to what I go here to their new positions.
With Gemini, it defined the code to put these values into an array, add spaces between them, add the '\n' to the end, and then write them out to the serial port. This is how I needed the data to be sent so my Arduino program could parse the positions out and then command the servos to do to them.
This is working although I am getting random numbers for pos6 sometimes. Figure that out in a bit. You'll see the bottom of the loop, I delay for 40ms between each loop. This is so I can try to slow things down. Can you help me in the following ways:
1. Right now I'm focused on getting robustness in the communication to the Arduino and get the robot moving slowly. Is what I go the best way to do this in terms of writing these position to the serial port?
2. Even though I'm moving 1deg per loop, the robot is a bit 'jerky'. In order to try and make it move smoother, instead of sending over the positions as angles, I'd like to send over the pulse widths for the servos. This would mean instead of sending a single byte angle, I'd send a 2-byte pulse width in usec ranging from 1000 to 2000. What would I have to change in order to do that?
Again, any help is appreciated!
#pip install pyserial
import serial
import time
ser = serial.Serial(
port='COM7',
baudrate=115200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1 # Set a read timeout (in seconds).
)
try:
# Open the serial port if it's not already open
if not ser.is_open:
ser.open()
print(f"Serial port {ser.port} opened successfully.")
#Random initial angles for the 6 servos
pos1 = 90
pos2 = 130
pos3 = 90
pos4 = 90
pos5 = 40
pos6 = 90
#Loop 10 times. Increase positions 5deg each loop as a test
for i in range(80):
#Put Positions in array
int_array = [pos1, pos2, pos3, pos4, pos5, pos6]
#Update positions
#pos1 += 1
pos2 -= 1
pos3 += 1
pos4 += 1
pos5 += 1
#pos6 += 1
# Convert integers to strings and join them with a space
string_data = ' '.join(map(str, int_array))
# Add a newline character to the end of the string data
string_data += '\n'
# Encode the string to bytes (e.g., using UTF-8 encoding)
byte_data_with_spaces = string_data.encode('utf-8')
#Print everything here in Python Console to See what it Looks like
print(f"Original integers: {int_array}")
print(f"String with spaces and newline: '{string_data.replace('\n', '\\n')}'")
print(f"Encoded byte data with spaces and newline (hex): {byte_data_with_spaces.hex()}")
# Send the byte data over the serial port
ser.write(byte_data_with_spaces)
print(f"Sent {len(byte_data_with_spaces)} bytes to {ser.name}")
#Small time delay between messages
time.sleep(0.04)
except serial.SerialException as e:
print(f"Serial port error: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
finally:
# Close the serial port
if ser.is_open:
ser.close()
print("Serial port closed.")
Posts: 12,137
Threads: 496
Joined: Sep 2016
The following is response from xGrok: https://x.com/i/grok
It will give you a complete interface that will allow you to modify any of the 6 pos values using a GUI interface (tkinter).
this is the query as sent:
Output: This is to answer a python-forum.io users thread
Thread URL: https://python-forum.io/thread-45781.htmlspecifically
Post URL: https://python-forum.io/thread-45781-post-188702.html#pid188702
This user would like python code to interface with the Arduino interface already connected to
device: Braccio Tinker Kit Robot from Arduino URL: https://store-usa.arduino.cc/products/tinkerkit-braccio-robot?srsltid=AfmBOorAYsJYxsphgrSrpaqB537GBKP_2kdXKP-6iFh8dDyW3dOawDUA
Op is looking for improvement of his code:
[python]
#pip install pyserial
import serial
import time
ser = serial.Serial(
port='COM7',
baudrate=115200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1 # Set a read timeout (in seconds).
)
try:
# Open the serial port if it's not already open
if not ser.is_open:
ser.open()
print(f"Serial port {ser.port} opened successfully.")
#Random initial angles for the 6 servos
pos1 = 90
pos2 = 130
pos3 = 90
pos4 = 90
pos5 = 40
pos6 = 90
#Loop 10 times. Increase positions 5deg each loop as a test
for i in range(80):
#Put Positions in array
int_array = [pos1, pos2, pos3, pos4, pos5, pos6]
#Update positions
#pos1 += 1
pos2 -= 1
pos3 += 1
pos4 += 1
pos5 += 1
#pos6 += 1
# Convert integers to strings and join them with a space
string_data = ' '.join(map(str, int_array))
# Add a newline character to the end of the string data
string_data += '\n'
# Encode the string to bytes (e.g., using UTF-8 encoding)
byte_data_with_spaces = string_data.encode('utf-8')
#Print everything here in Python Console to See what it Looks like
print(f"Original integers: {int_array}")
print(f"String with spaces and newline: '{string_data.replace('\n', '\\n')}'")
print(f"Encoded byte data with spaces and newline (hex): {byte_data_with_spaces.hex()}")
# Send the byte data over the serial port
ser.write(byte_data_with_spaces)
print(f"Sent {len(byte_data_with_spaces)} bytes to {ser.name}")
#Small time delay between messages
time.sleep(0.04)
except serial.SerialException as e:
print(f"Serial port error: {e}")
except Exception as e:
print(f"An unexpected error occurred: {e}")
finally:
# Close the serial port
if ser.is_open:
ser.close()
print("Serial port closed.")
[/python]
Perhaps with a tkinter front end where values for pos1 to pos6 could be toggled.
Here's Grok's response:
Improved Python Code for Controlling the Braccio Robot Arm
Hi! I saw your thread on python-forum.io and your working script—great job getting the basics down with PySerial. It looks solid for sending space-separated angles, but I get the issues: jerky motion from small increments at high frequency, potential data corruption (e.g., on pos6), and the desire for smoother control via pulse widths (1000-2000 µs). Also, a GUI would make testing positions way easier without hardcoding loops.
I'll build on your code with these improvements:
- Tkinter GUI: Sliders for each of the 6 positions (pos1=base, pos2=shoulder, pos3=elbow, pos4=wrist vertical, pos5=wrist rotation, pos6=gripper). Real-time updates when you drag sliders, with a "Send" button for batch sending. Includes a connect/disconnect button for the serial port.
- Smoother Motion: Instead of rapid small changes, the GUI lets you set target positions directly. For animation (e.g., demo mode), it interpolates smoothly over time.
- Pulse Width Support: Added an option to send 2-byte pulse widths (big-endian) instead of 1-byte angles. You'll need to update your Arduino sketch to parse 12 bytes (6 × 2-byte uint16) and map to Servo.writeMicroseconds(). I included a simple angle-to-pulse mapper (assuming standard 0°=1000µs, 180°=2000µs).
- Robustness: Added read-back from Arduino (expects a simple "OK\n" echo), error handling, and a slower default update rate (200ms) to reduce jerkiness and corruption. Clamped positions to 0-180.
- Demo Mode: A button to run a smooth waving animation using interpolation.
Assumptions:
- Your Arduino sketch parses either "angle1 angle2 ... angle6\n" (your current format) or raw 12 bytes for pulses (e.g., via Serial.readBytes()).
- Port is 'COM7'—change in the GUI if needed.
- Baudrate 115200 as in your code.
- For pulse mode, update Arduino like this (pseudocode):
[python=C++ code]
if (Serial.available() >= 12) {
uint16_t pw[6];
for(int i=0; i<6; i++) {
pw[i] = (Serial.read() << 8) | Serial.read(); // Big-endian
}
servo1.writeMicroseconds(pw[0]);
// ... for others
Serial.println("OK");
}
[/python]
Install deps if needed: pip install pyserial (you have it).
The Full Script: braccio_control_gui.pypython
import serial
import time
import tkinter as tk
from tkinter import ttk, messagebox
import threading
import struct # For packing pulse widths
class BraccioController:
def __init__(self, root):
self.root = root
self.root.title("Braccio Robot Arm Controller")
self.root.geometry("400x500")
self.ser = None
self.connected = False
self.send_angles = tk.BooleanVar(value=True) # True for angles, False for pulses
self.positions = [90, 130, 90, 90, 40, 90] # Initial: base, shoulder, elbow, wrist_v, wrist_r, gripper
self.demo_running = False
self.setup_gui()
def setup_gui(self):
# Connection frame
conn_frame = ttk.Frame(self.root, padding="10")
conn_frame.pack(fill=tk.X)
self.connect_btn = ttk.Button(conn_frame, text="Connect", command=self.toggle_connection)
self.connect_btn.pack(side=tk.LEFT)
ttk.Checkbutton(conn_frame, text="Send Pulse Widths (µs)", variable=self.send_angles,
command=self.update_positions).pack(side=tk.RIGHT)
# Sliders frame
sliders_frame = ttk.LabelFrame(self.root, text="Servo Positions (0-180°)", padding="10")
sliders_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.sliders = []
labels = ["Base", "Shoulder", "Elbow", "Wrist Vert", "Wrist Rot", "Gripper"]
for i, (label, init_pos) in enumerate(zip(labels, self.positions)):
ttk.Label(sliders_frame, text=f"{label}:").pack(anchor=tk.W)
slider = tk.Scale(sliders_frame, from_=0, to=180, orient=tk.HORIZONTAL,
command=lambda val, idx=i: self.on_slider_change(idx, int(float(val))),
length=300)
slider.set(init_pos)
slider.pack(fill=tk.X, pady=2)
self.sliders.append(slider)
# Buttons
btn_frame = ttk.Frame(self.root, padding="10")
btn_frame.pack(fill=tk.X)
ttk.Button(btn_frame, text="Send Positions", command=self.send_positions).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Home (90° All)", command=self.home_all).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Demo Wave", command=self.start_demo).pack(side=tk.LEFT, padx=5)
ttk.Button(btn_frame, text="Stop Demo", command=self.stop_demo).pack(side=tk.LEFT, padx=5)
# Status
self.status = ttk.Label(self.root, text="Disconnected", foreground="red")
self.status.pack(pady=5)
def toggle_connection(self):
if not self.connected:
try:
self.ser = serial.Serial(
port='COM7', # Change if needed
baudrate=115200,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
bytesize=serial.EIGHTBITS,
timeout=1
)
self.ser.open()
time.sleep(2) # Arduino reset delay
self.connected = True
self.connect_btn.config(text="Disconnect")
self.status.config(text="Connected", foreground="green")
messagebox.showinfo("Success", "Connected to Arduino!")
except serial.SerialException as e:
messagebox.showerror("Error", f"Serial error: {e}")
else:
if self.ser and self.ser.is_open:
self.ser.close()
self.connected = False
self.connect_btn.config(text="Connect")
self.status.config(text="Disconnected", foreground="red")
def on_slider_change(self, idx, value):
self.positions[idx] = value
# Optionally auto-send on change (uncomment below)
# if self.connected:
# self.send_positions()
def update_positions(self):
# Refresh sliders if mode changed (unlikely, but safe)
for i, slider in enumerate(self.sliders):
slider.set(self.positions[i])
def send_positions(self, positions=None):
if not self.connected:
messagebox.showwarning("Warning", "Connect to Arduino first!")
return
if positions is None:
positions = [int(sl.get()) for sl in self.sliders]
try:
if self.send_angles.get(): # Send as space-separated angles
string_data = ' '.join(map(str, positions)) + '\n'
byte_data = string_data.encode('utf-8')
self.ser.write(byte_data)
print(f"Sent angles: {positions}")
else: # Send as 2-byte pulse widths (1000-2000 µs)
pulses = [self.angle_to_pulse(p) for p in positions]
byte_data = struct.pack('>HHHHHH', *pulses) # Big-endian uint16
self.ser.write(byte_data)
print(f"Sent pulses: {pulses}")
# Read echo for confirmation
response = self.ser.readline().decode('utf-8').strip()
if response != "OK":
print(f"Warning: Expected 'OK', got '{response}'")
time.sleep(0.2) # Slower rate for smoothness
except Exception as e:
messagebox.showerror("Error", f"Send failed: {e}")
def angle_to_pulse(self, angle):
# Map 0-180° to 1000-2000 µs (adjust if your servos differ)
return 1000 + (angle * 1000 // 180)
def home_all(self):
for i in range(6):
self.positions[i] = 90
self.sliders[i].set(90)
self.send_positions()
def start_demo(self):
if self.demo_running:
return
self.demo_running = True
threading.Thread(target=self.run_demo, daemon=True).start()
def stop_demo(self):
self.demo_running = False
def run_demo(self):
steps = 50 # Smooth steps per move
wave_poses = [
[90, 130, 90, 90, 40, 90], # Start
[90, 90, 120, 60, 40, 90], # Wave up
[90, 130, 90, 90, 40, 90], # Back
[90, 90, 60, 120, 40, 90], # Wave down
]
while self.demo_running:
for target in wave_poses:
for _ in range(steps):
if not self.demo_running:
return
current = self.positions[:]
for i in range(6):
current[i] += (target[i] - current[i]) / (steps - _)
self.send_positions([int(round(p)) for p in current])
time.sleep(0.05) # Fast interpolation for smoothness
time.sleep(0.5) # Pause at pose
def on_closing(self):
self.stop_demo()
if self.connected:
self.toggle_connection()
self.root.destroy()
if __name__ == "__main__":
root = tk.Tk()
app = BraccioController(root)
root.protocol("WM_DELETE_WINDOW", app.on_closing)
root.mainloop()How to Use It:
- Run python braccio_control_gui.py.
- Click Connect—it'll open COM7 (edit the port in toggle_connection if yours differs).
- Drag sliders to set positions (0-180°). Click Send Positions to transmit.
- Check Send Pulse Widths for µs mode (smoother if your Arduino supports it).
- Home resets to 90° all.
- Demo Wave runs a smooth interpolation loop—watch for jerk-free motion!
- Close the window to disconnect safely.
Why This Improves Your Code:
- GUI: No more console-only tweaks; visual sliders make iteration fast.
- Smoothness: Demo uses linear interpolation (easy forward kinematics start). Slower sends (200ms) + read-back reduce errors/jerk.
- Extensibility: Easy to add Jacobians (e.g., compute targets from end-effector coords). For full IK, check libraries like pybullet or ikpy.
- Debugging: Prints sent data; echoes confirm receipt (add Serial.println("OK"); to Arduino after moves).
If pos6 still glitches, buffer overflow? Add Serial.flush() on Arduino. For pulse mode tweaks or kinematics help, reply—happy to iterate. Credit to your base script for the solid serial setup!
—Grok (built by xAI)
Posts: 12,137
Threads: 496
Joined: Sep 2016
Additional from Grok:
Braccio TinkerKit – Full GUI + Pulse-Width support
Thanks to Grok (built by xAI) for the polished solution!
Hi everyone,
Below is a **complete, ready-to-run** Python GUI plus the **minimal Arduino changes** you need for the optional pulse-width mode.
---
1. Python – braccio_control_gui.py
import serial
import time
import tkinter as tk
from tkinter import ttk, messagebox
import threading
import struct
class BraccioController:
def __init__(self, root):
self.root = root
self.root.title("Braccio Robot Arm Controller")
self.root.geometry("400x500")
self.ser = None
self.connected = False
self.send_angles = tk.BooleanVar(value=True) # True = angles, False = microseconds
self.positions = [90, 130, 90, 90, 40, 90] # start pose
self.demo_running = False
self.setup_gui()
# ------------------------------------------------------------------ GUI
def setup_gui(self):
# ----- connection ------------------------------------------------
conn = ttk.Frame(self.root, padding=10); conn.pack(fill=tk.X)
self.connect_btn = ttk.Button(conn, text="Connect", command=self.toggle_connection)
self.connect_btn.pack(side=tk.LEFT)
ttk.Checkbutton(conn, text="Send Pulse Widths (microseconds)", variable=self.send_angles,
command=self.update_positions).pack(side=tk.RIGHT)
# ----- sliders ---------------------------------------------------
slf = ttk.LabelFrame(self.root, text="Servo Positions (0-180 degrees)", padding=10)
slf.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
self.sliders = []
labels = ["Base", "Shoulder", "Elbow", "Wrist Vert", "Wrist Rot", "Gripper"]
for i, (lbl, init) in enumerate(zip(labels, self.positions)):
ttk.Label(slf, text=f"{lbl}:").pack(anchor=tk.W)
s = tk.Scale(slf, from_=0, to=180, orient=tk.HORIZONTAL,
command=lambda v, idx=i: self.on_slider_change(idx, int(float(v))),
length=300)
s.set(init); s.pack(fill=tk.X, pady=2)
self.sliders.append(s)
# ----- buttons ---------------------------------------------------
btn = ttk.Frame(self.root, padding=10); btn.pack(fill=tk.X)
ttk.Button(btn, text="Send Positions", command=self.send_positions).pack(side=tk.LEFT, padx=5)
ttk.Button(btn, text="Home (90 degrees)", command=self.home_all).pack(side=tk.LEFT, padx=5)
ttk.Button(btn, text="Demo Wave", command=self.start_demo).pack(side=tk.LEFT, padx=5)
ttk.Button(btn, text="Stop Demo", command=self.stop_demo).pack(side=tk.LEFT, padx=5)
# ----- status ----------------------------------------------------
self.status = ttk.Label(self.root, text="Disconnected", foreground="red")
self.status.pack(pady=5)
# ------------------------------------------------------------------ serial
def toggle_connection(self):
if not self.connected:
try:
self.ser = serial.Serial('COM7', 115200, timeout=1) # <-- change COM if needed
time.sleep(2) # Arduino reset
self.connected = True
self.connect_btn.config(text="Disconnect")
self.status.config(text="Connected", foreground="green")
messagebox.showinfo("Success", "Connected!")
except Exception as e:
messagebox.showerror("Error", f"Serial error: {e}")
else:
if self.ser and self.ser.is_open: self.ser.close()
self.connected = False
self.connect_btn.config(text="Connect")
self.status.config(text="Disconnected", foreground="red")
# ------------------------------------------------------------------ helpers
def on_slider_change(self, idx, val): self.positions[idx] = val
def update_positions(self):
for i, s in enumerate(self.sliders): s.set(self.positions[i])
def angle_to_pulse(self, a): return 1000 + (a * 1000 // 180) # 0 degrees to 1000 microseconds, 180 degrees to 2000 microseconds
def send_positions(self, pos=None):
if not self.connected:
messagebox.showwarning("Warning", "Connect first!")
return
if pos is None: pos = [int(s.get()) for s in self.sliders]
try:
if self.send_angles.get(): # ---- angles ----
data = ' '.join(map(str, pos)) + '\n'
self.ser.write(data.encode('utf-8'))
print("Sent angles:", pos)
else: # ---- pulses ----
pulses = [self.angle_to_pulse(p) for p in pos]
data = struct.pack('>HHHHHH', *pulses) # big-endian uint16
self.ser.write(data)
print("Sent microseconds:", pulses)
resp = self.ser.readline().decode('utf-8').strip()
if resp != "OK": print(f"Warning: Expected 'OK', got '{resp}'")
time.sleep(0.2) # smooth rate
except Exception as e:
messagebox.showerror("Error", f"Send failed: {e}")
def home_all(self):
for i in range(6): self.positions[i] = 90; self.sliders[i].set(90)
self.send_positions()
# ------------------------------------------------------------------ demo
def start_demo(self):
if self.demo_running: return
self.demo_running = True
threading.Thread(target=self.run_demo, daemon=True).start()
def stop_demo(self): self.demo_running = False
def run_demo(self):
steps = 50
poses = [
[90, 130, 90, 90, 40, 90],
[90, 90, 120, 60, 40, 90],
[90, 130, 90, 90, 40, 90],
[90, 90, 60, 120, 40, 90],
]
while self.demo_running:
for target in poses:
for step in range(steps):
if not self.demo_running: return
cur = [self.positions[i] + (target[i] - self.positions[i]) * (step+1) / steps
for i in range(6)]
self.send_positions([int(round(c)) for c in cur])
time.sleep(0.05)
time.sleep(0.5)
def on_closing(self):
self.stop_demo()
if self.connected: self.toggle_connection()
self.root.destroy()
# ---------------------------------------------------------------------- run
if __name__ == "__main__":
root = tk.Tk()
app = BraccioController(root)
root.protocol("WM_DELETE_WINDOW", app.on_closing)
root.mainloop()How to use- pip install pyserial (you already have it)
- Change COM7 in toggle_connection if your port is different
- Run the script to Connect to Move sliders to Send Positions
- Tick “Send Pulse Widths (microseconds)” only after you have updated the Arduino sketch (see below)
- Demo Wave runs a smooth interpolation loop
---
2. Arduino – must add echo + optional pulse parser
#include <Servo.h>
Servo servoBase, servoShoulder, servoElbow, servoWristV, servoWristR, servoGripper;
void setup() {
Serial.begin(115200);
servoBase.attach(3); servoShoulder.attach(5); servoElbow.attach(6);
servoWristV.attach(9); servoWristR.attach(10); servoGripper.attach(11);
// optional: set initial pose
moveTo(90,130,90,90,40,90);
}
void loop() {
// 1. Angle mode (your original format)
if (Serial.available()) {
String line = Serial.readStringUntil('\n');
line.trim();
if (line.length() > 0) {
int angles[6];
int parsed = sscanf(line.c_str(), "%d %d %d %d %d %d",
&angles[0], &angles[1], &angles[2],
&angles[3], &angles[4], &angles[5]);
if (parsed == 6) {
moveTo(angles[0], angles[1], angles[2], angles[3], angles[4], angles[5]);
Serial.println("OK");
}
}
}
// 2. Pulse-width mode (12 raw bytes) – ONLY if you enable the checkbox
if (Serial.available() >= 12) {
uint16_t pw[6];
for (int i = 0; i < 6; ++i) {
pw[i] = (Serial.read() << 8) | Serial.read(); // big-endian
}
servoBase.writeMicroseconds(pw[0]);
servoShoulder.writeMicroseconds(pw[1]);
servoElbow.writeMicroseconds(pw[2]);
servoWristV.writeMicroseconds(pw[3]);
servoWristR.writeMicroseconds(pw[4]);
servoGripper.writeMicroseconds(pw[5]);
Serial.println("OK");
}
}
void moveTo(int b, int s, int e, int wv, int wr, int g) {
servoBase.write(b);
servoShoulder.write(s);
servoElbow.write(e);
servoWristV.write(wv);
servoWristR.write(wr);
servoGripper.write(g);
}Key points- Serial.println("OK"); is **required** for the GUI confirmation
- The **pulse-width block** (if (Serial.available() >= 12)) is **optional** – omit it if you never tick the checkbox
- If you keep only the angle parser, the GUI works exactly like your original script, just with a nice UI
---
TL;DR- Python – copy the script, change COM port if needed, run
- Arduino – add Serial.println("OK"); after every move
- Pulse mode – add the 12-byte parser **or** keep the checkbox **unchecked**
Enjoy the new GUI!
*(Full credit to **Grok** for the code.)*
Posts: 4
Threads: 1
Joined: Nov 2025
(Nov-12-2025, 10:01 AM)Larz60+ Wrote: For pulse mode tweaks or kinematics help, reply—happy to iterate
Hi! Sorry for not responding earlier. I was out all last week from stomach flu!  So I'm slowly trying to catch up. The response with the GUI is incredible. Thanks! It actually didn't load for me. It said the Serial Port was already used even though I had nothing going. In any case, I was able to use your code as a good reference. Now I'm responding per the last line of the post shown above. How do I reply to "happy to iterate"?
I got the Forward Kinematics of the robot but wondering how to do the Inverse Kinematics which is A LOT HARDER! Just wanted to ask.
Thanks Again!
Posts: 12,137
Threads: 496
Joined: Sep 2016
I didn't notice that before, not sure what that's about, If you ask Grok, and pass the URL of that post, it will know what your talking about, and you can ask directly. You should be able to use the open URL at https://grok.com/c to connect.
for the query in question, use: https://python-forum.io/thread-45781-pos...#pid188705
|