Compare commits

..

8 Commits

Author SHA1 Message Date
0b23964d25 feat: added comments 2025-05-04 22:57:38 +02:00
aebc157507 feat: removed useless function 2025-05-04 22:33:59 +02:00
248e8dd073 feat: added sliders back 2025-05-04 22:30:57 +02:00
561b2f70a2 fix bug in rotation 2025-04-14 18:02:30 +02:00
25af55d963 robot control 2025-04-14 17:12:02 +02:00
6b0f2c2170 feat: finised project 2025-04-07 14:53:38 +02:00
90d08671d9 feat: addded 🦖 2025-04-05 23:05:15 +02:00
8479dec345 feat: created modules, logs and removed unuzed functions 2025-04-05 21:17:30 +02:00
5 changed files with 467 additions and 284 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
models
__pycache__

View File

@ -1,87 +1,118 @@
import math import logging
import numpy as np import numpy as np
def sandbox(t):
"""
python simulator.py -m sandbox
Un premier bac à sable pour faire des expériences
La fonction reçoit le temps écoulé depuis le début (t) et retourne une position cible
pour les angles des 12 moteurs
- Entrée: t, le temps (secondes écoulées depuis le début)
- Sortie: un tableau contenant les 12 positions angulaires cibles (radian) pour les moteurs
"""
# Par exemple, on envoie un mouvement sinusoidal
targets = [0] * 12
targets[0] = t ** 3
targets[1] = 0
targets[2] = 0
targets[3] = t ** 3
targets[4] = 0
targets[5] = 0
targets[6] = t ** 3
targets[7] = 0
targets[8] = 0
targets[9] = t ** 3
targets[10] = 0
targets[11] = 0
return targets
def direct(alpha, beta, gamma):
"""
python simulator.py -m direct
Le robot est figé en l'air, on ne contrôle qu'une patte
Reçoit en argument la cible (alpha, beta, gamma) des degrés de liberté de la patte, et produit
la position (x, y, z) atteinte par le bout de la patte
- Sliders: les angles des trois moteurs (alpha, beta, gamma)
- Entrées: alpha, beta, gamma, la cible (radians) des moteurs
- Sortie: un tableau contenant la position atteinte par le bout de la patte (en mètres)
"""
return 0, 0, 0
from math import * from math import *
from logs import *
from dynamixel_sdk import *
from xbox import *
import time
# DEBUG
LEGS_LOG_LEVEL = logging.WARNING
CONTROLLER_LOG_LEVEL = logging.CRITICAL
# Variables configurations
l1h = 0.049 l1h = 0.049
l1v = 0.032 l1v = 0.032
l1 = l1h l1 = l1h # this is not the real distance as it's not the one needed to calculate position.
# length between motor 2 and motor 3.
l2h = 0.0605 l2h = 0.0605
l2v = 0.02215 l2v = 0.02215
l2 = sqrt(l2h ** 2 + l2v ** 2) l2 = sqrt(l2h ** 2 + l2v ** 2)
# length between motor 3 and end of the leg.
l3h = 0.012 l3h = 0.012
l3v = 0.093 l3v = 0.093
l3 = sqrt(l3h ** 2 + l3v ** 2) l3 = sqrt(l3h ** 2 + l3v ** 2)
# offset of the 'head', the legs isolated at each end.
tete_x = 0.095 tete_x = 0.095
# offset of the legs at the side.
patte_y = 0.032 patte_y = 0.032
patte_x = 0.079 patte_x = 0.079
num_patte = 6
# Logs functions
legsLogger = setup_logger("legs", LEGS_LOG_LEVEL)
controllerLogger = setup_logger("Controller", CONTROLLER_LOG_LEVEL)
# Initialize controller
xbox = Xbox(controllerLogger)
CONTROLLER_MODE = xbox.initialized
xbox.mode_count = 5
neutral_position = np.array([
[0.08, 0.16, -0.12],
[-0.08, 0.16, -0.12],
[-0.2, -0.00, -0.12],
[-0.08, -0.16, -0.12],
[0.08, -0.16, -0.12],
[0.2, 0, -0.12]
])
REAL = 0
# Robot setup
if REAL == 1:
try:
portHandler = PortHandler("/dev/ttyUSB0")
except:
portHandler = PortHandler("/dev/ttyUSB1")
packetHandler = PacketHandler(1.0)
portHandler.openPort()
portHandler.setBaudRate(1000000)
ADDR_GOAL_POSITION = 30
ids = []
for id in range(0, 100):
model_number, result, error = packetHandler.ping(portHandler, id)
if model_number == 12:
print(f"Found AX-12 with id: {id}")
ids.append(id)
time.sleep(0.01)
if len(ids) != 18:
legsLogger.error("Not all motor found. Press enter to continue")
input()
def interpol2(point2, point1, t):
"""
Linear interpolation between two points in space
"""
x1, y1, z1 = point1
x2, y2, z2 = point2
return t * x1 + (1 - t) * x2, t * y1 + (1 - t) * y2, t * z1 + (1 - t) * z2
def get_current_step(t, step_duration, movement_duration):
"""
helper function used to calculate the current step of the movement i.e. what part of the movement.
"""
time_passed = 0
for i in range(len(step_duration)):
time_passed += step_duration[i]
if t % movement_duration < time_passed:
return i
def get_current_step_advancement(t, movement_duration, step_duration, current_step):
"""
helper function used to calculate the current advancement of the movement step i.e. the percentage of progression of said movement.
"""
current_step = get_current_step(t, step_duration, movement_duration)
t = t % movement_duration
for i in range(0, current_step):
t -= step_duration[i]
return t / step_duration[current_step]
def inverse(x, y, z): def inverse(x, y, z):
""" """
python simulator.py -m inverse Calculate the angle of each motor to have the end of the feet at a specified position
Le robot est figé en l'air, on ne contrôle qu'une patte
Reçoit en argument une position cible (x, y, z) pour le bout de la patte, et produit les angles
(alpha, beta, gamma) pour que la patte atteigne cet objectif
- Sliders: la position cible x, y, z du bout de la patte
- Entrée: x, y, z, une position cible dans le repère de la patte (mètres), provenant du slider
- Sortie: un tableau contenant les 3 positions angulaires cibles (en radians)
""" """
# Dimensions (m) # Dimensions (m)
z += l1v z += l1v
@ -89,76 +120,29 @@ def inverse(x, y, z):
theta0 = atan2(y, x) theta0 = atan2(y, x)
l = sqrt((sqrt(x ** 2 + y ** 2) - l1) ** 2 + z ** 2) l = sqrt((sqrt(x ** 2 + y ** 2) - l1) ** 2 + z ** 2)
# l = sqrt((x - l1h*cos(theta0)) ** 2 + (y - l1h*sin(theta0)) ** 2 + (z + l1v) ** 2)
param2 = -1 * (-(l ** 2) + l2 ** 2 + l3 ** 2) / (2 * l2 * l3) param2 = -1 * (-(l ** 2) + l2 ** 2 + l3 ** 2) / (2 * l2 * l3)
if param2 > 1 or param2 < -1: if param2 > 1 or param2 < -1:
print("\033[94m" + f"Tentative d'acces a une position impossible (param2) ({x}, {y}, {z})" + "\033[0m") legsLogger.warning("fTentative d'acces a une position impossible (param2) ({x}, {y}, {z})")
param2 = 1 if param2 > 1 else -1 param2 = 1 if param2 > 1 else -1
theta2 = acos(param2) theta2 = acos(param2)
param1 = (-l3 ** 2 + l2 ** 2 + l ** 2) / (2 * l2 * l) param1 = (-l3 ** 2 + l2 ** 2 + l ** 2) / (2 * l2 * l)
if param1 > 1 or param1 < -1: if param1 > 1 or param1 < -1:
print("\033[94m" + f"Tentative d'acces a une position impossible (param1) ({x}, {y}, {z})" + "\033[0m") legsLogger.warning(f"Tentative d'acces a une position impossible (param1) ({x}, {y}, {z})")
param1 = 1 if param1 > 1 else -1 param1 = 1 if param1 > 1 else -1
theta1 = acos(param1) + asin(z / l) theta1 = acos(param1) + asin(z / l)
# return [-theta0, theta1, theta2]
angle1 = atan(l2v / l2h) angle1 = atan(l2v / l2h)
return [-theta0, theta1 + angle1, theta2 + angle1 - pi / 2 + atan(l3h / l3v)] return [-theta0, theta1 + angle1, theta2 + angle1 - pi / 2 + atan(l3h / l3v)]
# return [0, angle1 , angle1 -pi/2 + atan(l3h/l3v)]
def draw(t):
"""
python simulator.py -m draw
Le robot est figé en l'air, on ne contrôle qu'une patte
Le but est, à partir du temps donné, de suivre une trajectoire de triangle. Pour ce faire, on
utilisera une interpolation linéaire entre trois points, et la fonction inverse précédente.
- Entrée: t, le temps (secondes écoulées depuis le début)
- Sortie: un tableau contenant les 3 positions angulaires cibles (en radians)
"""
def interpol(x2, y2, z2, x1, y1, z1, t):
return t * x1 + (1 - t) * x2, t * y1 + (1 - t) * y2, t * z1 + (1 - t) * z2
p1 = [0.15, 0, 0]
p2 = [0.1, 0.1, 0]
p3 = [0.1, 0, 0.05]
positions = [p1, p2, p3]
ratio = (t % 1)
partie = floor((t % 3))
partie2 = (partie + 1) % 3
# print(f"partie: {partie}, partie2: {partie2}")
wanted_origin_x, wanted_origin_y, wanted_origin_z = positions[partie][0], positions[partie][1], positions[partie][
2],
wanted_dest_x, wanted_dest_y, wanted_dest_z = positions[partie2][0], positions[partie2][1], positions[partie2][2],
# print(wanted_origin_x, wanted_origin_y, wanted_origin_z)
# print(wanted_dest_x, wanted_dest_y, wanted_dest_z)
# print(t)
wanted_x, wanted_y, wanted_z = interpol(wanted_origin_x, wanted_origin_y, wanted_origin_z, wanted_dest_x,
wanted_dest_y, wanted_dest_z, ratio)
return inverse(wanted_x, wanted_y, wanted_z)
def legs(targets_robot): def legs(targets_robot):
""" """
python simulator.py -m legs takes a list of target and offsets them to be in the legs referential
Le robot est figé en l'air, on contrôle toute les pattes
- Sliders: les 12 coordonnées (x, y, z) du bout des 4 pattes
- Entrée: des positions cibles (tuples (x, y, z)) pour le bout des 4 pattes
- Sortie: un tableau contenant les 12 positions angulaires cibles (radian) pour les moteurs
""" """
targets = [0] * 18 targets = [0] * 18
@ -187,36 +171,74 @@ def legs(targets_robot):
return targets return targets
def interpol2(point2, point1, t): def normalize(matrix, slider_max, speed):
x1, y1, z1 = point1 return (matrix / slider_max) * speed
x2, y2, z2 = point2
return t * x1 + (1 - t) * x2, t * y1 + (1 - t) * y2, t * z1 + (1 - t) * z2
def walkV1(t, speed_x, speed_y, speed_rotation): counter = 0
def convert_to_robot(positions):
""" """
python simulator.py -m walk Send a robot position to the real robot.
"""
Le but est d'intégrer tout ce que nous avons vu ici pour faire marcher le robot global counter
counter += 1
- Sliders: speed_x, speed_y, speed_rotation, la vitesse cible du robot if counter % 2 != 0:
- Entrée: t, le temps (secondes écoulées depuis le début) return
speed_x, speed_y, et speed_rotation, vitesses cibles contrôlées par les sliders index = [
- Sortie: un tableau contenant les 12 positions angulaires cibles (radian) pour les moteurs 11, 12, 13,
41, 42, 43,
21, 22, 23,
51, 52, 53,
61, 62, 63,
31, 32, 33]
dir = [-1] * 18
for i in range(len(positions)):
value = int(512 + positions[i] * 150 * dir[i])
packetHandler.write2ByteTxOnly(portHandler, index[i], ADDR_GOAL_POSITION, value)
def walk(t, sx, sy, sr):
"""
Choose the algorithm depending on input of xbox controller
"""
xboxdata = xbox.get_data()
if xbox.initialized:
max_slider = 0.200
controllerLogger.debug(xboxdata)
if xbox.mode == 0:
positions = static()
elif xbox.mode == 1:
positions = jump(xboxdata["y2"])
elif xbox.mode == 2:
positions = dino_naive(t, max_slider * xboxdata["y1"], max_slider * xboxdata["x1"],
max_slider * xboxdata["y2"],
max_slider * xboxdata["x2"], max_slider * (xboxdata["r2"] + 1))
elif xbox.mode == 3:
positions = naive_walk(t, max_slider * xboxdata["x1"], max_slider * xboxdata["y1"])
elif xbox.mode == 4:
positions = walk_v2(t, max_slider * xboxdata["y1"], -1 * max_slider * xboxdata["x1"],
xboxdata["x2"] * max_slider,
max_slider * xboxdata["y2"])
# print(positions)
if REAL == 1:
convert_to_robot(positions)
return positions
else:
return walk_v2(t, sx, sy, sr, None)
# different move algorithm
def naive_walk(t, speed_x, speed_y):
"""
First version of walk, using triangle for each step
""" """
# t = t*speed_x * 20
num_patte = 6
slider_max = 0.200 slider_max = 0.200
neutral_position = np.array([
[0.1, 0.15, -0.15],
[-0.1, 0.15, -0.15],
[-0.2, -0.00, -0.15],
[-0.1, -0.15, -0.15],
[0.1, -0.15, -0.15],
[0.2, 0, -0.15]
])
real_position = np.copy(neutral_position) real_position = np.copy(neutral_position)
movement_x = np.array([ movement_x = np.array([
@ -246,36 +268,15 @@ def walkV1(t, speed_x, speed_y, speed_rotation):
assert len( assert len(
step_duration) == step_count, f"all movements steps must have a length, currently, {len(step_duration)}/{step_count} have them" step_duration) == step_count, f"all movements steps must have a length, currently, {len(step_duration)}/{step_count} have them"
def get_current_step(t):
time_passed = 0
for i in range(len(step_duration)):
time_passed += step_duration[i]
if t % movement_duration < time_passed:
return i
def get_current_step_advancement(t):
current_step = get_current_step(t)
t = t % movement_duration
for i in range(0, current_step):
t -= step_duration[i]
return t / step_duration[current_step]
def get_next_step(t): def get_next_step(t):
return floor((get_current_step(t) + 1) % step_count) return floor((get_current_step(t, step_duration, movement_duration) + 1) % step_count)
def rotate(patte):
return [1, -1, 0, -1, 1, 0][
patte] * movement_x # + [-1, 1, -1, 1][patte] * movement_y # mettre des 0 partout sur le Y fait une très belle rotation
def normalize(matrix, slider_max, speed):
return (matrix / slider_max) * speed
offsets = np.array([0, 1 / 3, 2 / 3, 0, 1 / 3, 2 / 3]) * movement_duration # offset between each leg offsets = np.array([0, 1 / 3, 2 / 3, 0, 1 / 3, 2 / 3]) * movement_duration # offset between each leg
assert len(offsets) == num_patte, f"all offsets must be set, currently, {len(offsets)}/{num_patte} have them" assert len(offsets) == num_patte, f"all offsets must be set, currently, {len(offsets)}/{num_patte} have them"
for patte in range(num_patte): for patte in range(num_patte):
time = t + offsets[patte] time = t + offsets[patte]
mov_index_start = get_current_step(time) mov_index_start = get_current_step(time, step_duration, movement_duration)
mov_index_end = get_next_step(time) mov_index_end = get_next_step(time)
mov_start_x = normalize(movement_x[mov_index_start], slider_max, speed_x) mov_start_x = normalize(movement_x[mov_index_start], slider_max, speed_x)
@ -287,99 +288,58 @@ def walkV1(t, speed_x, speed_y, speed_rotation):
mov_start_z = movement_z[mov_index_start] mov_start_z = movement_z[mov_index_start]
mov_end_z = movement_z[mov_index_end] mov_end_z = movement_z[mov_index_end]
mov_start_rotate = normalize(rotate(patte)[mov_index_start], 0.5, speed_rotation) mov_start = neutral_position[patte] + mov_start_z + mov_start_x + mov_start_y
mov_end_rotate = normalize(rotate(patte)[mov_index_end], 0.5, speed_rotation) mov_end = neutral_position[patte] + mov_end_z + mov_end_x + mov_end_y
mov_start = neutral_position[patte] + mov_start_z + mov_start_x + mov_start_y + mov_start_rotate
mov_end = neutral_position[patte] + mov_end_z + mov_end_x + mov_end_y + mov_end_rotate
(real_position[patte][0], (real_position[patte][0],
real_position[patte][1], real_position[patte][1],
real_position[patte][2]) = interpol2(mov_start, mov_end, get_current_step_advancement(time)) real_position[patte][2]) = interpol2(mov_start, mov_end,
print( get_current_step_advancement(time, movement_duration, step_duration,
f"[{patte}] [{get_current_step(time)}->{get_next_step(time)}], start: {mov_start}, end: {mov_end}, current ({real_position[patte][0]}, {real_position[patte][1]}, {real_position[patte][2]})") mov_index_start))
legsLogger.debug(
f"[{patte}] [{mov_index_start}->{mov_index_end}], start: {mov_start}, end: {mov_end}, current ({real_position[patte][0]}, {real_position[patte][1]}, {real_position[patte][2]})")
return legs(real_position) return legs(real_position)
def translate(tx, ty, tz): def static():
return np.array([ """
[1.0, 0.0, 0.0, tx], Function to have the robot stand at the neutral position
[0.0, 1.0, 0.0, ty], """
[0.0, 0.0, 1.0, tz], return legs(neutral_position)
[0.0, 0.0, 0.0, 1.0],
])
def Rx(alpha): # Walk V2
return np.array([ def walk_v2(t, speed_x, speed_y, speed_r, y2):
[1.0, 0.0, 0.0, 0.0], """
[0.0, np.cos(alpha), -np.sin(alpha), 0.0], Second version of the walking algorithm, using circle for each leg
[0.0, np.sin(alpha), np.cos(alpha), 0.0], This is the main algorithm.
[0.0, 0.0, 0.0, 1.0], """
]) max_dist = 0.3
def Ry(alpha):
return np.array([
[np.cos(alpha), 0.0, -np.sin(alpha), 0.0],
[0.0, 1.0, 0.0, 0.0],
[np.sin(alpha), 0.0, np.cos(alpha), 0.0],
[0.0, 0.0, 0.0, 1.0],
])
def Rz(alpha):
return np.array([
[np.cos(alpha), -np.sin(alpha), 0.0, 0.0],
[np.sin(alpha), np.cos(alpha), 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
])
# multiplication de matrices: @
# gauche: monde, droite: repere
def walkV2(t, speed_x, speed_y, speed_rotation):
def get_rotation_center(speed_x, speed_y, theta_point): def get_rotation_center(speed_x, speed_y, theta_point):
direction = np.array([-speed_y, speed_x]) """
return direction / max(0.001, theta_point) Helper function that calculate the center of rotation of the robot based on the current parameters
"""
norm = sqrt(speed_x ** 2 + speed_y ** 2)
x0, y0 = get_rotation_center(speed_x, speed_y, speed_rotation) r = 1000 if theta_point == 0 else norm / theta_point
if norm != 0:
print(x0, y0) return np.array([speed_y, -speed_x]) / norm * r
else:
""" return 0, 0
python simulator.py -m walk
Le but est d'intégrer tout ce que nous avons vu ici pour faire marcher le robot
- Sliders: speed_x, speed_y, speed_rotation, la vitesse cible du robot
- Entrée: t, le temps (secondes écoulées depuis le début)
speed_x, speed_y, et speed_rotation, vitesses cibles contrôlées par les sliders
- Sortie: un tableau contenant les 12 positions angulaires cibles (radian) pour les moteurs
"""
# t = t*speed_x * 20
num_patte = 4
slider_max = 0.200
neutral_position = np.array([
[-0.06, 0.06, -0.13],
[-0.06, -0.06, -0.13],
[0.06, -0.06, -0.13], # [0.15, 0.15, -0.01],
[0.06, 0.06, -0.13]
])
center_x, center_y = get_rotation_center(speed_x, speed_y, speed_r)
legsLogger.debug(f"rotation center: center_x: {center_x}, center_y: {center_y}")
real_position = np.copy(neutral_position) real_position = np.copy(neutral_position)
movement_z = np.array([ movement_z = np.array([
[0, 0, 0.02], [0, 0, 0.03],
[0, 0, -0.01], [0, 0, -0.02],
[0, 0, -0.01] [0, 0, -0.02]
]) ])
step_duration = np.array([0.05, 0.3, 0.05]) step_duration = np.array([0.15, 0.3, 0.15])
step_count = len(movement_z) step_count = len(movement_z)
movement_duration = np.sum(step_duration) movement_duration = np.sum(step_duration)
@ -387,31 +347,26 @@ def walkV2(t, speed_x, speed_y, speed_rotation):
assert len( assert len(
step_duration) == step_count, f"all movements steps must have a length, currently, {len(step_duration)}/{step_count} have them" step_duration) == step_count, f"all movements steps must have a length, currently, {len(step_duration)}/{step_count} have them"
def get_current_step(t):
time_passed = 0
for i in range(len(step_duration)):
time_passed += step_duration[i]
if t % movement_duration < time_passed:
return i
def get_current_step_advancement(t):
current_step = get_current_step(t)
t = t % movement_duration
for i in range(0, current_step):
t -= step_duration[i]
return t / step_duration[current_step]
def get_next_step(t): def get_next_step(t):
return floor((get_current_step(t) + 1) % step_count) return floor((get_current_step(t, step_duration, movement_duration) + 1) % step_count)
offsets = np.array([0, 0.5, 0, 0.5]) * movement_duration # offset between each leg offsets = np.array([0, 1 / 3, 2 / 3, 0, 1 / 3, 2 / 3]) * movement_duration # offset between each leg
assert len(offsets) == num_patte, f"all offsets must be set, currently, {len(offsets)}/{num_patte} have them" assert len(offsets) == num_patte, f"all offsets must be set, currently, {len(offsets)}/{num_patte} have them"
# used to cap the speed to a max
max_radius = 0
for patte in range(num_patte):
x1, y1 = real_position[patte][0], real_position[patte][1]
circle_radius = sqrt((center_x - x1) ** 2 + (center_y - y1) ** 2)
max_radius = max(max_radius, circle_radius)
for patte in range(num_patte): for patte in range(num_patte):
time = t + offsets[patte] time = t + offsets[patte]
mov_index_start = get_current_step(time) mov_index_start = get_current_step(time, step_duration, movement_duration)
mov_index_end = get_next_step(time) mov_index_end = get_next_step(time)
step_adv = get_current_step_advancement(time, movement_duration, step_duration, mov_index_start)
mov_start_z = movement_z[mov_index_start] mov_start_z = movement_z[mov_index_start]
mov_end_z = movement_z[mov_index_end] mov_end_z = movement_z[mov_index_end]
@ -420,33 +375,137 @@ def walkV2(t, speed_x, speed_y, speed_rotation):
(real_position[patte][0], (real_position[patte][0],
real_position[patte][1], real_position[patte][1],
real_position[patte][2]) = interpol2(mov_start, mov_end, get_current_step_advancement(time)) real_position[patte][2]) = interpol2(mov_start, mov_end, step_adv)
dthteta = speed_rotation * 2
theta = dthteta * (get_current_step_advancement(time) - 0.5)
# theta = 0
print(theta)
# rotating the vector
x1, y1 = real_position[patte][0], real_position[patte][1] x1, y1 = real_position[patte][0], real_position[patte][1]
print(f"x1: {x1}, y1: {y1}")
x1t, y1t = x1 - x0, y1 - y0
x2t, y2t = x1t * cos(theta) + y1t * sin(theta), x1t * sin(theta) + y1t * cos(theta)
x2, y2 = x2t + x0, y2t + y0
print(f"x2: {x2}, y2: {y2}")
if mov_index_start == 1:
# theta += time
# xp = d * cos(theta) + real_position[patte][0]
real_position[patte][0] = x2
real_position[patte][1] = y2
# print( circle_radius = sqrt((center_x - x1) ** 2 + (center_y - y1) ** 2)
# f"[{patte}] [{get_current_step(time)}->{get_next_step(time)}], start: {mov_start}, end: {mov_end}, current ({real_position[patte][0]}, {real_position[patte][1]}, {real_position[patte][2]})") cur_theta = acos((x1 - center_x) / circle_radius)
if y1 < center_y:
cur_theta *= -1
if speed_r != 0:
speed = sqrt(speed_x ** 2 + speed_y ** 2) * (speed_r / abs(speed_r))
else:
speed = sqrt(speed_x ** 2 + speed_y ** 2)
if speed == 0:
speed = speed_r
dthteta = speed * (1 / max_radius * max_dist)
legsLogger.info(f"speed: {speed}, dthteta: {dthteta}")
if mov_index_start == 0:
# midair part 1
theta_offset = dthteta * (- step_adv)
elif mov_index_start == 1:
# moving part
theta_offset = dthteta * (step_adv - 0.5) * 2
else:
# midair part 1
theta_offset = dthteta * (1 - step_adv)
wanted_theta = cur_theta + theta_offset
wanted_x = center_x + circle_radius * cos(wanted_theta)
wanted_y = center_y + circle_radius * sin(wanted_theta)
real_position[patte][0] = wanted_x
real_position[patte][1] = wanted_y
if REAL:
return legs(real_position)
return legs(real_position), [center_x, center_y]
def jump(sy):
"""
Make the robot jump (at least we wished)
"""
offset = np.array([
[0, 0, -0.15],
[0, 0, -0.15],
[0, 0, -0.15],
[0, 0, -0.15],
[0, 0, -0.15],
[0, 0, -0.15]
])
offset *= sy
return legs(neutral_position + offset)
def dino_naive(t, speed_x, speed_y, hz, hy, hx):
"""
based on walk naive, but with a head and a tail
"""
slider_max = 0.200
real_position = np.copy(neutral_position)
movement_x = np.array([
[0.00, 0, 0],
[0.04, 0, 0],
[-0.04, 0, 0],
])
movement_y = np.array([
[0.0, 0, 0],
[0, 0.04, 0],
[0, -0.04, 0],
])
movement_z = np.array([
[0, 0, 0.08],
[0, 0, -0.02],
[0, 0, -0.02]
])
# duration of each step of the movement
step_duration = np.array([0.05, 0.3, 0.05])
step_count = len(movement_z)
movement_duration = np.sum(step_duration)
assert len(
step_duration) == step_count, f"all movements steps must have a length, currently, {len(step_duration)}/{step_count} have them"
def get_next_step(t):
return floor((get_current_step(t, step_duration, movement_duration) + 1) % step_count)
offsets = np.array([0, 1 / 3, 2 / 3, 0, 1 / 3, 2 / 3]) * movement_duration # offset between each leg
assert len(offsets) == num_patte, f"all offsets must be set, currently, {len(offsets)}/{num_patte} have them"
for patte in range(num_patte):
if patte in [2, 5]:
continue
time = t + offsets[patte]
mov_index_start = get_current_step(time, step_duration, movement_duration)
mov_index_end = get_next_step(time)
mov_start_x = normalize(movement_x[mov_index_start], slider_max, speed_x)
mov_end_x = normalize(movement_x[mov_index_end], slider_max, speed_x)
mov_start_y = normalize(movement_y[mov_index_start], slider_max, speed_y)
mov_end_y = normalize(movement_y[mov_index_end], slider_max, speed_y)
mov_start_z = movement_z[mov_index_start]
mov_end_z = movement_z[mov_index_end]
mov_start = neutral_position[patte] + mov_start_z + mov_start_x + mov_start_y
mov_end = neutral_position[patte] + mov_end_z + mov_end_x + mov_end_y
(real_position[patte][0],
real_position[patte][1],
real_position[patte][2]) = interpol2(mov_start, mov_end,
get_current_step_advancement(time, movement_duration, step_duration,
mov_index_start))
real_position[2][2] = -0.1
real_position[2][0] = -0.28
real_position[5][2] = 0.05 + hz
real_position[5][0] = 0.25 + hx
real_position[5][1] = hy
return legs(real_position) return legs(real_position)
walk = walkV1
if __name__ == "__main__": if __name__ == "__main__":
print("N'exécutez pas ce fichier, mais simulator.py") print("N'exécutez pas ce fichier, mais simulator.py")

47
logs.py Normal file
View File

@ -0,0 +1,47 @@
import logging
class ColorFormatter(logging.Formatter):
"""Custom formatter with colored output based on log level"""
# ANSI color codes
GREY = "\x1b[38;21m"
GREEN = "\x1b[92m"
YELLOW = "\x1b[93m"
RED = "\x1b[91m"
BOLD_RED = "\x1b[31;1m"
RESET = "\x1b[0m"
# Format including function name
FORMAT = "%(asctime)s | %(funcName)s | %(message)s"
FORMATS = {
logging.DEBUG: GREY + FORMAT + RESET,
logging.INFO: GREEN + FORMAT + RESET,
logging.WARNING: YELLOW + FORMAT + RESET,
logging.ERROR: RED + FORMAT + RESET,
logging.CRITICAL: BOLD_RED + FORMAT + RESET
}
def format(self, record):
log_format = self.FORMATS.get(record.levelno)
formatter = logging.Formatter(log_format)
return formatter.format(record)
def setup_logger(name, level=logging.INFO):
"""Set up a logger with the custom color formatter"""
logger = logging.getLogger(name)
logger.setLevel(level)
# Create console handler
console_handler = logging.StreamHandler()
console_handler.setLevel(level)
# Add formatter to console handler
console_handler.setFormatter(ColorFormatter())
# Add console handler to logger
logger.addHandler(console_handler)
return logger

View File

@ -3,6 +3,7 @@ import argparse
import math import math
import time import time
import pybullet as p import pybullet as p
if __package__ is None or __package__ == '': if __package__ is None or __package__ == '':
import control import control
else: else:
@ -14,6 +15,7 @@ dirname = os.path.dirname(__file__) + '/models/'
n_motors = 0 n_motors = 0
jointsMap = [] jointsMap = []
def init(): def init():
"""Initialise le simulateur""" """Initialise le simulateur"""
# Instanciation de Bullet # Instanciation de Bullet
@ -21,7 +23,7 @@ def init():
p.setGravity(0, 0, -10) p.setGravity(0, 0, -10)
# Chargement du sol # Chargement du sol
planeId = p.loadURDF(dirname+'/plane.urdf') planeId = p.loadURDF(dirname + '/plane.urdf')
p.setPhysicsEngineParameter(fixedTimeStep=dt) p.setPhysicsEngineParameter(fixedTimeStep=dt)
@ -29,7 +31,7 @@ def init():
def loadModel(name, fixed=False, startPos=[0., 0., 0.1], startOrientation=[0., 0., 0.]): def loadModel(name, fixed=False, startPos=[0., 0., 0.1], startOrientation=[0., 0., 0.]):
"""Charge un modèle""" """Charge un modèle"""
startOrientation = p.getQuaternionFromEuler(startOrientation) startOrientation = p.getQuaternionFromEuler(startOrientation)
model = p.loadURDF(dirname+"/"+name+"/robot.urdf", model = p.loadURDF(dirname + "/" + name + "/robot.urdf",
startPos, startOrientation, useFixedBase=fixed) startPos, startOrientation, useFixedBase=fixed)
return model return model
@ -43,7 +45,7 @@ def setJoints(robot, joints):
joints {list} -- liste des positions cibles (rad) joints {list} -- liste des positions cibles (rad)
""" """
global jointsMap global jointsMap
for k in range(len(joints)): for k in range(len(joints)):
jointInfo = p.getJointInfo(robot, jointsMap[k]) jointInfo = p.getJointInfo(robot, jointsMap[k])
p.setJointMotorControl2( p.setJointMotorControl2(
@ -60,6 +62,7 @@ def inverseControls(name='', x_default=0.15, y_default=0.):
return target_x, target_y, target_z, target return target_x, target_y, target_z, target
def directControls(): def directControls():
alpha = p.addUserDebugParameter('alpha', -math.pi, math.pi, 0) alpha = p.addUserDebugParameter('alpha', -math.pi, math.pi, 0)
beta = p.addUserDebugParameter('beta', -math.pi, math.pi, 0) beta = p.addUserDebugParameter('beta', -math.pi, math.pi, 0)
@ -78,6 +81,7 @@ def inverseUpdate(controls):
return x, y, z return x, y, z
def tick(): def tick():
global t global t
@ -85,6 +89,7 @@ def tick():
p.stepSimulation() p.stepSimulation()
time.sleep(dt) time.sleep(dt)
if __name__ == "__main__": if __name__ == "__main__":
# Arguments # Arguments
parser = argparse.ArgumentParser(prog="Quadruped") parser = argparse.ArgumentParser(prog="Quadruped")
@ -104,11 +109,11 @@ if __name__ == "__main__":
if robot == 'quadruped': if robot == 'quadruped':
oneLegStartPos = [-0.04, 0., floatHeight] oneLegStartPos = [-0.04, 0., floatHeight]
oneLegstartOrientation = [0., 0., math.pi + math.pi/4] oneLegstartOrientation = [0., 0., math.pi + math.pi / 4]
n_motors = 12 n_motors = 12
jointsMap = [0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14] jointsMap = [0, 1, 2, 4, 5, 6, 8, 9, 10, 12, 13, 14]
else: else:
oneLegstartOrientation = [0., 0., -math.pi/2] oneLegstartOrientation = [0., 0., -math.pi / 2]
oneLegStartPos = [-0.079, 0.032, floatHeight] oneLegStartPos = [-0.079, 0.032, floatHeight]
n_motors = 18 n_motors = 18
jointsMap = list(range(18)) jointsMap = list(range(18))
@ -149,8 +154,8 @@ if __name__ == "__main__":
inverseControls('leg4_', -0.05, -0.24), inverseControls('leg4_', -0.05, -0.24),
inverseControls('leg5_', 0.05, -0.24), inverseControls('leg5_', 0.05, -0.24),
inverseControls('leg6_', 0.24, 0.) inverseControls('leg6_', 0.24, 0.)
] ]
elif mode == 'walk': elif mode == 'walk':
speed_x = p.addUserDebugParameter('speed_x', -0.2, 0.2, 0.0) speed_x = p.addUserDebugParameter('speed_x', -0.2, 0.2, 0.0)
speed_y = p.addUserDebugParameter('speed_y', -0.2, 0.2, 0.0) speed_y = p.addUserDebugParameter('speed_y', -0.2, 0.2, 0.0)
@ -161,6 +166,8 @@ if __name__ == "__main__":
robot = loadModel(robot, fixed, startPos, startOrientation) robot = loadModel(robot, fixed, startPos, startOrientation)
target = loadModel('target2', True)
# Boucle principale # Boucle principale
while True: while True:
if mode == 'motors': if mode == 'motors':
@ -168,23 +175,25 @@ if __name__ == "__main__":
for entry in motors_sliders: for entry in motors_sliders:
joints.append(p.readUserDebugParameter(entry)) joints.append(p.readUserDebugParameter(entry))
elif mode == 'inverse': elif mode == 'inverse':
joints = control.inverse(*inverseUpdate(leg)) + [0]*(n_motors-3) joints = control.inverse(*inverseUpdate(leg)) + [0] * (n_motors - 3)
elif mode == 'direct': elif mode == 'direct':
alpha_slider, beta_slider, gamma_slider, target = leg alpha_slider, beta_slider, gamma_slider, target = leg
alpha = p.readUserDebugParameter(alpha_slider) alpha = p.readUserDebugParameter(alpha_slider)
beta = p.readUserDebugParameter(beta_slider) beta = p.readUserDebugParameter(beta_slider)
gamma = p.readUserDebugParameter(gamma_slider) gamma = p.readUserDebugParameter(gamma_slider)
joints = [alpha, beta, gamma] + [0]*(n_motors-3) joints = [alpha, beta, gamma] + [0] * (n_motors - 3)
x, y, z = control.direct(alpha, beta, gamma) x, y, z = control.direct(alpha, beta, gamma)
p.resetBasePositionAndOrientation(target, [x, y, z + floatHeight], p.resetBasePositionAndOrientation(target, [x, y, z + floatHeight],
p.getQuaternionFromEuler([0, 0, 0])) p.getQuaternionFromEuler([0, 0, 0]))
elif mode == 'draw': elif mode == 'draw':
joints = control.draw(t) + [0]*(n_motors-3) joints = control.draw(t) + [0] * (n_motors - 3)
def getLegTip(): def getLegTip():
res = p.getLinkState(robot, 3) res = p.getLinkState(robot, 3)
return res[0] return res[0]
if lastLine is None: if lastLine is None:
lastLine = time.time(), getLegTip() lastLine = time.time(), getLegTip()
elif time.time() - lastLine[0] > 0.1: elif time.time() - lastLine[0] > 0.1:
@ -201,7 +210,16 @@ if __name__ == "__main__":
x = p.readUserDebugParameter(speed_x) x = p.readUserDebugParameter(speed_x)
y = p.readUserDebugParameter(speed_y) y = p.readUserDebugParameter(speed_y)
rotation = p.readUserDebugParameter(speed_rotation) rotation = p.readUserDebugParameter(speed_rotation)
joints = control.walk(t, x, y, rotation) data = control.walk(t, x, y, rotation)
if len(data) == 2: # When we receive the position of a target
joints = data[0]
x, y = data[1]
z = 0
else:
joints = data
x, y, z = 0, 0, -5 # We hide the target under the map
p.resetBasePositionAndOrientation(target, [x, y, z],
p.getQuaternionFromEuler([0, 0, 0]))
elif mode == 'sandbox': elif mode == 'sandbox':
joints = control.sandbox(t) joints = control.sandbox(t)

57
xbox.py Normal file
View File

@ -0,0 +1,57 @@
import pygame
class Xbox:
def __init__(self, controllerLogger):
self.logger = controllerLogger
self.mode_count = 2
self.mode = 0
pygame.init()
self.controllers = []
for i in range(0, pygame.joystick.get_count()):
self.controllers.append(pygame.joystick.Joystick(i))
self.controllers[-1].init()
self.logger.info(f"Detected controller {self.controllers[-1].get_name()}")
if len(self.controllers) == 0:
self.logger.critical("No controllers detected. Can't initialize remote control.")
self.initialized = False
else:
self.initialized = True
self.data = {"x1": 0, "x2": 0, "y1": 0, "y2": 0, "up": 0, "down": 0, "left": 0, "right": 0, "r1": 0, "r2": 0,
"r3": 0,
"l1": 0, "l2": 0, "l3": 0}
def get_data(self):
for event in pygame.event.get():
event = dict(event.dict)
keys = event.keys()
try:
if "axis" in keys:
axis = event["axis"]
if axis in [1, 4]:
value = -event["value"]
if abs(value) < 0.2:
value = 0
self.data[{0: "x1", 1: "y1", 4: "y2", 3: "x2", 5: "r2", 2: "l2"}[event["axis"]]] = value
else:
value = event["value"]
if abs(value) < 0.2:
value = 0
self.data[{0: "x1", 1: "y1", 4: "y2", 3: "x2", 5: "r2", 2: "l2"}[event["axis"]]] = value
elif "button" in keys:
pass
elif "joy" in keys: # To manage arrows
data = event["value"][0]
if data != 0:
self.mode += data
self.mode %= self.mode_count
self.logger.info(f"Switched mode ({data}). New mode: {self.mode}")
else:
print(event)
except Exception as e:
print(event)
print(e)
return self.data