Project Initialization
First Upload to Gitea
This commit is contained in:
+12
@@ -0,0 +1,12 @@
|
||||
# Ignore dependency folders
|
||||
node_modules/
|
||||
.venv/
|
||||
.vs/
|
||||
__pycache__/
|
||||
|
||||
# Ignore environment files with private passwords/keys
|
||||
.env
|
||||
|
||||
# Ignore OS junk
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
@@ -0,0 +1,74 @@
|
||||
from threading import Thread, Event
|
||||
from queue import Queue
|
||||
import numpy as np
|
||||
import serial
|
||||
import time
|
||||
|
||||
import config as cfg
|
||||
import DataTypes as dt
|
||||
|
||||
class ArduinoCommunication(Thread):
|
||||
def __init__(self, port=cfg.port, baudrate=cfg.baudrate, timeout=cfg.timeout):
|
||||
super().__init__()
|
||||
self.daemon = True # Thread schließt sich beim Programmende
|
||||
self.serial_conn = serial.Serial(port, baudrate, timeout=timeout)
|
||||
time.sleep(2) # Warten bis Arduino ready
|
||||
|
||||
self.command_queue = Queue()
|
||||
self.response_queue = Queue()
|
||||
self.running = Event()
|
||||
self.running.set()
|
||||
|
||||
def run(self):
|
||||
while self.running.is_set():
|
||||
# 1. Befehle senden
|
||||
if not self.command_queue.empty():
|
||||
command = self.command_queue.get()
|
||||
self._write(command)
|
||||
|
||||
# 2. Antwort lesen (falls vorhanden)
|
||||
if self.serial_conn.in_waiting > 0:
|
||||
line = self.serial_conn.readline().decode('utf-8', errors='ignore').strip()
|
||||
if line:
|
||||
print(f"[Arduino] {line}")
|
||||
self.response_queue.put(line)
|
||||
|
||||
time.sleep(0.01)
|
||||
|
||||
# Werte auf Arduino schreiben
|
||||
def write(self, target):
|
||||
if isinstance(target, dt.RadArray):
|
||||
target = dt.DegArray(np.degrees(target.data))
|
||||
if self.serial_conn and self.serial_conn.is_open:
|
||||
leg_str = '\n'.join([','.join(map(str, row)) for row in target])
|
||||
formatted_data = f"<{leg_str}>"
|
||||
print(f"Gesendete Daten:\n{formatted_data}")
|
||||
self.serial_conn.write(bytes(formatted_data, 'utf-8'))
|
||||
|
||||
# Werte von Arduino ablesen und Synchronisierunga
|
||||
def wait4arduino(self, expected_message):
|
||||
while True:
|
||||
# Lese eine Zeile von der seriellen Verbindung
|
||||
data = self.serial_conn .readline()
|
||||
|
||||
# Konvertiere die Zeile in einen lesbaren String
|
||||
data = str(data, "utf-8").strip("\r\n")
|
||||
|
||||
# Debugging-Ausgabe (optional)
|
||||
print(f"Empfangene Daten: {data}")
|
||||
|
||||
# Ueberpruefe, ob die empfangene Nachricht der erwarteten Nachricht entspricht
|
||||
if data == expected_message:
|
||||
print("Bewegung abgeschlossen!")
|
||||
break
|
||||
else:
|
||||
time.sleep(0.05)
|
||||
|
||||
def _write(self, message):
|
||||
if self.serial_conn and self.serial_conn.is_open:
|
||||
self.serial_conn.write(message.encode('utf-8'))
|
||||
|
||||
def stop(self):
|
||||
self.running.clear()
|
||||
self.join()
|
||||
self.serial_conn.close()
|
||||
@@ -0,0 +1,93 @@
|
||||
import pygame
|
||||
import math
|
||||
import time
|
||||
|
||||
import DataTypes as dt
|
||||
|
||||
def normalize(x, y, deadzone=0.15):
|
||||
if abs(x) < deadzone:
|
||||
x = 0.0
|
||||
if abs(y) < deadzone:
|
||||
y = 0.0
|
||||
|
||||
mag = math.hypot(x, y)
|
||||
if mag > 1.0:
|
||||
x /= mag
|
||||
y /= mag
|
||||
|
||||
return x, y
|
||||
|
||||
|
||||
def controller_loop(shared_intent: dt.ControlIntent, lock):
|
||||
pygame.init()
|
||||
pygame.joystick.init()
|
||||
|
||||
joysticks = {}
|
||||
prev_buttons = {}
|
||||
|
||||
clock = pygame.time.Clock()
|
||||
|
||||
while True:
|
||||
# 🔑 REQUIRED: keeps joystick state updating
|
||||
pygame.event.pump()
|
||||
|
||||
for event in pygame.event.get():
|
||||
if event.type == pygame.JOYDEVICEADDED:
|
||||
joy = pygame.joystick.Joystick(event.device_index)
|
||||
joysticks[joy.get_instance_id()] = joy
|
||||
prev_buttons[joy.get_instance_id()] = [0] * joy.get_numbuttons()
|
||||
print(f"[Controller] Joystick connected: {joy.get_name()}")
|
||||
|
||||
elif event.type == pygame.JOYDEVICEREMOVED:
|
||||
joysticks.pop(event.instance_id, None)
|
||||
prev_buttons.pop(event.instance_id, None)
|
||||
print("[Controller] Joystick disconnected")
|
||||
|
||||
if not joysticks:
|
||||
time.sleep(0.1)
|
||||
continue
|
||||
|
||||
# Use first joystick
|
||||
joy = next(iter(joysticks.values()))
|
||||
jid = joy.get_instance_id()
|
||||
|
||||
# ---- AXES ----
|
||||
forward = -joy.get_axis(1) # usually inverted
|
||||
sideways = joy.get_axis(0)
|
||||
|
||||
forward, sideways = normalize(forward, sideways)
|
||||
|
||||
walk = abs(forward) > 0 or abs(sideways) > 0
|
||||
|
||||
# ---- BUTTONS ----
|
||||
buttons = joy.get_numbuttons()
|
||||
current_buttons = [joy.get_button(i) for i in range(buttons)]
|
||||
|
||||
emote = None
|
||||
quit_flag = False
|
||||
|
||||
for i in range(buttons):
|
||||
if current_buttons[i] and not prev_buttons[jid][i]:
|
||||
# Button DOWN (edge)
|
||||
if i == 0:
|
||||
emote = "HELLO"
|
||||
elif i == 1:
|
||||
emote = "SAD"
|
||||
elif i == 9:
|
||||
quit_flag = True
|
||||
|
||||
prev_buttons[jid] = current_buttons
|
||||
|
||||
# ---- UPDATE INTENT (atomic) ----
|
||||
with lock:
|
||||
shared_intent.move_x = forward
|
||||
shared_intent.move_y = sideways
|
||||
shared_intent.walk = walk
|
||||
|
||||
if emote:
|
||||
shared_intent.emote = emote
|
||||
|
||||
if quit_flag:
|
||||
shared_intent.quit = True
|
||||
|
||||
clock.tick(60)
|
||||
@@ -0,0 +1,71 @@
|
||||
from dataclasses import dataclass
|
||||
import numpy as np
|
||||
from pygame import Vector2
|
||||
|
||||
def checkDimensions(self, row:int, column:int):
|
||||
if self.data.shape != (row, column):
|
||||
raise ValueError(f"{type(self).__name__} must be of shape ({row}, {column}), got {self.data.shape}")
|
||||
|
||||
# Dataclasses
|
||||
@dataclass
|
||||
class PosArray:
|
||||
data: np.ndarray
|
||||
def __post_init__(self):
|
||||
if not isinstance(self.data, np.ndarray):
|
||||
self.data = np.array(self.data, dtype=float)
|
||||
checkDimensions(self, 6, 3)
|
||||
# behave like numpy
|
||||
def __array__(self): return self.data
|
||||
def __getitem__(self, key): return self.data[key]
|
||||
def __iter__(self): return iter(self.data)
|
||||
def __repr__(self): return f"PosArray(\n{self.data}\n)"
|
||||
|
||||
|
||||
@dataclass
|
||||
class DegArray:
|
||||
data: np.ndarray
|
||||
def __post_init__(self):
|
||||
if not isinstance(self.data, np.ndarray):
|
||||
self.data = np.array(self.data)
|
||||
checkDimensions(self, 6, 3)
|
||||
self.data = self.data.astype(int)
|
||||
|
||||
def to_rad(self) -> "RadArray":
|
||||
return RadArray(np.radians(self.data))
|
||||
# behave like numpy
|
||||
def __array__(self): return self.data
|
||||
def __getitem__(self, key): return self.data[key]
|
||||
def __iter__(self): return iter(self.data)
|
||||
def __repr__(self): return f"PosArray(\n{self.data}\n)"
|
||||
|
||||
@dataclass
|
||||
class RadArray:
|
||||
data: np.ndarray
|
||||
def __post_init__(self):
|
||||
if not isinstance(self.data, np.ndarray):
|
||||
self.data = np.array(self.data, dtype=float)
|
||||
checkDimensions(self, 6, 3)
|
||||
|
||||
def to_deg(self) -> "DegArray":
|
||||
return DegArray(np.degrees(self.data))
|
||||
# behave like numpy
|
||||
def __array__(self): return self.data
|
||||
def __getitem__(self, key): return self.data[key]
|
||||
def __iter__(self): return iter(self.data)
|
||||
def __repr__(self): return f"PosArray(\n{self.data}\n)"
|
||||
|
||||
@dataclass
|
||||
class RobotCommand:
|
||||
legs: DegArray
|
||||
state: int = 0
|
||||
text: str = ""
|
||||
|
||||
@dataclass
|
||||
class ControlIntent:
|
||||
move_x: float = 0.0
|
||||
move_y: float = 0.0
|
||||
turn: float = 0.0
|
||||
emote: str | None = None
|
||||
walk: bool = False
|
||||
quit: bool = False
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
[InternetShortcut]
|
||||
URL=https://www.thingiverse.com/thing:6345316
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 229 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 105 KiB |
Binary file not shown.
@@ -0,0 +1,80 @@
|
||||
import socket
|
||||
import struct
|
||||
|
||||
PACKET_VERSION = 1
|
||||
FLOAT_COUNT = 30 * 6 * 3
|
||||
|
||||
PACKET_FMT = f"<H I B B {FLOAT_COUNT}f"
|
||||
PACKET_SIZE = struct.calcsize(PACKET_FMT)
|
||||
|
||||
import time
|
||||
import numpy as np
|
||||
from threading import Thread, Event
|
||||
from queue import Queue
|
||||
|
||||
import config as cfg
|
||||
import DataTypes as dt
|
||||
|
||||
|
||||
class ESP32Communication(Thread):
|
||||
def __init__(self, host=cfg.esp32_ip, port=cfg.esp32_port, timeout=cfg.timeout):
|
||||
super().__init__()
|
||||
self.daemon = True
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.settimeout(timeout)
|
||||
|
||||
print(f"Connecting to ESP32 at {host}:{port} ...")
|
||||
self.sock.connect((host, port))
|
||||
print("Connected to ESP32!")
|
||||
|
||||
self.command_queue = Queue()
|
||||
self.response_queue = Queue()
|
||||
self.running = Event()
|
||||
self.running.set()
|
||||
|
||||
def run(self):
|
||||
while self.running.is_set():
|
||||
# 1. Send queued commands
|
||||
if not self.command_queue.empty():
|
||||
command = self.command_queue.get()
|
||||
self._write(command)
|
||||
|
||||
# 2. Read response
|
||||
self.sock.setblocking(False)
|
||||
try:
|
||||
data = self.sock.recv(1024)
|
||||
if data:
|
||||
line = data.decode('utf-8', errors='ignore').strip()
|
||||
print(f"[ESP32] {line}")
|
||||
self.response_queue.put(line)
|
||||
except BlockingIOError:
|
||||
pass
|
||||
|
||||
time.sleep(0.01)
|
||||
|
||||
def write(self, target):
|
||||
if isinstance(target, dt.RadArray):
|
||||
target = dt.DegArray(np.degrees(target.data))
|
||||
leg_str = '\n'.join([','.join(map(str, row)) for row in target])
|
||||
formatted_data = f"<{leg_str}>"
|
||||
print(f"Gesendete Daten:\n{formatted_data}")
|
||||
self._write(formatted_data)
|
||||
|
||||
def wait4esp32(self, expected_message):
|
||||
while True:
|
||||
try:
|
||||
data = self.sock.recv(1024).decode('utf-8').strip()
|
||||
print(f"Empfangene Daten: {data}")
|
||||
if data == expected_message:
|
||||
print("Bewegung abgeschlossen!")
|
||||
break
|
||||
except socket.timeout:
|
||||
time.sleep(0.05)
|
||||
|
||||
def _write(self, message):
|
||||
self.sock.sendall(message.encode('utf-8'))
|
||||
|
||||
def stop(self):
|
||||
self.running.clear()
|
||||
self.join()
|
||||
self.sock.close()
|
||||
@@ -0,0 +1,204 @@
|
||||
import socket
|
||||
import struct
|
||||
import time
|
||||
import numpy as np
|
||||
from threading import Thread, Event
|
||||
from queue import Queue
|
||||
|
||||
import config as cfg
|
||||
import DataTypes as dt
|
||||
|
||||
|
||||
# ============================================================
|
||||
# Protocol definitions (SHARED with ESP32)
|
||||
# ============================================================
|
||||
|
||||
PACKET_VERSION = 1
|
||||
|
||||
# Packet IDs
|
||||
PKT_COMMAND = 1
|
||||
PKT_STATUS = 2
|
||||
PKT_DEBUG = 3
|
||||
|
||||
# Field Types
|
||||
FT_ARRAY_F32 = 1 # float32 array
|
||||
FT_STRING = 2 # utf-8 string
|
||||
FT_UINT8 = 3
|
||||
FT_FLOAT32 = 4
|
||||
|
||||
# Header: packet_id | version | payload_len | timestamp_ms
|
||||
HEADER_FMT = "<B B H I"
|
||||
HEADER_SIZE = struct.calcsize(HEADER_FMT)
|
||||
|
||||
# TLV field: type | length | value
|
||||
TLV_FMT = "<B H"
|
||||
TLV_SIZE = struct.calcsize(TLV_FMT)
|
||||
|
||||
|
||||
# ============================================================
|
||||
# ESP32 Communication Thread
|
||||
# ============================================================
|
||||
|
||||
class ESP32Communication(Thread):
|
||||
def __init__(self, host=cfg.esp32_ip, port=cfg.esp32_port, timeout=cfg.timeout):
|
||||
super().__init__(daemon=True)
|
||||
|
||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
self.sock.settimeout(timeout)
|
||||
|
||||
print(f"Connecting to ESP32 at {host}:{port} ...")
|
||||
self.sock.connect((host, port))
|
||||
print("Connected to ESP32!")
|
||||
|
||||
self.tx_queue = Queue()
|
||||
self.rx_queue = Queue()
|
||||
self.running = Event()
|
||||
self.running.set()
|
||||
|
||||
# --------------------------------------------------------
|
||||
# Thread loop
|
||||
# --------------------------------------------------------
|
||||
def run(self):
|
||||
self.sock.setblocking(False)
|
||||
|
||||
while self.running.is_set():
|
||||
self._handle_tx()
|
||||
self._handle_rx()
|
||||
time.sleep(0.002)
|
||||
|
||||
# --------------------------------------------------------
|
||||
# Public API
|
||||
# --------------------------------------------------------
|
||||
def send_command(self, radial_array :dt.RadArray, state=0, name=""):
|
||||
"""
|
||||
Example command packet:
|
||||
- 3x6 float array
|
||||
- uint8 state
|
||||
- optional string
|
||||
"""
|
||||
data = np.degrees(radial_array).astype(np.uint8)
|
||||
print(data)
|
||||
fields = [
|
||||
self._tlv_array(data),
|
||||
self._tlv_uint8(state)
|
||||
]
|
||||
|
||||
if name:
|
||||
fields.append(self._tlv_string(name))
|
||||
|
||||
packet = self._build_packet(PKT_COMMAND, fields)
|
||||
self.tx_queue.put(packet)
|
||||
|
||||
def receive(self):
|
||||
"""
|
||||
Non-blocking receive.
|
||||
Returns (packet_id, timestamp, fields) or None
|
||||
"""
|
||||
try:
|
||||
return self.rx_queue.get_nowait()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def stop(self):
|
||||
self.running.clear()
|
||||
self.join()
|
||||
self.sock.close()
|
||||
|
||||
# --------------------------------------------------------
|
||||
# TX / RX handlers
|
||||
# --------------------------------------------------------
|
||||
def _handle_tx(self):
|
||||
if not self.tx_queue.empty():
|
||||
data = self.tx_queue.get()
|
||||
self.sock.sendall(data)
|
||||
|
||||
def _handle_rx(self):
|
||||
try:
|
||||
header = self._recv_exact(HEADER_SIZE)
|
||||
if not header:
|
||||
return
|
||||
|
||||
packet_id, version, payload_len, timestamp = struct.unpack(HEADER_FMT, header)
|
||||
|
||||
if payload_len == 0:
|
||||
# Nothing to parse
|
||||
self.rx_queue.put((packet_id, timestamp, []))
|
||||
return
|
||||
|
||||
payload = self._recv_exact(payload_len)
|
||||
if payload is None:
|
||||
return # incomplete, skip
|
||||
|
||||
fields = self._parse_tlv(payload)
|
||||
self.rx_queue.put((packet_id, timestamp, fields))
|
||||
|
||||
except BlockingIOError:
|
||||
pass
|
||||
|
||||
# --------------------------------------------------------
|
||||
# Packet construction
|
||||
# --------------------------------------------------------
|
||||
def _build_packet(self, packet_id, fields):
|
||||
payload = b"".join(fields)
|
||||
|
||||
header = struct.pack(
|
||||
HEADER_FMT,
|
||||
packet_id,
|
||||
PACKET_VERSION,
|
||||
len(payload),
|
||||
int(time.time() * 1000) & 0xFFFFFFFF
|
||||
)
|
||||
|
||||
return header + payload
|
||||
|
||||
# --------------------------------------------------------
|
||||
# TLV helpers
|
||||
# --------------------------------------------------------
|
||||
def _tlv(self, ftype, data):
|
||||
return struct.pack(TLV_FMT, ftype, len(data)) + data
|
||||
|
||||
def _tlv_array(self, array):
|
||||
# Convert to uint8 degrees before sending
|
||||
arr_uint8 = array.astype(np.uint8)
|
||||
return self._tlv(FT_ARRAY_F32, arr_uint8.tobytes())
|
||||
|
||||
def _tlv_string(self, text):
|
||||
return self._tlv(FT_STRING, text.encode("utf-8"))
|
||||
|
||||
def _tlv_uint8(self, value):
|
||||
return self._tlv(FT_UINT8, struct.pack("<B", value))
|
||||
|
||||
# --------------------------------------------------------
|
||||
# TLV parsing
|
||||
# --------------------------------------------------------
|
||||
def _parse_tlv(self, payload):
|
||||
pos = 0
|
||||
fields = []
|
||||
|
||||
while pos < len(payload):
|
||||
ftype, flen = struct.unpack(
|
||||
TLV_FMT, payload[pos:pos + TLV_SIZE]
|
||||
)
|
||||
pos += TLV_SIZE
|
||||
|
||||
data = payload[pos:pos + flen]
|
||||
pos += flen
|
||||
|
||||
fields.append((ftype, data))
|
||||
|
||||
return fields
|
||||
|
||||
# --------------------------------------------------------
|
||||
# Socket helper
|
||||
# --------------------------------------------------------
|
||||
def _recv_exact(self, size):
|
||||
data = b""
|
||||
while len(data) < size:
|
||||
try:
|
||||
chunk = self.sock.recv(size - len(data))
|
||||
if not chunk:
|
||||
return None
|
||||
data += chunk
|
||||
except BlockingIOError:
|
||||
return None
|
||||
return data
|
||||
@@ -0,0 +1,78 @@
|
||||
import numpy as np
|
||||
from ArduinoCommunication import ArduinoCommunication
|
||||
from EspCommunication import ESP32Communication
|
||||
from simulation import Simulation
|
||||
import DataTypes as dt
|
||||
import kinematics as kin
|
||||
import config as cfg
|
||||
|
||||
# Globale Variablen
|
||||
robotCommunication = None
|
||||
shared_sim = None
|
||||
if cfg.sim:
|
||||
shared_sim = Simulation()
|
||||
else:
|
||||
if cfg.arduinoConnection:
|
||||
robotCommunication = ArduinoCommunication()
|
||||
else:
|
||||
robotCommunication = ESP32Communication()
|
||||
|
||||
vector_dirmov = [0, 0] # Direction Movement
|
||||
current_rad: dt.RadArray # Current Rad
|
||||
current_pos: dt.PosArray # Current Position
|
||||
|
||||
# current_deg: dt.DegArray # Current Degrees
|
||||
# legarray: dt.DegArray # Working Leg Array
|
||||
# target_pos: dt.PosArray # Target Position
|
||||
|
||||
robot_state = "idle" # Current State
|
||||
# Init for 6 Legs
|
||||
#leg_state = np.array(["step", "drag", "step", "drag", "step", "drag"])
|
||||
# Init for 4 Legs
|
||||
leg_state = np.array(["step", "drag", "drag", "drag", "drag", "drag"])
|
||||
control_pause: bool = False
|
||||
|
||||
# Initilize mit Start Position
|
||||
#init_deg: dt.DegArray = dt.DegArray(
|
||||
# [
|
||||
# [90, 30, 115],
|
||||
# [90, 30, 115],
|
||||
# [90, 30, 115],
|
||||
# [90, 150, 65],
|
||||
# [90, 150, 65],
|
||||
# [90, 150, 65],
|
||||
# ]
|
||||
#)
|
||||
init_deg: dt.DegArray = dt.DegArray(
|
||||
[
|
||||
[90, 30, 95],
|
||||
[90, 30, 95],
|
||||
[90, 30, 95],
|
||||
[90, 150, 85],
|
||||
[90, 150, 85],
|
||||
[90, 150, 85],
|
||||
]
|
||||
)
|
||||
#init_deg: dt.DegArray = dt.DegArray(
|
||||
# [
|
||||
# [90, 45, 140],
|
||||
# [90, 45, 140],
|
||||
# [90, 45, 140],
|
||||
# [90, 135, 40],
|
||||
# [90, 135, 40],
|
||||
# [90, 135, 40],
|
||||
# ]
|
||||
#)
|
||||
|
||||
init90_deg: dt.DegArray = dt.DegArray(
|
||||
[
|
||||
[90, 90, 90],
|
||||
[90, 90, 90],
|
||||
[90, 90, 90],
|
||||
[90, 90, 90],
|
||||
[90, 90, 90],
|
||||
[90, 90, 90],
|
||||
]
|
||||
)
|
||||
|
||||
center_points: dt.PosArray = kin.ikpyForward(init_deg.to_rad())
|
||||
@@ -0,0 +1,5 @@
|
||||
for theta1 in np.linspace(min1, max1, 50):
|
||||
for theta2 in np.linspace(min2, max2, 50):
|
||||
for theta3 in np.linspace(min3, max3, 50):
|
||||
xyz = forward_kinematics([theta1, theta2, theta3])
|
||||
# collect (x,y,z)
|
||||
@@ -0,0 +1,157 @@
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
|
||||
# --- Conversion functions ---
|
||||
def nm_to_kgcm(tau_nm):
|
||||
return tau_nm * 10.197
|
||||
|
||||
# --- Torque calculation function ---
|
||||
def calculate():
|
||||
# Get user inputs
|
||||
mass_total = float(mass_total_var.get())
|
||||
legs_supporting = int(legs_supporting_var.get())
|
||||
g = 9.81
|
||||
coxa_len = float(coxa_len_var.get())
|
||||
femur_len = float(femur_len_var.get())
|
||||
tibia_len = float(tibia_len_var.get())
|
||||
safety_factor = float(safety_factor_var.get())
|
||||
servo_torque_max = float(servo_torque_max_var.get())
|
||||
|
||||
# Derived values
|
||||
mass_per_leg = mass_total / legs_supporting
|
||||
force_per_leg = mass_per_leg * g
|
||||
|
||||
# Torques (Nm)
|
||||
tau_tibia = force_per_leg * tibia_len
|
||||
tau_femur = force_per_leg * (femur_len + tibia_len)
|
||||
tau_coxa = force_per_leg * coxa_len
|
||||
|
||||
# Apply safety factor
|
||||
tau_tibia_req = tau_tibia * safety_factor
|
||||
tau_femur_req = tau_femur * safety_factor
|
||||
tau_coxa_req = tau_coxa * safety_factor
|
||||
|
||||
# Convert to kg·cm
|
||||
coxa_kgcm = nm_to_kgcm(tau_coxa)
|
||||
femur_kgcm = nm_to_kgcm(tau_femur)
|
||||
tibia_kgcm = nm_to_kgcm(tau_tibia)
|
||||
coxa_kgcm_req = nm_to_kgcm(tau_coxa_req)
|
||||
femur_kgcm_req = nm_to_kgcm(tau_femur_req)
|
||||
tibia_kgcm_req = nm_to_kgcm(tau_tibia_req)
|
||||
|
||||
# Update info labels
|
||||
info_label.config(
|
||||
text=(
|
||||
f"Mass per leg: {mass_per_leg:.3f} kg\n"
|
||||
f"Force per leg: {force_per_leg:.2f} N\n"
|
||||
f"Servo max torque: {servo_torque_max:.2f} kg·cm\n"
|
||||
f"Safety Factor: x{safety_factor:.2f}"
|
||||
)
|
||||
)
|
||||
|
||||
# Update progress bars
|
||||
update_bar(coxa_bar, coxa_val, coxa_kgcm, servo_torque_max)
|
||||
update_bar(femur_bar, femur_val, femur_kgcm, servo_torque_max)
|
||||
update_bar(tibia_bar, tibia_val, tibia_kgcm, servo_torque_max)
|
||||
|
||||
update_bar(coxa_bar_req, coxa_val_req, coxa_kgcm_req, servo_torque_max)
|
||||
update_bar(femur_bar_req, femur_val_req, femur_kgcm_req, servo_torque_max)
|
||||
update_bar(tibia_bar_req, tibia_val_req, tibia_kgcm_req, servo_torque_max)
|
||||
|
||||
|
||||
def update_bar(bar, label, value, limit):
|
||||
ratio = min(value / limit, 1.0)
|
||||
percent = (value / limit) * 100
|
||||
bar["value"] = percent if percent <= 100 else 100
|
||||
label.config(text=f"{value:.2f} / {limit:.2f} kg·cm")
|
||||
if value <= limit:
|
||||
bar.configure(style="Green.Horizontal.TProgressbar")
|
||||
else:
|
||||
bar.configure(style="Red.Horizontal.TProgressbar")
|
||||
|
||||
|
||||
# --- Tkinter GUI ---
|
||||
root = tk.Tk()
|
||||
root.title("Hexapod Torque Estimator (Live)")
|
||||
root.geometry("700x700")
|
||||
root.resizable(False, False)
|
||||
|
||||
mainframe = ttk.Frame(root, padding=10)
|
||||
mainframe.pack(fill="both", expand=True)
|
||||
|
||||
# Progress bar styles
|
||||
style = ttk.Style(root)
|
||||
style.theme_use("clam")
|
||||
style.configure("Green.Horizontal.TProgressbar", troughcolor="#333", background="#4CAF50")
|
||||
style.configure("Red.Horizontal.TProgressbar", troughcolor="#333", background="#F44336")
|
||||
|
||||
# Input variables
|
||||
mass_total_var = tk.DoubleVar(value=1.08)
|
||||
legs_supporting_var = tk.IntVar(value=3)
|
||||
coxa_len_var = tk.DoubleVar(value=0.056)
|
||||
femur_len_var = tk.DoubleVar(value=0.072)
|
||||
tibia_len_var = tk.DoubleVar(value=0.07)
|
||||
safety_factor_var = tk.DoubleVar(value=4.5)
|
||||
servo_torque_max_var = tk.DoubleVar(value=2.3)
|
||||
|
||||
# --- Layout ---
|
||||
def add_input(label, variable, from_, to, step=0.01):
|
||||
frame = ttk.Frame(mainframe)
|
||||
frame.pack(fill="x", pady=3)
|
||||
ttk.Label(frame, text=label, width=25).pack(side="left")
|
||||
entry = ttk.Entry(frame, textvariable=variable, width=8)
|
||||
entry.pack(side="left")
|
||||
scale = ttk.Scale(frame, variable=variable, from_=from_, to=to, command=lambda e: calculate())
|
||||
scale.pack(side="left", fill="x", expand=True, padx=10)
|
||||
return entry
|
||||
|
||||
add_input("Total Mass [kg]", mass_total_var, 0.1, 5.0)
|
||||
|
||||
# Integer spinbox for legs supporting
|
||||
frame = ttk.Frame(mainframe)
|
||||
frame.pack(fill="x", pady=3)
|
||||
ttk.Label(frame, text="Legs Supporting", width=25).pack(side="left")
|
||||
spin = ttk.Spinbox(frame, from_=1, to=6, textvariable=legs_supporting_var, width=8, command=calculate)
|
||||
spin.pack(side="left")
|
||||
|
||||
add_input("Coxa Length [m]", coxa_len_var, 0.03, 0.1)
|
||||
add_input("Femur Length [m]", femur_len_var, 0.03, 0.15)
|
||||
add_input("Tibia Length [m]", tibia_len_var, 0.03, 0.15)
|
||||
add_input("Safety Factor", safety_factor_var, 1.0, 3.0)
|
||||
add_input("Servo Torque Max [kg·cm]", servo_torque_max_var, 1.0, 5.0)
|
||||
|
||||
# Info label
|
||||
info_label = ttk.Label(mainframe, text="", justify="left", font=("Courier", 10))
|
||||
info_label.pack(pady=10)
|
||||
|
||||
# Function to create a labeled progress bar
|
||||
def add_progress(label_text):
|
||||
frame = ttk.Frame(mainframe)
|
||||
frame.pack(fill="x", pady=3)
|
||||
ttk.Label(frame, text=label_text, width=10).pack(side="left")
|
||||
bar = ttk.Progressbar(frame, length=400, maximum=100)
|
||||
bar.pack(side="left", padx=5)
|
||||
val_label = ttk.Label(frame, width=20, anchor="w")
|
||||
val_label.pack(side="left")
|
||||
return bar, val_label
|
||||
|
||||
# --- Without Safety Factor ---
|
||||
ttk.Label(mainframe, text="Without Safety Factor", font=("Helvetica", 11, "bold")).pack(pady=(15, 3))
|
||||
coxa_bar, coxa_val = add_progress("Coxa")
|
||||
femur_bar, femur_val = add_progress("Femur")
|
||||
tibia_bar, tibia_val = add_progress("Tibia")
|
||||
|
||||
# --- With Safety Factor ---
|
||||
ttk.Label(mainframe, text="With Safety Factor", font=("Helvetica", 11, "bold")).pack(pady=(15, 3))
|
||||
coxa_bar_req, coxa_val_req = add_progress("Coxa")
|
||||
femur_bar_req, femur_val_req = add_progress("Femur")
|
||||
tibia_bar_req, tibia_val_req = add_progress("Tibia")
|
||||
|
||||
# Auto-update on variable changes
|
||||
for var in [mass_total_var, legs_supporting_var, coxa_len_var, femur_len_var, tibia_len_var, safety_factor_var, servo_torque_max_var]:
|
||||
var.trace_add("write", lambda *args: calculate())
|
||||
|
||||
# Initial calculation
|
||||
calculate()
|
||||
|
||||
root.mainloop()
|
||||
@@ -0,0 +1,672 @@
|
||||
<robot name="JackBot">
|
||||
|
||||
<!-- Base -->
|
||||
<link name="base_link">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<box size="0.160 0.120 0.090"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<material name="red">
|
||||
<color rgba="1 0 0 0.5"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
|
||||
<!-- LEG 1 -->
|
||||
<link name="leg1_coxa">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.068" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.028 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="green">
|
||||
<color rgba="0 1 0 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg1_coxa_joint" type="revolute">
|
||||
<parent link="base_link"/>
|
||||
<child link="leg1_coxa"/>
|
||||
<origin xyz="0.052 0.072 0" rpy="0 0 -0.7854"/>
|
||||
<!-- position from base -->
|
||||
<axis xyz="0 0 1"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg1_femur">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.0602" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 -0.0365" rpy="0 0 0"/>
|
||||
<material name="blue">
|
||||
<color rgba="0 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg1_femur_joint" type="revolute">
|
||||
<parent link="leg1_coxa"/>
|
||||
<child link="leg1_femur"/>
|
||||
<origin xyz="0.068 0 0" rpy="0 0 0"/>
|
||||
<axis xyz="0 -1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg1_tibia">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.07055" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.035 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="yellow">
|
||||
<color rgba="0 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg1_tibia_joint" type="revolute">
|
||||
<parent link="leg1_femur"/>
|
||||
<child link="leg1_tibia"/>
|
||||
<origin xyz="0 0 -0.0602" rpy="0 0 0"/>
|
||||
<axis xyz="0 1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg1_tip">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<sphere radius="0.01"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<material name="purple">
|
||||
<color rgba="1 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg1_tip_joint" type="fixed">
|
||||
<parent link="leg1_tibia"/>
|
||||
<child link="leg1_tip"/>
|
||||
<origin xyz="0.07055 0 0" rpy="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
<!-- LEG 2 -->
|
||||
<link name="leg2_coxa">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.068" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.028 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="green">
|
||||
<color rgba="0 1 0 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg2_coxa_joint" type="revolute">
|
||||
<parent link="base_link"/>
|
||||
<child link="leg2_coxa"/>
|
||||
<origin xyz="0 0.070 0" rpy="0 0 0"/>
|
||||
<!-- position from base -->
|
||||
<axis xyz="0 0 1"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg2_femur">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.0602" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 -0.0365" rpy="0 0 0"/>
|
||||
<material name="blue">
|
||||
<color rgba="0 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg2_femur_joint" type="revolute">
|
||||
<parent link="leg2_coxa"/>
|
||||
<child link="leg2_femur"/>
|
||||
<origin xyz="0.068 0 0" rpy="0 0 0"/>
|
||||
<axis xyz="0 -1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg2_tibia">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.07055" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.035 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="yellow">
|
||||
<color rgba="0 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg2_tibia_joint" type="revolute">
|
||||
<parent link="leg2_femur"/>
|
||||
<child link="leg2_tibia"/>
|
||||
<origin xyz="0 0 -0.0602" rpy="0 0 0"/>
|
||||
<axis xyz="0 1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg2_tip">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<sphere radius="0.01"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<material name="purple">
|
||||
<color rgba="1 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg2_tip_joint" type="fixed">
|
||||
<parent link="leg2_tibia"/>
|
||||
<child link="leg2_tip"/>
|
||||
<origin xyz="0.07055 0 0" rpy="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
<!-- LEG 3 -->
|
||||
<link name="leg3_coxa">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.068" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.028 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="green">
|
||||
<color rgba="0 1 0 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg3_coxa_joint" type="revolute">
|
||||
<parent link="base_link"/>
|
||||
<child link="leg3_coxa"/>
|
||||
<origin xyz="-0.052 0.072 0" rpy="0 0 0.7854"/>
|
||||
<!-- position from base -->
|
||||
<axis xyz="0 0 1"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg3_femur">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.0602" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 -0.0365" rpy="0 0 0"/>
|
||||
<material name="blue">
|
||||
<color rgba="0 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg3_femur_joint" type="revolute">
|
||||
<parent link="leg3_coxa"/>
|
||||
<child link="leg3_femur"/>
|
||||
<origin xyz="0.068 0 0" rpy="0 0 0"/>
|
||||
<axis xyz="0 -1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg3_tibia">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.07055" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.035 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="yellow">
|
||||
<color rgba="0 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg3_tibia_joint" type="revolute">
|
||||
<parent link="leg3_femur"/>
|
||||
<child link="leg3_tibia"/>
|
||||
<origin xyz="0 0 -0.0602" rpy="0 0 0"/>
|
||||
<axis xyz="0 1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg3_tip">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<sphere radius="0.01"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<material name="purple">
|
||||
<color rgba="1 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg3_tip_joint" type="fixed">
|
||||
<parent link="leg3_tibia"/>
|
||||
<child link="leg3_tip"/>
|
||||
<origin xyz="0.07055 0 0" rpy="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
<!-- LEG 4 -->
|
||||
<link name="leg4_coxa">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.068" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.028 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="green">
|
||||
<color rgba="0 1 0 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg4_coxa_joint" type="revolute">
|
||||
<parent link="base_link"/>
|
||||
<child link="leg4_coxa"/>
|
||||
<origin xyz="-0.052 -0.072 0" rpy="0 0 2.3562"/>
|
||||
<!-- position from base -->
|
||||
<axis xyz="0 0 1"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg4_femur">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.0602" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 -0.0365" rpy="0 0 0"/>
|
||||
<material name="blue">
|
||||
<color rgba="0 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg4_femur_joint" type="revolute">
|
||||
<parent link="leg4_coxa"/>
|
||||
<child link="leg4_femur"/>
|
||||
<origin xyz="0.068 0 0" rpy="3.1416 0 0"/>
|
||||
<axis xyz="0 -1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg4_tibia">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.07055" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.035 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="yellow">
|
||||
<color rgba="0 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg4_tibia_joint" type="revolute">
|
||||
<parent link="leg4_femur"/>
|
||||
<child link="leg4_tibia"/>
|
||||
<origin xyz="0 0 -0.0602" rpy="0 0 0"/>
|
||||
<axis xyz="0 1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg4_tip">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<sphere radius="0.01"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<material name="purple">
|
||||
<color rgba="1 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg4_tip_joint" type="fixed">
|
||||
<parent link="leg4_tibia"/>
|
||||
<child link="leg4_tip"/>
|
||||
<origin xyz="0.07055 0 0" rpy="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
<!-- LEG 5 -->
|
||||
<link name="leg5_coxa">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.068" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.028 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="green">
|
||||
<color rgba="0 1 0 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg5_coxa_joint" type="revolute">
|
||||
<parent link="base_link"/>
|
||||
<child link="leg5_coxa"/>
|
||||
<origin xyz="0 -0.070 0" rpy="0 0 3.1416"/>
|
||||
<!-- position from base -->
|
||||
<axis xyz="0 0 1"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg5_femur">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.0602" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 -0.0365" rpy="0 0 0"/>
|
||||
<material name="blue">
|
||||
<color rgba="0 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg5_femur_joint" type="revolute">
|
||||
<parent link="leg5_coxa"/>
|
||||
<child link="leg5_femur"/>
|
||||
<origin xyz="0.068 0 0" rpy="3.1416 0 0"/>
|
||||
<axis xyz="0 -1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg5_tibia">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.07055" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.035 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="yellow">
|
||||
<color rgba="0 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg5_tibia_joint" type="revolute">
|
||||
<parent link="leg5_femur"/>
|
||||
<child link="leg5_tibia"/>
|
||||
<origin xyz="0 0 -0.0602" rpy="0 0 0"/>
|
||||
<axis xyz="0 1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg5_tip">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<sphere radius="0.01"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<material name="purple">
|
||||
<color rgba="1 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg5_tip_joint" type="fixed">
|
||||
<parent link="leg5_tibia"/>
|
||||
<child link="leg5_tip"/>
|
||||
<origin xyz="0.07055 0 0" rpy="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
<!-- LEG 6 -->
|
||||
<link name="leg6_coxa">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.068" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.028 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="green">
|
||||
<color rgba="0 1 0 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg6_coxa_joint" type="revolute">
|
||||
<parent link="base_link"/>
|
||||
<child link="leg6_coxa"/>
|
||||
<origin xyz="0.052 -0.072 0" rpy="0 0 -2.3562"/>
|
||||
<!-- position from base -->
|
||||
<axis xyz="0 0 1"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg6_femur">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.0602" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 -0.0365" rpy="0 0 0"/>
|
||||
<material name="blue">
|
||||
<color rgba="0 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg6_femur_joint" type="revolute">
|
||||
<parent link="leg6_coxa"/>
|
||||
<child link="leg6_femur"/>
|
||||
<origin xyz="0.068 0 0" rpy="3.1416 0 0"/>
|
||||
<axis xyz="0 -1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg6_tibia">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<cylinder length="0.07055" radius="0.005"/>
|
||||
</geometry>
|
||||
<origin xyz="0.035 0 0" rpy="0 1.5708 0"/>
|
||||
<material name="yellow">
|
||||
<color rgba="0 1 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg6_tibia_joint" type="revolute">
|
||||
<parent link="leg6_femur"/>
|
||||
<child link="leg6_tibia"/>
|
||||
<origin xyz="0 0 -0.0602" rpy="0 0 0"/>
|
||||
<axis xyz="0 1 0"/>
|
||||
<limit lower="0.2618" upper="2.8798" effort="1" velocity="1"/>
|
||||
</joint>
|
||||
|
||||
<link name="leg6_tip">
|
||||
<inertial>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<mass value="1"/>
|
||||
<inertia
|
||||
ixx="0.001" ixy="0" ixz="0"
|
||||
iyy="0.001" iyz="0"
|
||||
izz="0.001"/>
|
||||
</inertial>
|
||||
<visual>
|
||||
<geometry>
|
||||
<sphere radius="0.01"/>
|
||||
</geometry>
|
||||
<origin xyz="0 0 0" rpy="0 0 0"/>
|
||||
<material name="purple">
|
||||
<color rgba="1 0 1 1"/>
|
||||
</material>
|
||||
</visual>
|
||||
</link>
|
||||
<joint name="leg6_tip_joint" type="fixed">
|
||||
<parent link="leg6_tibia"/>
|
||||
<child link="leg6_tip"/>
|
||||
<origin xyz="0.07055 0 0" rpy="0 0 0"/>
|
||||
</joint>
|
||||
|
||||
</robot>
|
||||
@@ -0,0 +1,50 @@
|
||||
|
||||
# JackBot — Hexapod Control & Simulation
|
||||
|
||||
**Purpose**
|
||||
- **Overview**: JackBot is a Python project for controlling and simulating a six-legged (hexapod) robot. It computes foot trajectories, runs inverse kinematics against a URDF model, and sends joint commands to either a hardware controller (ESP32 / Arduino) or a PyBullet simulation.
|
||||
|
||||
**Quick Start**
|
||||
- **Simulation**: enable the simulator in `config.py` by setting `sim = True`, then run:
|
||||
|
||||
```bash
|
||||
python3 main.py
|
||||
```
|
||||
- **Hardware**: set `sim = False` in `config.py` and choose `arduinoConnection = True` or provide your ESP32 settings. Verify `port`, `baudrate`, `esp32_ip`, and `esp32_port` in `config.py`.
|
||||
|
||||
**Dependencies**
|
||||
- **Python packages**: At minimum the project uses `numpy`, `pygame`, `ikpy`, `pybullet`, `pyserial`, and `matplotlib`.
|
||||
- **Install** (recommended inside a venv):
|
||||
|
||||
```bash
|
||||
python3 -m venv .venv
|
||||
source .venv/bin/activate
|
||||
pip install numpy pygame ikpy pybullet pyserial matplotlib
|
||||
```
|
||||
|
||||
**Project structure (key files)**
|
||||
- **Entry point**: [main.py](main.py) — starts the controller and state loop.
|
||||
- **Controller**: [Controller.py](Controller.py) — joystick input and `ControlIntent` updates.
|
||||
- **Kinematics**: [kinematics.py](kinematics.py) — URDF-based forward/inverse kinematics using IKPy.
|
||||
- **Simulation**: [simulation.py](simulation.py) — PyBullet visualization and joint control.
|
||||
- **Hardware comms**: [EspCommunication.py](EspCommunication.py) and [ArduinoCommunication.py](ArduinoCommunication.py) — send commands to ESP32 or Arduino.
|
||||
- **Runtime globals**: [GlobalVariables.py](GlobalVariables.py) — central runtime objects and flags.
|
||||
- **Types**: [DataTypes.py](DataTypes.py) — `PosArray`, `DegArray`, `RadArray`, `ControlIntent`.
|
||||
- **States**: [RobotState/idle.py](RobotState/idle.py) and [RobotState/walking.py](RobotState/walking.py) — state machine implementations.
|
||||
- **Config**: [config.py](config.py) — ports, flags (`sim`, `arduinoConnection`), URDF path, timing values.
|
||||
|
||||
**How it works**
|
||||
- `main.py` creates a `ControlIntent` and `RobotContext`, starts the controller thread, and runs a state loop. The `WalkingState` computes per-leg foot paths (Bezier/linear), converts positions to joint angles with `kinematics.ikpyInverse()`, then sends commands to hardware or updates the simulator.
|
||||
|
||||
**Configuration notes**
|
||||
- **URDF**: the robot description file is `JackBotUrdf.urdf`; ensure the path in `config.py` (`urdf_path`) is correct.
|
||||
- **Timing**: step timing and tick rate are configured in `config.py` (`standard_duration`, `standard_tickpersec`).
|
||||
|
||||
**Suggested next steps**
|
||||
- Add a `requirements.txt` or `pyproject.toml` for reproducible installs.
|
||||
- Document hardware wiring and expected serial/ESP packet formats if you plan to use hardware.
|
||||
- Provide an example `config.py` for common setups (sim vs hardware).
|
||||
|
||||
**License & Contributing**
|
||||
- No license file included. Add `LICENSE` if you want to publish or share.
|
||||
- Contributions: open an issue or PR with improvements; add tests for kinematics if possible.
|
||||
@@ -0,0 +1,20 @@
|
||||
import numpy as np
|
||||
|
||||
class RobotState:
|
||||
def on_enter(self, ctx):
|
||||
pass
|
||||
|
||||
def on_exit(self, ctx):
|
||||
pass
|
||||
|
||||
def update(self, ctx, intent, dt):
|
||||
pass
|
||||
|
||||
class RobotContext:
|
||||
def __init__(self):
|
||||
self.current_rad = None
|
||||
self.current_pos = None
|
||||
self.leg_state = None
|
||||
|
||||
self.robotCommunication = None
|
||||
self.shared_sim = None
|
||||
@@ -0,0 +1,7 @@
|
||||
import RobotState
|
||||
|
||||
class IdleState(RobotState):
|
||||
def update(self, ctx, intent, dt):
|
||||
if intent.walk:
|
||||
return "walking"
|
||||
return None
|
||||
@@ -0,0 +1,70 @@
|
||||
# RobotState/walking.py
|
||||
import time
|
||||
import numpy as np
|
||||
import config as cfg
|
||||
import kinematics as kin
|
||||
import DataTypes as dt
|
||||
import RobotState
|
||||
|
||||
class WalkingState(RobotState):
|
||||
|
||||
def on_enter(self, ctx):
|
||||
self.t = 0.0
|
||||
self.tick_duration = 1 / cfg.standard_tickpersec
|
||||
self.ticks = cfg.standard_duration * cfg.standard_tickpersec
|
||||
|
||||
self.current_pos_copy = ctx.current_pos.copy()
|
||||
self.dirmov = [0.0, 0.0]
|
||||
|
||||
def update(self, ctx, intent, dt):
|
||||
# 🛑 Transition check
|
||||
if not intent.walk:
|
||||
return "idle"
|
||||
|
||||
self.dirmov = [intent.move_x, intent.move_y]
|
||||
|
||||
t = self.t / self.ticks
|
||||
tick_pos_temp = []
|
||||
|
||||
for leg_id in range(6):
|
||||
if ctx.leg_state[leg_id] == "drag":
|
||||
tick_pos_temp.append(
|
||||
self.current_pos_copy[leg_id]
|
||||
+ (ctx.center_points[leg_id] - self.current_pos_copy[leg_id]) * t
|
||||
)
|
||||
|
||||
elif ctx.leg_state[leg_id] == "step":
|
||||
mid = [
|
||||
(self.current_pos_copy[leg_id][0] + ctx.center_points[leg_id][0]) / 2,
|
||||
(self.current_pos_copy[leg_id][1] + ctx.center_points[leg_id][1]) / 2,
|
||||
ctx.center_points[leg_id][2] + cfg.step_height,
|
||||
]
|
||||
|
||||
def bez(a, b, c):
|
||||
return (1 - t)**2 * a + 2*(1 - t)*t*b + t*t*c
|
||||
|
||||
x = bez(self.current_pos_copy[leg_id][0], mid[0], ctx.center_points[leg_id][0])
|
||||
y = bez(self.current_pos_copy[leg_id][1], mid[1], ctx.center_points[leg_id][1])
|
||||
z = bez(self.current_pos_copy[leg_id][2], mid[2], ctx.center_points[leg_id][2])
|
||||
|
||||
tick_pos_temp.append([x, y, z])
|
||||
|
||||
tick_pos = dt.PosArray(tick_pos_temp)
|
||||
target_rad = kin.ikpyInverse(tick_pos)
|
||||
|
||||
ctx.current_pos = tick_pos
|
||||
ctx.current_rad = target_rad
|
||||
|
||||
if ctx.robotCommunication:
|
||||
ctx.robotCommunication.send_command(target_rad)
|
||||
|
||||
self.t += 1
|
||||
if self.t >= self.ticks:
|
||||
self.t = 0
|
||||
ctx.leg_state = (
|
||||
np.array(["drag","step","drag","step","drag","step"])
|
||||
if ctx.leg_state[0] == "step"
|
||||
else np.array(["step","drag","step","drag","step","drag"])
|
||||
)
|
||||
|
||||
return None
|
||||
@@ -0,0 +1,29 @@
|
||||
# Connection
|
||||
port = "COM4"
|
||||
baudrate = 38400
|
||||
|
||||
esp32_ip = "192.168.188.32"
|
||||
esp32_port = 3323
|
||||
|
||||
timeout = 2.0
|
||||
|
||||
sim: bool = False
|
||||
arduinoConnection: bool = False
|
||||
urdf_path = "JackBotUrdf.urdf"
|
||||
|
||||
# row = legnumber (left 0,1,2 right 3,4,5)
|
||||
# column = servo position from torso(0) to feet(2)
|
||||
|
||||
# Dimensions in m
|
||||
#robot_height = -0.1
|
||||
step_height = 0.05
|
||||
step_length = 0.08
|
||||
# Leglength in m
|
||||
# L1 = 0.068
|
||||
# L2 = 0.0602
|
||||
# L3 = 0.070
|
||||
|
||||
# standard_tickpersec = # 20 Fluessige Bewegung
|
||||
standard_tickpersec:float = 25 # [ticks/s]
|
||||
standard_duration:float = 0.4 # [s],ip
|
||||
standard_tickduration:float = 1 / standard_tickpersec
|
||||
+293
@@ -0,0 +1,293 @@
|
||||
from ikpy.chain import Chain
|
||||
from ikpy.link import OriginLink, URDFLink
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
import math
|
||||
import time
|
||||
|
||||
import GlobalVariables as gv
|
||||
import config as cfg
|
||||
import DataTypes as dt
|
||||
|
||||
leg_chains = {
|
||||
0: Chain.from_urdf_file(
|
||||
cfg.urdf_path,
|
||||
base_elements=[
|
||||
"base_link",
|
||||
"leg1_coxa_joint",
|
||||
"leg1_coxa",
|
||||
"leg1_femur_joint",
|
||||
"leg1_femur",
|
||||
"leg1_tibia_joint",
|
||||
"leg1_tibia",
|
||||
"leg1_tip_joint",
|
||||
"leg1_tip",
|
||||
],
|
||||
),
|
||||
1: Chain.from_urdf_file(
|
||||
cfg.urdf_path,
|
||||
base_elements=[
|
||||
"base_link",
|
||||
"leg2_coxa_joint",
|
||||
"leg2_coxa",
|
||||
"leg2_femur_joint",
|
||||
"leg2_femur",
|
||||
"leg2_tibia_joint",
|
||||
"leg2_tibia",
|
||||
"leg2_tip_joint",
|
||||
"leg2_tip",
|
||||
],
|
||||
),
|
||||
2: Chain.from_urdf_file(
|
||||
cfg.urdf_path,
|
||||
base_elements=[
|
||||
"base_link",
|
||||
"leg3_coxa_joint",
|
||||
"leg3_coxa",
|
||||
"leg3_femur_joint",
|
||||
"leg3_femur",
|
||||
"leg3_tibia_joint",
|
||||
"leg3_tibia",
|
||||
"leg3_tip_joint",
|
||||
"leg3_tip",
|
||||
],
|
||||
),
|
||||
3: Chain.from_urdf_file(
|
||||
cfg.urdf_path,
|
||||
base_elements=[
|
||||
"base_link",
|
||||
"leg4_coxa_joint",
|
||||
"leg4_coxa",
|
||||
"leg4_femur_joint",
|
||||
"leg4_femur",
|
||||
"leg4_tibia_joint",
|
||||
"leg4_tibia",
|
||||
"leg4_tip_joint",
|
||||
"leg4_tip",
|
||||
],
|
||||
),
|
||||
4: Chain.from_urdf_file(
|
||||
cfg.urdf_path,
|
||||
base_elements=[
|
||||
"base_link",
|
||||
"leg5_coxa_joint",
|
||||
"leg5_coxa",
|
||||
"leg5_femur_joint",
|
||||
"leg5_femur",
|
||||
"leg5_tibia_joint",
|
||||
"leg5_tibia",
|
||||
"leg5_tip_joint",
|
||||
"leg5_tip",
|
||||
],
|
||||
),
|
||||
5: Chain.from_urdf_file(
|
||||
cfg.urdf_path,
|
||||
base_elements=[
|
||||
"base_link",
|
||||
"leg6_coxa_joint",
|
||||
"leg6_coxa",
|
||||
"leg6_femur_joint",
|
||||
"leg6_femur",
|
||||
"leg6_tibia_joint",
|
||||
"leg6_tibia",
|
||||
"leg6_tip_joint",
|
||||
"leg6_tip",
|
||||
],
|
||||
),
|
||||
}
|
||||
|
||||
for leg in leg_chains.values():
|
||||
leg.active_links_mask = [False, True, True, True, False]
|
||||
|
||||
|
||||
def ikpyForward(target_rad: dt.RadArray) -> dt.PosArray:
|
||||
target_pos_temp = []
|
||||
|
||||
for leg_id, chain in leg_chains.items():
|
||||
# Forward kinematics -> 4x4 matrix
|
||||
fk_matrix = chain.forward_kinematics([0] + list(target_rad[leg_id]) + [0])
|
||||
# Extract translation vector (x, y, z)
|
||||
x, y, z = fk_matrix[:3, 3]
|
||||
target_pos_temp.append([x, y, z])
|
||||
return dt.RadArray(np.array(target_pos_temp))
|
||||
|
||||
|
||||
def ikpyInverse(target_pos: dt.PosArray) -> dt.RadArray:
|
||||
target_rad_temp = []
|
||||
print("target_pos:\n",target_pos)
|
||||
for leg_id, chain in leg_chains.items():
|
||||
# prepare initial guess
|
||||
guess = np.array([0] + list(gv.current_rad[leg_id]) + [0], dtype=float)
|
||||
|
||||
try:
|
||||
ik_result = chain.inverse_kinematics(
|
||||
target_pos[leg_id],
|
||||
initial_position=guess,
|
||||
max_iter=100
|
||||
)
|
||||
except ValueError:
|
||||
# fallback → keep the clipped guess
|
||||
ik_result = guess
|
||||
|
||||
# keep only the 3 actuated joint angles (indices 1,2,3)
|
||||
target_rad_temp.append(ik_result[1:4])
|
||||
|
||||
return dt.RadArray(np.array(target_rad_temp))
|
||||
# for leg_id, chain in leg_chains.items():
|
||||
# ik_result = chain.inverse_kinematics(target_pos[leg_id])
|
||||
# # Remove first element (IKPy adds a "dummy" fixed base joint)
|
||||
# target_rad_temp.append(ik_result[1:4])
|
||||
return dt.RadArray(np.array(target_rad_temp))
|
||||
|
||||
|
||||
def ikpytest():
|
||||
targets: dt.PosArray = dt.PosArray(
|
||||
[
|
||||
[0.047, 0.272, 0],
|
||||
[0, 0.272, 0],
|
||||
[-0.047, 0.272, 0],
|
||||
[-0.047, -0.272, 0],
|
||||
[0, -0.272, 0],
|
||||
[0.047, -0.272, 0],
|
||||
]
|
||||
)
|
||||
joint_angles_deg: dt.DegArray = dt.DegArray(
|
||||
[
|
||||
[90, 90, 90],
|
||||
[90, 90, 90],
|
||||
[90, 90, 90],
|
||||
[90, 90, 90],
|
||||
[90, 90, 90],
|
||||
[90, 90, 90],
|
||||
]
|
||||
)
|
||||
current_rad: dt.RadArray = joint_angles_deg.to_rad()
|
||||
ikpyInverse(targets, current_rad)
|
||||
|
||||
|
||||
# walk old
|
||||
"""
|
||||
def walk(duration=standard_duration, ticks=standard_tickrate, curve_height=gv.step_height):
|
||||
tick_duration = duration / ticks
|
||||
tick_positions = np.copy(gv.current_pos)
|
||||
dirmov_copy = gv.vector_dirmov
|
||||
target_pos =[]
|
||||
for i in range(6): # Zielposition berechnen
|
||||
target_pos.append([gv.center_points[i][0] + dirmov_copy[0] * gv.step_length, gv.center_points[i][1] + dirmov_copy[1] * gv.step_length, gv.robot_height ])
|
||||
def interpolate(t, p0, p1, p2):
|
||||
return (1 - t)**2 * p0 + 2 * (1 - t) * t * p1 + t**2 * p2
|
||||
|
||||
for tick in range(int(ticks) + 1):
|
||||
loop_start = time.perf_counter()
|
||||
t = tick / ticks
|
||||
|
||||
for leg_id in range(6):
|
||||
if gv.leg_state[leg_id] == "drag":
|
||||
# Linear interpolation
|
||||
tick_positions[leg_id] = gv.current_pos[leg_id] + (gv.center_points[leg_id] - gv.current_pos[leg_id]) * t
|
||||
|
||||
elif gv.leg_state[leg_id] == "step":
|
||||
# Curve movement (Bezier path)
|
||||
help_pos = [
|
||||
target_pos[leg_id][0] - gv.current_pos[leg_id][0],
|
||||
target_pos[leg_id][1] - gv.current_pos[leg_id][1],
|
||||
target_pos[leg_id][2] + curve_height
|
||||
]
|
||||
x = interpolate(t, gv.current_pos[leg_id][0], help_pos[0], target_pos[leg_id][0])
|
||||
y = interpolate(t, gv.current_pos[leg_id][1], help_pos[1], target_pos[leg_id][1])
|
||||
z = interpolate(t, gv.current_pos[leg_id][2], help_pos[2], target_pos[leg_id][2])
|
||||
tick_positions[leg_id] = [x, y, z]
|
||||
|
||||
# Send all legs at once through IK
|
||||
ikpyInverse(tick_positions, gv.current_pos)
|
||||
|
||||
elapsed = time.perf_counter() - loop_start
|
||||
sleep_time = tick_duration - elapsed
|
||||
if sleep_time > 0:
|
||||
time.sleep(sleep_time)
|
||||
|
||||
# Final position correction
|
||||
ikpyInverse(target_pos, gv.current_pos)
|
||||
|
||||
if (gv.leg_state[0] == "step"):
|
||||
gv.leg_state = np.array(["drag", "step", "drag", "step", "drag", "step"])
|
||||
if (gv.leg_state[0] == "drag"):
|
||||
gv.leg_state = np.array(["step", "drag", "step", "drag", "step", "drag"])
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
|
||||
# Berechnung der Bein Bewegung - Grade
|
||||
def drag_leg(
|
||||
leg_num,
|
||||
current_pos,
|
||||
target_pos,
|
||||
duration=cfg.standard_duration,
|
||||
ticks=cfg.standard_tickrate,
|
||||
):
|
||||
tick_duration = duration / ticks
|
||||
dragtickvec = (target_pos - current_pos[leg_num]) / ticks
|
||||
|
||||
tick_positions = np.copy(current_pos)
|
||||
|
||||
for tick in range(int(ticks)):
|
||||
start_time = time.perf_counter()
|
||||
|
||||
tick_positions[leg_num] += dragtickvec
|
||||
ikpyInverse(tick_positions, current_pos)
|
||||
|
||||
elapsed = time.perf_counter() - start_time
|
||||
sleep_time = tick_duration - elapsed
|
||||
if sleep_time > 0:
|
||||
time.sleep(sleep_time)
|
||||
|
||||
tick_positions[leg_num] = target_pos
|
||||
ikpyInverse(tick_positions, current_pos)
|
||||
time.sleep(0.05)
|
||||
|
||||
|
||||
# Berechnung der Bein Bewegung - Kurve
|
||||
def curve_leg(
|
||||
leg_num,
|
||||
current_pos,
|
||||
target_pos,
|
||||
curve_height=cfg.step_height,
|
||||
duration=cfg.standard_duration,
|
||||
ticks=cfg.standard_tickrate,
|
||||
):
|
||||
tick_duration = duration / ticks
|
||||
|
||||
help_pos = [
|
||||
target_pos[0] - current_pos[leg_num][0],
|
||||
target_pos[1] - current_pos[leg_num][1],
|
||||
target_pos[2] + curve_height,
|
||||
]
|
||||
|
||||
def interpolate(t, p0, p1, p2):
|
||||
return (1 - t) ** 2 * p0 + 2 * (1 - t) * t * p1 + t**2 * p2
|
||||
|
||||
tick_positions = np.copy(current_pos)
|
||||
|
||||
for tick in range(int(ticks) + 1):
|
||||
start_time = time.perf_counter()
|
||||
|
||||
t = tick / ticks
|
||||
x = interpolate(t, current_pos[leg_num][0], help_pos[0], target_pos[0])
|
||||
y = interpolate(t, current_pos[leg_num][1], help_pos[1], target_pos[1])
|
||||
z = interpolate(t, current_pos[leg_num][2], help_pos[2], target_pos[2])
|
||||
|
||||
tick_positions[leg_num] = [x, y, z]
|
||||
ikpyInverse(tick_positions, current_pos)
|
||||
|
||||
elapsed = time.perf_counter() - start_time
|
||||
sleep_time = tick_duration - elapsed
|
||||
if sleep_time > 0:
|
||||
time.sleep(sleep_time)
|
||||
|
||||
tick_positions[leg_num] = target_pos
|
||||
ikpyInverse(tick_positions, current_pos)
|
||||
time.sleep(0.05)
|
||||
|
||||
"""
|
||||
if __name__ == "__main__":
|
||||
print(ikpyForward(gv.test_deg.to_rad()))
|
||||
@@ -0,0 +1,61 @@
|
||||
# Global Variables
|
||||
import GlobalVariables as gv
|
||||
|
||||
# main.py
|
||||
import time
|
||||
import threading
|
||||
|
||||
from DataTypes import ControlIntent
|
||||
from RobotState.RobotState import RobotContext
|
||||
from Controller import controller_loop
|
||||
from RobotState.idle import IdleState
|
||||
from RobotState.walking import WalkingState
|
||||
|
||||
import DataTypes as dt
|
||||
|
||||
def main():
|
||||
intent = ControlIntent()
|
||||
ctx = RobotContext()
|
||||
|
||||
ctx.center_points = dt.PosArray([
|
||||
[50, 50, -80],
|
||||
[50,-50, -80],
|
||||
[0, 70, -80],
|
||||
[0,-70, -80],
|
||||
[-50,50, -80],
|
||||
[-50,-50,-80]
|
||||
])
|
||||
ctx.current_pos = ctx.center_points.copy()
|
||||
|
||||
controller_thread = threading.Thread(
|
||||
target=controller_loop,
|
||||
args=(intent,),
|
||||
daemon=True
|
||||
)
|
||||
controller_thread.start()
|
||||
|
||||
states = {
|
||||
"idle": IdleState(),
|
||||
"walking": WalkingState()
|
||||
}
|
||||
|
||||
current = states["idle"]
|
||||
current.on_enter(ctx)
|
||||
|
||||
last = time.perf_counter()
|
||||
|
||||
while not intent.quit:
|
||||
now = time.perf_counter()
|
||||
dt_s = now - last
|
||||
last = now
|
||||
|
||||
next_state = current.update(ctx, intent, dt_s)
|
||||
if next_state:
|
||||
current.on_exit(ctx)
|
||||
current = states[next_state]
|
||||
current.on_enter(ctx)
|
||||
|
||||
time.sleep(0.01)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,68 @@
|
||||
import pybullet as p
|
||||
import numpy as np
|
||||
import RobotState as rs
|
||||
import GlobalVariables as gv
|
||||
import config as cfg
|
||||
import DataTypes as dt
|
||||
import time
|
||||
import os
|
||||
import math
|
||||
|
||||
|
||||
class Simulation:
|
||||
def __init__(self):
|
||||
self.physics_client = p.connect(p.GUI)
|
||||
self.robot = p.loadURDF(cfg.urdf_path, useFixedBase=True)
|
||||
|
||||
self.revolute_joints = [
|
||||
i
|
||||
for i in range(p.getNumJoints(self.robot))
|
||||
if p.getJointInfo(self.robot, i)[2] == p.JOINT_REVOLUTE
|
||||
]
|
||||
|
||||
self.set_all_joints_to_90()
|
||||
p.resetDebugVisualizerCamera(
|
||||
cameraDistance=1.0,
|
||||
cameraYaw=50,
|
||||
cameraPitch=-35,
|
||||
cameraTargetPosition=[0, 0, 0],
|
||||
)
|
||||
|
||||
def set_all_joints_to_90(self):
|
||||
for joint_index in range(p.getNumJoints(self.robot)):
|
||||
joint_info = p.getJointInfo(self.robot, joint_index)
|
||||
joint_type = joint_info[2]
|
||||
if joint_type == p.JOINT_REVOLUTE:
|
||||
p.resetJointState(self.robot, joint_index, math.radians(90))
|
||||
|
||||
def updatePos(self, current_rad: dt.RadArray):
|
||||
radflat = current_rad.data.flatten()
|
||||
for joint_index, target_angle in zip(self.revolute_joints, radflat):
|
||||
p.setJointMotorControl2(
|
||||
bodyIndex=self.robot,
|
||||
jointIndex=joint_index,
|
||||
controlMode=p.POSITION_CONTROL,
|
||||
targetPosition=target_angle,
|
||||
force=500,
|
||||
)
|
||||
|
||||
def step(self):
|
||||
p.stepSimulation()
|
||||
|
||||
def disconnect(self):
|
||||
p.disconnect(self.physics_client)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
count = 0
|
||||
current_rad: dt.RadArray = gv.init_deg.to_rad()
|
||||
gv.shared_sim.updatePos(current_rad)
|
||||
|
||||
rs.walking()
|
||||
while True:
|
||||
count = +1
|
||||
gv.shared_sim.step()
|
||||
time.sleep(1 / 240)
|
||||
if count > 50:
|
||||
rs.walking()
|
||||
count = 0
|
||||
Reference in New Issue
Block a user