├── README.md ├── prototypes ├── phase_generator.ipynb └── trajectory_planner.ipynb ├── quadruped.ipynb └── robotviz └── robotviz.h /README.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | Install miniconda: 4 | 5 | ``` 6 | cd /tmp 7 | wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh 8 | bash Miniconda3-latest-Linux-x86_64.sh 9 | export PATH="$HOME/miniconda3/bin:$PATH" 10 | ``` 11 | 12 | Install the ros environment: 13 | 14 | ``` 15 | . $HOME/miniconda3/etc/profile.d/conda.sh 16 | 17 | yes | conda create --name cheetah --channel conda-forge \ 18 | ros-core \ 19 | ros-actionlib \ 20 | ros-dynamic-reconfigure \ 21 | xeus-cling \ 22 | xplot \ 23 | widgetsnbextension \ 24 | "bqplot>=0.11.4,<0.12" \ 25 | python=3.6 26 | 27 | conda activate cheetah 28 | 29 | yes | pip install jupyter 30 | ``` -------------------------------------------------------------------------------- /prototypes/phase_generator.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "name": "phase_generator", 7 | "version": "0.3.2", 8 | "provenance": [] 9 | }, 10 | "kernelspec": { 11 | "name": "python3", 12 | "display_name": "Python 3" 13 | } 14 | }, 15 | "cells": [ 16 | { 17 | "cell_type": "code", 18 | "metadata": { 19 | "id": "3vRKlC_KXf58", 20 | "colab_type": "code", 21 | "colab": {} 22 | }, 23 | "source": [ 24 | "import numpy as np\n", 25 | "import matplotlib.pyplot as plt\n", 26 | "import math\n", 27 | "import time\t" 28 | ], 29 | "execution_count": 0, 30 | "outputs": [] 31 | }, 32 | { 33 | "cell_type": "code", 34 | "metadata": { 35 | "id": "VOR-sQyeXt39", 36 | "colab_type": "code", 37 | "colab": {} 38 | }, 39 | "source": [ 40 | "class Gait():\n", 41 | " def __init__(self, max_leg_displacement):\n", 42 | " self.max_leg_displacement = max_leg_displacement\n", 43 | " self.leg_clocks = [0, 0, 0,0]\n", 44 | " self.last_touchdown = 0\n", 45 | " self.phase_gen_started = False\n", 46 | "\n", 47 | " def now(self):\n", 48 | " return int(round(time.time() * 1000))\n", 49 | "\n", 50 | " def generate(self, target_velocity_x):\n", 51 | " now = self.now()\n", 52 | " elapsed_time_ref = 0\n", 53 | " swing_phase_period = 250 \n", 54 | " stance_phase_period = (self.max_leg_displacement / target_velocity_x) * 1000\n", 55 | " stride_phase_period = swing_phase_period + stance_phase_period\n", 56 | "\n", 57 | " leg_clocks = [0, 0, 0, 0]\n", 58 | " swing_phase_signals = [0, 0, 0, 0]\n", 59 | " stance_phase_signals = [0, 0, 0, 0]\n", 60 | "\n", 61 | " if not self.phase_gen_started:\n", 62 | " self.phase_gen_started = True\n", 63 | " self.last_touchdown = now\n", 64 | " \n", 65 | " if now - self.last_touchdown >= stride_phase_period:\n", 66 | " self.last_touchdown = now\n", 67 | " \n", 68 | " if elapsed_time_ref > stride_phase_period:\n", 69 | " elapsed_time_ref = stride_phase_period\n", 70 | " else:\n", 71 | " elapsed_time_ref = now - self.last_touchdown\n", 72 | " \n", 73 | " leg_clocks[0] = elapsed_time_ref - (0.0 * stride_phase_period)\n", 74 | " leg_clocks[1] = elapsed_time_ref - (0.5 * stride_phase_period)\n", 75 | " leg_clocks[2] = elapsed_time_ref - (0.5 * stride_phase_period)\n", 76 | " leg_clocks[3] = elapsed_time_ref - (0.0 * stride_phase_period)\n", 77 | "\n", 78 | " for i in range(4):\n", 79 | " \n", 80 | " if leg_clocks[i] > 0 and leg_clocks[i] < stance_phase_period:\n", 81 | " stance_phase_signals[i] = leg_clocks[i] / stance_phase_period\n", 82 | " else:\n", 83 | " stance_phase_signals[i] = 0\n", 84 | "\n", 85 | " if leg_clocks[i] > -swing_phase_period and leg_clocks[i] < 0:\n", 86 | " swing_phase_signals[i] = (leg_clocks[i] + swing_phase_period) / swing_phase_period\n", 87 | " \n", 88 | " elif leg_clocks[i] > stance_phase_period and leg_clocks[i] < stride_phase_period:\n", 89 | " swing_phase_signals[i] = (leg_clocks[i] - stance_phase_period) / swing_phase_period \n", 90 | "\n", 91 | " else:\n", 92 | " swing_phase_signals[i] = 0\n", 93 | "\n", 94 | " return stance_phase_signals, swing_phase_signals, now " 95 | ], 96 | "execution_count": 0, 97 | "outputs": [] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "metadata": { 102 | "id": "7ifL2LB4X8Os", 103 | "colab_type": "code", 104 | "outputId": "9ce9defb-5e21-4e54-bfba-0fbbd1b7c6b4", 105 | "colab": { 106 | "base_uri": "https://localhost:8080/", 107 | "height": 282 108 | } 109 | }, 110 | "source": [ 111 | "FREQ = 100\n", 112 | "MAX_DISPLACEMENT = 0.15\n", 113 | "VEL = 2.0\n", 114 | "g = Gait(MAX_DISPLACEMENT)\n", 115 | "has_started = False\n", 116 | "time_started = 0\n", 117 | "l_swing_signals = []\n", 118 | "r_swing_signals = []\n", 119 | "l_stance_signals = []\n", 120 | "r_stance_signals = []\n", 121 | "collected_time = []\n", 122 | "\n", 123 | "while True:\n", 124 | " if not has_started:\n", 125 | " has_started = True\n", 126 | " time_started = g.now()\n", 127 | "\n", 128 | " stance_signals = []\n", 129 | " swing_signals = []\n", 130 | " current_time = 0\n", 131 | "\n", 132 | " stance_signals, swing_signals, current_time = g.generate(VEL)\n", 133 | " \n", 134 | " l_stance_signals.append(stance_signals[0])\n", 135 | " l_swing_signals.append(swing_signals[0])\n", 136 | "\n", 137 | " r_stance_signals.append(stance_signals[1])\n", 138 | " r_swing_signals.append(swing_signals[1])\n", 139 | "\n", 140 | " collected_time.append(current_time)\n", 141 | "\n", 142 | " if g.now() - time_started > 700:\n", 143 | " plt.plot(collected_time, l_swing_signals, 'r')\n", 144 | " plt.plot(collected_time, r_swing_signals, 'b')\n", 145 | " plt.plot(collected_time, l_stance_signals, 'r')\n", 146 | " plt.plot(collected_time, r_stance_signals, 'b')\n", 147 | " plt.show()\n", 148 | " break\n", 149 | "\n", 150 | " time.sleep(1 / FREQ)" 151 | ], 152 | "execution_count": 5, 153 | "outputs": [ 154 | { 155 | "output_type": "display_data", 156 | "data": { 157 | "image/png": "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\n", 158 | "text/plain": [ 159 | "
" 160 | ] 161 | }, 162 | "metadata": { 163 | "tags": [] 164 | } 165 | } 166 | ] 167 | }, 168 | { 169 | "cell_type": "code", 170 | "metadata": { 171 | "id": "os93xtJ-zVRX", 172 | "colab_type": "code", 173 | "colab": {} 174 | }, 175 | "source": [ 176 | "" 177 | ], 178 | "execution_count": 0, 179 | "outputs": [] 180 | } 181 | ] 182 | } -------------------------------------------------------------------------------- /prototypes/trajectory_planner.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "nbformat": 4, 3 | "nbformat_minor": 0, 4 | "metadata": { 5 | "colab": { 6 | "name": "trajectory_planner", 7 | "version": "0.3.2", 8 | "provenance": [], 9 | "collapsed_sections": [] 10 | }, 11 | "kernelspec": { 12 | "name": "python3", 13 | "display_name": "Python 3" 14 | } 15 | }, 16 | "cells": [ 17 | { 18 | "cell_type": "code", 19 | "metadata": { 20 | "id": "o2Y7rq6TSxFy", 21 | "colab_type": "code", 22 | "colab": {} 23 | }, 24 | "source": [ 25 | "import matplotlib.pyplot as plt\n", 26 | "import math" 27 | ], 28 | "execution_count": 0, 29 | "outputs": [] 30 | }, 31 | { 32 | "cell_type": "code", 33 | "metadata": { 34 | "id": "RlqE_xPBZt6H", 35 | "colab_type": "code", 36 | "colab": {} 37 | }, 38 | "source": [ 39 | "def scale_control_points(leg_length, max_foot_displacement):\n", 40 | " control_points_x = [-0.15, -0.2805,-0.3,-0.3,-0.3, 0.0, 0.0 , 0.0, 0.3032, 0.3032, 0.2826, 0.15]\n", 41 | " control_points_y = [ 0.5, 0.5, 0.3611, 0.3611, 0.3611, 0.3611, 0.3611, 0.3214, 0.3214, 0.3214, 0.5, 0.5]\n", 42 | " total_control_points = len(control_points_x)\n", 43 | " leg_ratio = leg_length / 0.444\n", 44 | "\n", 45 | " for i in range(12):\n", 46 | " if i == 0:\n", 47 | " control_points_x[i] = -max_foot_displacement / 2.0\n", 48 | " elif i == 11:\n", 49 | " control_points_x[i] = max_foot_displacement / 2.0\n", 50 | " else:\n", 51 | " control_points_x[i] = control_points_x[i] * leg_ratio\n", 52 | "\n", 53 | " control_points_y[i] = (control_points_y[i] * leg_ratio) - (0.5 * leg_ratio)\n", 54 | " \n", 55 | " return control_points_x, control_points_y" 56 | ], 57 | "execution_count": 0, 58 | "outputs": [] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "metadata": { 63 | "id": "r_S0DIb0a2vI", 64 | "colab_type": "code", 65 | "outputId": "fd7af44e-9a45-452e-eb25-a0e922d64bc0", 66 | "colab": { 67 | "base_uri": "https://localhost:8080/", 68 | "height": 71 69 | } 70 | }, 71 | "source": [ 72 | "LEG_LENGTH = 0.282\n", 73 | "MAX_FOOT_DISPLACEMENT = 0.125\n", 74 | "control_points_x, control_points_y = scale_control_points(LEG_LENGTH, MAX_FOOT_DISPLACEMENT)\n", 75 | "print(\"CONTROL POINTS X: {}\" .format(control_points_x))\n", 76 | "print(\"CONTROL POINTS Y: {}\" .format(control_points_y))" 77 | ], 78 | "execution_count": 81, 79 | "outputs": [ 80 | { 81 | "output_type": "stream", 82 | "text": [ 83 | "CONTROL POINTS X: [-0.0625, -0.17815540540540542, -0.1905405405405405, -0.1905405405405405, -0.1905405405405405, 0.0, 0.0, 0.0, 0.19257297297297296, 0.19257297297297296, 0.17948918918918919, 0.0625]\n", 84 | "CONTROL POINTS Y: [0.0, 0.0, -0.08822027027027027, -0.08822027027027027, -0.08822027027027027, -0.08822027027027027, -0.08822027027027027, -0.11343513513513512, -0.11343513513513512, -0.11343513513513512, 0.0, 0.0]\n" 85 | ], 86 | "name": "stdout" 87 | } 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "metadata": { 93 | "id": "GosiDy0jWH2u", 94 | "colab_type": "code", 95 | "colab": {} 96 | }, 97 | "source": [ 98 | "class TrajectoryPlanner:\n", 99 | " def __init__(self, max_foot_displacement, stance_depth):\n", 100 | " self.max_foot_displacement = max_foot_displacement\n", 101 | " self.stance_depth = stance_depth\n", 102 | " self.control_points_x = [-0.0625, -0.08415, -0.09, -0.09, -0.09, 0.0, 0.0, 0.0, 0.09096, 0.09096, 0.08478000000000001, 0.0625]\n", 103 | " self.control_points_y = [0.0, 0.0, -0.04167, -0.04167, -0.04167, -0.04167, -0.04167, -0.05357999999999999, -0.05357999999999999, -0.05357999999999999, 0.0, 0.0]\n", 104 | " self.total_control_points = len(self.control_points_x)\n", 105 | " self.factorial = [1.0,1.0,2.0,6.0,24.0,120.0,720.0,5040.0,40320.0,362880.0,3628800.0,39916800.0,479001600.0]\n", 106 | " \n", 107 | " self.control_points_x[0] = -self.max_foot_displacement / 2\n", 108 | " self.control_points_x[self.total_control_points - 1] = self.max_foot_displacement / 2\n", 109 | " \n", 110 | " def generate_swing(self, phase_signal):\n", 111 | " n = self.total_control_points - 1\n", 112 | " x = 0\n", 113 | " y = 0\n", 114 | "\n", 115 | " for i in range(self.total_control_points):\n", 116 | " coeff = self.factorial[n] / (self.factorial[i] * self.factorial[n - i])\n", 117 | "\n", 118 | " x = x + (coeff * pow(phase_signal, i) * pow((1 - phase_signal), (n - i)) * self.control_points_x[i])\n", 119 | " y = y + (coeff * pow(phase_signal, i) * pow((1 - phase_signal), (n - i)) * self.control_points_y[i])\n", 120 | "\n", 121 | " return x, y\n", 122 | "\n", 123 | " def generate_stance(self, phase_signal):\n", 124 | " x = 0\n", 125 | " y = 0\n", 126 | " x = (self.max_foot_displacement / 2) * (1 - (2 * phase_signal)) \n", 127 | " y = self.stance_depth * math.cos((3.1416 * x) / self.max_foot_displacement)\n", 128 | " return x, y" 129 | ], 130 | "execution_count": 0, 131 | "outputs": [] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "metadata": { 136 | "id": "RSgCB1OoUlg7", 137 | "colab_type": "code", 138 | "outputId": "9831dfa5-69d2-49da-dfb7-478acc36425d", 139 | "colab": { 140 | "base_uri": "https://localhost:8080/", 141 | "height": 269 142 | } 143 | }, 144 | "source": [ 145 | "LEG_LENGTH = 0.282\n", 146 | "MAX_DISPLACEMENT = 0.125\n", 147 | "STANCE_DEPTH = 0.005\n", 148 | "\n", 149 | "swing_x_points = []\n", 150 | "swing_y_points = []\n", 151 | "\n", 152 | "stance_x_points = []\n", 153 | "stance_y_points = []\n", 154 | "\n", 155 | "tp = TrajectoryPlanner(MAX_DISPLACEMENT, STANCE_DEPTH)\n", 156 | "#fig = plt.figure()\n", 157 | "#ax = fig.gca(projection='2d')\n", 158 | "plt.ylim([0, 0.3])\n", 159 | "plt.xlim([-0.2, 0.2])\n", 160 | "plt.grid(linestyle='-', linewidth='0.5', color='gray')\n", 161 | "\n", 162 | "for i in range(101):\n", 163 | " phase_magnitude = i / 100\n", 164 | " x_swing, y_swing = tp.generate_swing(phase_magnitude)\n", 165 | " x_stance, y_stance = tp.generate_stance(phase_magnitude)\n", 166 | " ref_x = 0.0\n", 167 | " ref_y = 0.28\n", 168 | " swing_x_points.append(ref_x - x_swing)\n", 169 | " swing_y_points.append(ref_y + y_swing)\n", 170 | " stance_x_points.append(ref_x - x_stance)\n", 171 | " stance_y_points.append(ref_y + y_stance)\n", 172 | "\n", 173 | "plt.plot(swing_x_points, swing_y_points)\n", 174 | "plt.plot(stance_x_points, stance_y_points)\n", 175 | "\n", 176 | "plt.show()" 177 | ], 178 | "execution_count": 83, 179 | "outputs": [ 180 | { 181 | "output_type": "display_data", 182 | "data": { 183 | "image/png": "iVBORw0KGgoAAAANSUhEUgAAAYUAAAD8CAYAAACYebj1AAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAHZhJREFUeJzt3XlwlPed5/H3V61bQkhCYLA4LJnD\nhtixE8Dx+JJjgvE4NtmJMyEz2SEbT8hh12TWNVNjyrt2halUTZKaSaV2nY3ZHXJOgh0n8TBZYuKD\ndk18QsDGBg9GYBmkcCNAIHS09N0/noeHtlagRnpaLcHnVfVUP8fvefrbT0vP5zm6nzZ3R0REBCAv\n1wWIiMjIoVAQEZGIQkFERCIKBRERiSgUREQkolAQEZFIRqFgZovMbLuZNZrZg/1M/5KZvWlmr5vZ\n78xsdtq05eF8283s9jiLFxGReNlA31MwswTwDvAxoBnYAHzG3beltalw9+Nh/93AV9x9URgOPwPm\nA5cCzwIz3b0nGy9GRESGJpMjhflAo7vvcvcuYDWwOL3B6UAIlQGnk2YxsNrdO939XaAxXJ6IiIxA\n+Rm0qQX2pA03A9f1bWRm9wEPAIXAR9PmfaXPvLX9zLsMWAZQXFz84alTp2ZSe051d3dTUFCQ6zIG\npDrjpTrjNRrqHA01ArzzzjuH3H38UJeTSShkxN0fBR41sz8D/huw9DzmXQmsBJg1a5Zv3749rrKy\nJplM0tDQkOsyBqQ646U64zUa6hwNNQKY2XtxLCeT00ctwJS04cnhuLNZDXxikPOKiEgOZRIKG4AZ\nZlZnZoXAEmBNegMzm5E2eCewI+xfAywxsyIzqwNmAK8NvWwREcmGAU8fuXvKzO4H1gEJYJW7bzWz\nFcBGd18D3G9mC4BuoJXw1FHY7glgG5AC7tMnj0RERq6Mrim4+1pgbZ9xD6f1f/Uc834d+PpgCxQR\nkeGjbzSLiEhEoSAiIhGFgoiIRBQKIiISie3LayIjljt0n4LONug6AV0ng667PRif6gi7Tujphp4u\n6O2G3p6g8x7wXgDq3nsPev4dLA/yEmCJ4DFRAInCoMsvgvxiKCgJu1IoLAu7MVA0BvILc7xSRPqn\nUJDRpbsDTh6Akwfh5GFoT+tOtZ7pOo7CqaPQcSwIgyF9EtqCEDBjam9vcNOXMCQGLVEExRVQPBaK\nK6GkEkqqwq4aSsdBafhYNj7oSsdBQv+ykl36C5Pccye/+zjs3wZte6FtX/B44gCc2AcnDsKJ/cFw\nV1v/y7BEsEEtrQ4eyyfC+CugqCLY+BaFe+iFY4I99oKSM4/5JWf27hMFQZdXAHn54dGARU/zQvot\nD9yhNxV0PV3Qk4KezjNHHd2nwq79zNFJZ1vwGjrbgsA63bUfhsONYaAdO8uKsuD1lV8C5RPSHifC\nmIkwZlL4ODHWt0cuLgoFya6eVLBhP/4HON4Cx/cGj217w3F/gLZ93NjTCS/2mbdo7JmN36QPBv2n\n95qjvedwb7p47Ps23sPC7EyIFJTEt9yeVBAO7Yeh/VB4VBQ+ngiPkk7sh90vQ9v+IIj6uDFRBm9N\nhopLg27MpLC/FiomBY+l44Z/ncmIp1CQ2Ow71sEzb+/nxR2HKD30BsuPraCGoxh9frMjv/jMRmrK\nfBgzkcb9J5l+7Y1n9nbLJ0JhaW5eSK4l8qF8fNANxD04VXb66KptP7TtZd9/bGRyRV4Quju3B8Hc\n55RXygppYhIPjPsuU6tLmV9XzUevmMDkqot0vQugUJAYNLe2851nd/DLzS309DqTq0qYVzWBd+16\nqJ/B+Evr3r+HWlL1/+2hNieTTP9AQ25ewGhmduZaxIQro9GNPUkmp9/ZsycVXItJO2J7t3E7LUfa\nqCwt5PfvtfLrLXt5ZM1Wbp4xnq8umMGHplYN/+uRnFMoyKC5O09s3MOKf9tGd6/zF9dP48/mT2X6\nhHLMDPh4rkuU0xL5Z04lMReAGR8J7lDZQPBe7j7Szi82tfDTV9/jT777EouvuZSv3T2HylJ9Uupi\nolCQQenpdb72b1v50cvvcX39OL71qat12mEUMzOmjSvjgY/N5Is31/PYCzv5bnInr+46wv/67Ie4\nVkcNFw19eU3Om7vzt0++wY9efo8v3FTHv/zldQqEC0hZUT4PLJzFU/fdQEG+sWTlK6zbui/XZckw\nUSjIefvH377DLze18NcLZvDQnbPJy9MnWC5EH6gdy1NfuYErJ1Vw379s4um39ua6JBkGCgU5L8nt\nB/if6xv59NwpfPW2GQPPIKPauPIifnzvfK6ePJa/+tnrvPbukVyXJFmmUJCMtXV087dPbmHWJWP4\n2uI54cVkudCNKS5g1efmMbm6hC/95PfsPXYq1yVJFikUJGOPrt/JwbZOvnnP1RQXJHJdjgyjytJC\n/vdfzKWzu4e/+tlmenp94JlkVFIoSEYOn+jk+y++y59cW8sHp1TmuhzJgcvHl/O1xR9gQ1MrP3ip\nKdflSJYoFCQjP311N52pXr5y6+W5LkVy6JMfqqVh1ni+/cw7HGjryHU5kgUKBRmQu/Or11v4SH01\n0yeMyXU5kkNmxiN3zaGju4f/8VxjrsuRLFAoyIB2HjzJroMnufPqS3NdiowAdTVl/Om8KazesJsD\nx3W0cKFRKMiANr3XCsAfXT4ux5XISPGFm+rp7nFWb9iT61IkZgoFGdD2/W2UFCSoG1eW61JkhKir\nKeP6+nE8tbkFd30S6UKiUJAB7TvewcSxxfrmsrzPog9MZNehk+w5ou8tXEgUCjKgrlQvRfn6U5H3\nm19XDcDmPa05rkTipP90GVBRfh4d3UP5jWO5ENWPD04n7j7cnuNKJE4KBRnQ5KpSWo6eItUzxB+r\nlwtKUX6C/DyjXTsMF5SMQsHMFpnZdjNrNLMH+5n+gJltM7MtZvacmU1Lm9ZjZq+H3Zo4i5fhMefS\nCrp7nDdbzvaD8nIxauvoJtXrVBQX5LoUidGAoWBmCeBR4A5gNvAZM5vdp9lmYK67Xw08CXwzbdop\nd78m7O6OqW4ZRjdMryGRZzyte+pLmt+HH1W+cpK+0HghyeRIYT7Q6O673L0LWA0sTm/g7uvd/fSJ\nxVeAyfGWKblUXVbIx668hCc27OFEZyrX5cgI8ZNXdlNRnM9H6vX9lQtJJqFQC6R/Q6U5HHc29wK/\nSRsuNrONZvaKmX1iEDXKCPDFW+ppbe/m0fW6tYHAv77ewrNv7+eLt1yuO+ZeYGL9jWYz+yzBr4Lf\nkjZ6mru3mFk98LyZvenuO/vMtwxYBlBTU0MymYyzrKxoamq66Oq8sTaf7yV3UtnewqzqeDcEF+P6\nzKZs1vnavhSPvdHJzKo8ZvkeksnmQS9rNKzP0VBjrNz9nB1wPbAubXg5sLyfdguAt4EJ51jWD4B7\nzvV8M2fO9NFg/fr1uS4hI3HWefxUlzd8a71fu+K3vmP/8diW635xrs9sykadrSc7ffkvt/i0v/u1\nf/K7L/rRk11DXuZoWJ+joUZ3d2CjD7A9z6TL5PTRBmCGmdWZWSGwBHjfp4jM7FrgMeBudz+QNr7K\nzIrC/hrgBmDboBNMcur0L3DlmbFk5atsaT6a65JkGOw/3sE//XY7N39zPatf280XbqrjJ395HWNL\n9amjC9GAp4/cPWVm9wPrgASwyt23mtkKgmRaA3wLKAd+Hv5E424PPml0JfCYmfUSXL/4B3dXKIxi\ndTVlrF52HUtXbeBPH3uZR+6aw5J5U/TTnBeYwyc6Wb/9IGvf3MsL7xykp9dZOPsSHlg4kysmVuS6\nPMmijK4puPtaYG2fcQ+n9S84y3wvAVcNpUAZeaZPGMNT993AXz++meW/fJN1W/fxyF1zqKvRDfNG\nqwPHO9i0+yibdrfy8s7DvPWHY7hDbWUJX7ipnk/Pm6L39yIR64VmuXiMH1PEjz9/HT94qYl/euYd\nFn77BT49bwpfbphObWVJrsuTszjW3s2uQyfYefAkOw60sX1fG9v+cJwDbZ0AFCbyuGZqJV+9bQa3\nzprAVbVjdSPEi4xCQQYtL8/4/I11fPzqSXznuR08vmEPP3ttD7fPuYQ/v24aH6kfR0IblGHj7hw5\n2cXeYx1sPpBi98tNtBw9RXPrKZqPtPPekXaOtndH7QsTedSPL+PGGTXMuXQs10ypZM6lFfqI6UVO\noSBDNqGimK//p6v4yq3T+dHLTTy+YQ9r39zHJRVF/PFVk1hw5SXMu6yaQt1p9by5O8c7Uhw+0cnh\nk10cPtHJwRPhY1vQHWg709+Vfn+qTVspTORRW1VCbWUJd141iWnjSqmrKad+fBnTqkvJT+g9kfdT\nKEhsaitLWH7HlfzXBTN57u0D/GpzCz99dTfff7GJ0sIE8y6r5iP147h2aiVX1Y6lrOji+vM7vYE/\n1t5Na3sXre1dHD3df7KL1vZujoT9R8Kutb2L7p7+f8SmqrSACWOKqRlTSH1NNeMriphYUcykscW0\nNG7jrttuoKasSKd/5LxcXP+VMiyKCxLcefUk7rx6Eu1dKX634xC/azzEi42H+MbTBwEwg7pxZVwx\naQzTx5fTcShFxe5WJleWUFM+cjdkve60dXTT1pHieEc3x0+lOH6qm2P9dEfbu4LHU90cbQ/G9fT2\nv4E3g8qSAqpKC6kqK2RKdSkfnFxJdXkh48oKqS4rpKa8iOqyQsaPCR4LzrGXnzy0nQljirO1GuQC\nplCQrCotzGfhnIksnDMRgCMnu3hjz1HeaD7Kf+wNLnI+/dY+eh1WbnkJCM51jx9TxISKImrKi6gq\nDTaWFSUFlBflU1aUT0lBgpLCPAoTCQoSRn4ij0SekTDj9Kdj3aHHnZ7eoEv19NLV00tXqpfOsOvo\n7qGju4dTXT20h48nO1O0d/VwojPFyc4UJzpTUQic6Ejh6357ztc8pjifytICxpYUUFlSyKTKkmiD\nf3p8sPEvoLK0kOrwten6i4wECgUZVtVlhdx6xQRuvWJCNK4z1cMvnn6BidPn0Nx6ipbWU9G58j1H\n2nmzOTit0pXK7u855OcZpYUJysLgKStMUF6cT3VZKeXF+VQUF3B4XwsfmHU5Y0sKGFMcbOArSoJp\nlaXBOG3cZTRTKEjOFeUnuLQ8j4YrLjlnu85UDyc6Upzs7KEj1UN7Vw9dqV66wyMAd6fv7wAl8iDP\njPy8vOiIojCRR3FBHoX5eZQUJCguTFCcn8joQngyeZCGWy4fyssVGdEUCjJqFOUnKCpPMK4815WI\nXLj0eTQREYkoFEREJKJQEBGRiEJBREQiCgUREYkoFEREJKJQEBGRiEJBREQiCgUREYkoFEREJKJQ\nEBGRiEJBREQiCgUREYkoFEREJKJQEBGRiEJBREQiCgUREYkoFEREJKJQEBGRSEahYGaLzGy7mTWa\n2YP9TH/AzLaZ2RYze87MpqVNW2pmO8JuaZzFi4hIvAYMBTNLAI8CdwCzgc+Y2ew+zTYDc939auBJ\n4JvhvNXAI8B1wHzgETOriq98ERGJUyZHCvOBRnff5e5dwGpgcXoDd1/v7u3h4CvA5LD/duAZdz/i\n7q3AM8CieEoXEZG45WfQphbYkzbcTLDnfzb3Ar85x7y1fWcws2XAMoCamhqSyWQGZeVWU1OT6oyR\n6oyX6ozPaKgxTpmEQsbM7LPAXOCW85nP3VcCKwFmzZrlDQ0NcZaVFclkEtUZH9UZL9UZn9FQY5wy\nOX3UAkxJG54cjnsfM1sAPATc7e6d5zOviIiMDJmEwgZghpnVmVkhsARYk97AzK4FHiMIhANpk9YB\nC82sKrzAvDAcJyIiI9CAp4/cPWVm9xNszBPAKnffamYrgI3uvgb4FlAO/NzMAHa7+93ufsTM/p4g\nWABWuPuRrLwSEREZsoyuKbj7WmBtn3EPp/UvOMe8q4BVgy1QRESGj77RLCIiEYWCiIhEFAoiIhJR\nKIiISEShICIiEYWCiIhEFAoiIhJRKIiISEShICIiEYWCiIhEFAoiIhJRKIiISEShICIiEYWCiIhE\nFAoiIhJRKIiISEShICIiEYWCiIhEFAoiIhJRKIiISEShICIiEYWCiIhEFAoiIhJRKIiISEShICIi\nEYWCiIhEFAoiIhLJKBTMbJGZbTezRjN7sJ/pN5vZJjNLmdk9fab1mNnrYbcmrsJFRCR++QM1MLME\n8CjwMaAZ2GBma9x9W1qz3cDngL/pZxGn3P2aGGoVEZEsGzAUgPlAo7vvAjCz1cBiIAoFd28Kp/Vm\noUYRERkmmYRCLbAnbbgZuO48nqPYzDYCKeAf3P2pvg3MbBmwDKCmpoZkMnkei8+NpqYm1Rkj1Rkv\n1Rmf0VBjnDIJhaGa5u4tZlYPPG9mb7r7zvQG7r4SWAkwa9Ysb2hoGIayhiaZTKI646M646U64zMa\naoxTJheaW4ApacOTw3EZcfeW8HEXkASuPY/6RERkGGUSChuAGWZWZ2aFwBIgo08RmVmVmRWF/TXA\nDaRdixARkZFlwFBw9xRwP7AOeBt4wt23mtkKM7sbwMzmmVkz8CngMTPbGs5+JbDRzN4A1hNcU1Ao\niIiMUBldU3D3tcDaPuMeTuvfQHBaqe98LwFXDbFGEREZJvpGs4iIRBQKIiISUSiIiEhEoSAiIhGF\ngoiIRBQKIiISUSiIiEhEoSAiIhGFgoiIRBQKIiISUSiIiEhEoSAiIhGFgoiIRBQKIiISUSiIiEhE\noSAiIhGFgoiIRBQKIiISUSiIiEhEoSAiIhGFgoiIRBQKIiISUSiIiEhEoSAiIhGFgoiIRBQKIiIS\nUSiIiEgko1Aws0Vmtt3MGs3swX6m32xmm8wsZWb39Jm21Mx2hN3SuAoXEZH4DRgKZpYAHgXuAGYD\nnzGz2X2a7QY+B/y0z7zVwCPAdcB84BEzqxp62SIikg2ZHCnMBxrdfZe7dwGrgcXpDdy9yd23AL19\n5r0deMbdj7h7K/AMsCiGukVEJAvyM2hTC+xJG24m2PPPRH/z1vZtZGbLgGUANTU1JJPJDBefO01N\nTaozRqozXqozPqOhxjhlEgpZ5+4rgZUAs2bN8oaGhtwWlIFkMonqjI/qjJfqjM9oqDFOmZw+agGm\npA1PDsdlYijziojIMMskFDYAM8yszswKgSXAmgyXvw5YaGZV4QXmheE4EREZgQYMBXdPAfcTbMzf\nBp5w961mtsLM7gYws3lm1gx8CnjMzLaG8x4B/p4gWDYAK8JxIiIyAmV0TcHd1wJr+4x7OK1/A8Gp\nof7mXQWsGkKNIiIyTPSNZhERiSgUREQkolAQEZGIQkFERCIKBRERiSgUREQkolAQEZGIQkFERCIK\nBRERiSgUREQkolAQEZGIQkFERCIKBRERiSgUREQkolAQEZGIQkFERCIKBRERiSgUREQkolAQEZGI\nQkFERCIKBRERiSgUREQkolAQEZGIQkFERCIKBRERiSgUREQkolAQEZFIRqFgZovMbLuZNZrZg/1M\nLzKzx8Ppr5rZZeH4y8zslJm9Hnbfi7d8ERGJU/5ADcwsATwKfAxoBjaY2Rp335bW7F6g1d2nm9kS\n4BvAp8NpO939mpjrFhGRLMjkSGE+0Ojuu9y9C1gNLO7TZjHww7D/SeA2M7P4yhQRkeGQSSjUAnvS\nhpvDcf22cfcUcAwYF06rM7PNZvaCmd00xHpFRCSLBjx9NER7ganuftjMPgw8ZWZz3P14eiMzWwYs\nA6ipqSGZTGa5rKFrampSnTFSnfFSnfEZDTXGyt3P2QHXA+vShpcDy/u0WQdcH/bnA4cA62dZSWDu\nuZ5v5syZPhqsX78+1yVkRHXGS3XGazTUORpqdHcHNvoA2/NMukxOH20AZphZnZkVAkuANX3arAGW\nhv33AM+7u5vZ+PBCNWZWD8wAdg0mvEREJPsGPH3k7ikzu5/gaCABrHL3rWa2giCZ1gD/DPzYzBqB\nIwTBAXAzsMLMuoFe4EvufiQbL0RERIYuo2sK7r4WWNtn3MNp/R3Ap/qZ7xfAL4ZYo4iIDBN9o1lE\nRCIKBRERiSgUREQkolAQEZGIQkFERCIKBRERiSgUREQkolAQEZGIQkFERCIKBRERiSgUREQkolAQ\nEZGIQkFERCIKBRERiSgUREQkolAQEZGIQkFERCIKBRERiSgUREQkolAQEZGIQkFERCIKBRERiSgU\nREQkolAQEZGIQkFERCIKBRERiSgUREQkolAQEZFIRqFgZovMbLuZNZrZg/1MLzKzx8Ppr5rZZWnT\nlofjt5vZ7fGVLiIicRswFMwsATwK3AHMBj5jZrP7NLsXaHX36cC3gW+E884GlgBzgEXAd8PliYjI\nCJTJkcJ8oNHdd7l7F7AaWNynzWLgh2H/k8BtZmbh+NXu3unu7wKN4fJERGQEys+gTS2wJ224Gbju\nbG3cPWVmx4Bx4fhX+sxb2/cJzGwZsCwc7DSztzKqPrdqgEO5LiIDqjNeqjNeo6HO0VAjwKw4FpJJ\nKGSdu68EVgKY2UZ3n5vjkgakOuOlOuOlOuMzGmqEoM44lpPJ6aMWYEra8ORwXL9tzCwfGAscznBe\nEREZITIJhQ3ADDOrM7NCggvHa/q0WQMsDfvvAZ53dw/HLwk/nVQHzABei6d0ERGJ24Cnj8JrBPcD\n64AEsMrdt5rZCmCju68B/hn4sZk1AkcIgoOw3RPANiAF3OfuPQM85crBv5xhpTrjpTrjpTrjMxpq\nhJjqtGCHXkRERN9oFhGRNAoFERGJ5CQUzKzazJ4xsx3hY1U/ba4xs5fNbKuZbTGzT6dNqwtvp9EY\n3l6jMFd1hu2eNrOjZvbrPuN/YGbvmtnrYXfNCK1zpK3PpWGbHWa2NG18Mrxdyun1OSHG2kbFrVwG\nW6eZXWZmp9LW3fdyXOfNZrbJzFJmdk+faf2+/yOwzp609dn3wzfDXecDZrYt3FY+Z2bT0qad3/p0\n92HvgG8CD4b9DwLf6KfNTGBG2H8psBeoDIefAJaE/d8DvpyrOsNptwF3Ab/uM/4HwD0jYX0OUOeI\nWZ9ANbArfKwK+6vCaUlgbhbqSgA7gXqgEHgDmN2nzVeA74X9S4DHw/7ZYfsioC5cTiJL628odV4G\nvJXtv8XzqPMy4GrgR+n/I+d6/0dSneG0EyNofd4KlIb9X0573897febq9FH6bTF+CHyibwN3f8fd\nd4T9fwAOAOPNzICPEtxO46zzD1edYX3PAW1ZqiETg65zBK7P24Fn3P2Iu7cCzxDcNyubRsutXIZS\n53AasE53b3L3LUBvn3mH8/0fSp3DKZM617t7ezj4CsF3wmAQ6zNXoXCJu+8N+/cBl5yrsZnNJ0jI\nnQS3zzjq7qlwcr+3zshFnWfx9fCQ7ttmVhRjbemGUudIW5/93VYlvZ7vh4fr/z3Gjd1Az/m+NuG6\nSr+Vy0DzxmUodQLUmdlmM3vBzG7KUo2Z1pmNec/XUJ+r2Mw2mtkrZpatHSk4/zrvBX4zyHmzd5sL\nM3sWmNjPpIfSB9zdzeysn4s1s0nAj4Gl7t4b905PXHWexXKCjV8hwWeI/w5YMQLrjE2W6/xzd28x\nszHAL4D/THBYLwPbC0x198Nm9mHgKTOb4+7Hc13YKDYt/HusB543szfdfWcuCzKzzwJzgVsGu4ys\nhYK7LzjbNDPbb2aT3H1vuNE/cJZ2FcD/BR5y99M31jsMVJpZfrgnNKRbZ8RR5zmWfXqvuNPMvg/8\nzQisc6StzxagIW14MsG1BNy9JXxsM7OfEhxWxxEK53Mrl2bL3a1cBl2nByeYOwHc/fdmtpPgul0s\n98sZRJ3nmrehz7zJWKrq/7kG/d6l/T3uMrMkcC3B2Yy4ZVSnmS0g2Pm6xd070+Zt6DNv8lxPlqvT\nR+m3xVgK/GvfBhZ8AuZXwI/c/fT5bsI/7vUEt9M46/zDVee5hBu+0+ftPwFk6+6vg65zBK7PdcBC\nM6uy4NNJC4F1ZpZvZjUAZlYAfJz41udouZXLoOs0s/EW/pZJuGc7g+CiY67qPJt+3/+RVmdYX1HY\nXwPcQHDnhpzUaWbXAo8Bd7t7+s7W+a/P4bh63s/V9HHAc8AO4FmgOhw/F/g/Yf9ngW7g9bTumnBa\nPcE/XiPwc6AoV3WGw/8OHAROEZyzuz0c/zzwJsHG6ydA+Qitc6Stz8+HtTQC/yUcVwb8HtgCbAW+\nQ4yf8gH+GHiHYE/voXDcCoJ/MoDicN00huuqPm3eh8L5tgN3ZPl/Z1B1Ap8M19vrwCbgrhzXOS/8\nGzxJcMS19Vzv/0irE/ij8H/7jfDx3hzX+SywnzPbyjWDXZ+6zYWIiET0jWYREYkoFEREJKJQEBGR\niEJBREQiCgUREYkoFEREJKJQEBGRyP8D8Ud7KhcfBRgAAAAASUVORK5CYII=\n", 184 | "text/plain": [ 185 | "
" 186 | ] 187 | }, 188 | "metadata": { 189 | "tags": [] 190 | } 191 | } 192 | ] 193 | }, 194 | { 195 | "cell_type": "code", 196 | "metadata": { 197 | "id": "d-NcdEkZUYYS", 198 | "colab_type": "code", 199 | "colab": {} 200 | }, 201 | "source": [ 202 | "" 203 | ], 204 | "execution_count": 0, 205 | "outputs": [] 206 | } 207 | ] 208 | } -------------------------------------------------------------------------------- /quadruped.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "#pragma cling add_include_path(\"/home/juan/champ_ws/src/champ/champ_base/include/libchamp/include\")\n", 10 | "#pragma cling add_include_path(\"/home/juan/champ_ws/src/champ/champ_config/include\")\n", 11 | "#pragma cling add_include_path(\"/usr/include/eigen3\")" 12 | ] 13 | }, 14 | { 15 | "cell_type": "code", 16 | "execution_count": 2, 17 | "metadata": {}, 18 | "outputs": [], 19 | "source": [ 20 | "#include \"ros/ros.h\"\n", 21 | "#pragma cling load(\"roscpp\")\n", 22 | "\n", 23 | "#include \"xplot/xfigure.hpp\"\n", 24 | "#include \"xplot/xmarks.hpp\"\n", 25 | "#include \"xplot/xaxes.hpp\"\n", 26 | "#include \"geometry/geometry.h\"\n", 27 | "#include \"robotviz/robotviz.h\"\n", 28 | "\n", 29 | "#include \"ros/ros.h\"\n", 30 | "#include \"sensor_msgs/JointState.h\"\n", 31 | "#include \"quadruped_base/quadruped_base.h\"\n", 32 | "\n", 33 | "#include \"quadruped_base/quadruped_components.h\"\n", 34 | "#include \"body_controller/body_controller.h\"\n", 35 | "#include \"leg_controller/leg_controller.h\"\n", 36 | "#include \"kinematics/kinematics.h\"\n", 37 | "\n", 38 | "#include \"quadruped_description.h\"\n", 39 | "\n", 40 | "#include \"unistd.h\"\n", 41 | "#include \"iostream\"\n", 42 | "\n", 43 | "//#define USE_ROS" 44 | ] 45 | }, 46 | { 47 | "cell_type": "code", 48 | "execution_count": 3, 49 | "metadata": {}, 50 | "outputs": [], 51 | "source": [ 52 | "ros::Publisher joint_states_publisher; \n", 53 | "\n", 54 | "#ifdef USE_ROS\n", 55 | "int argc = 1;\n", 56 | "char arg0[] = \"talker\";\n", 57 | "char* argv[] = {arg0, NULL};\n", 58 | "ros::init(argc, argv, \"champ_notebook_controller\");\n", 59 | "\n", 60 | "ros::NodeHandle nh(\"\");\n", 61 | "joint_states_publisher = nh.advertise(\"/champ/joint_states\", 100);\n", 62 | "#endif\n", 63 | "\n", 64 | "std::vector joint_names;\n", 65 | "joint_names.push_back(\"lf_hip_joint\");\n", 66 | "joint_names.push_back(\"lf_upper_leg_joint\");\n", 67 | "joint_names.push_back(\"lf_lower_leg_joint\");\n", 68 | "joint_names.push_back(\"rf_hip_joint\");\n", 69 | "joint_names.push_back(\"rf_upper_leg_joint\");\n", 70 | "joint_names.push_back(\"rf_lower_leg_joint\");\n", 71 | "joint_names.push_back(\"lh_hip_joint\");\n", 72 | "joint_names.push_back(\"lh_upper_leg_joint\");\n", 73 | "joint_names.push_back(\"lh_lower_leg_joint\");\n", 74 | "joint_names.push_back(\"rh_hip_joint\");\n", 75 | "joint_names.push_back(\"rh_upper_leg_joint\");\n", 76 | "joint_names.push_back(\"rh_lower_leg_joint\");" 77 | ] 78 | }, 79 | { 80 | "cell_type": "code", 81 | "execution_count": 4, 82 | "metadata": {}, 83 | "outputs": [], 84 | "source": [ 85 | "champ::GaitConfig gait_config;\n", 86 | "\n", 87 | "gait_config.pantograph_leg = false;\n", 88 | "gait_config.max_linear_velocity_x = 0.5;\n", 89 | "gait_config.max_linear_velocity_y = 0.5;\n", 90 | "gait_config.max_angular_velocity_z = 1.0;\n", 91 | "gait_config.swing_height = 0.04;\n", 92 | "gait_config.stance_depth = 0.0;\n", 93 | "gait_config.stance_duration = 0.25;\n", 94 | "gait_config.nominal_height = 0.2;\n", 95 | "gait_config.knee_orientation = \">>\";\n", 96 | "\n", 97 | "champ::QuadrupedBase base(gait_config);\n", 98 | "champ::URDF::loadFromHeader(base);\n", 99 | "\n", 100 | "champ::BodyController body_controller(base);\n", 101 | "champ::LegController leg_controller(base);\n", 102 | "champ::Kinematics kinematics(base);" 103 | ] 104 | }, 105 | { 106 | "cell_type": "code", 107 | "execution_count": 5, 108 | "metadata": {}, 109 | "outputs": [], 110 | "source": [ 111 | "xpl::figure fig;\n", 112 | "\n", 113 | "xpl::linear_scale sx, sy;\n", 114 | "sx.min = -0.3;\n", 115 | "sx.max = 0.3;\n", 116 | "sy.min = -0.3;\n", 117 | "sy.max = 0.3;\n", 118 | "\n", 119 | "xpl::lines line(sx, sy);\n", 120 | "RobotViz robotviz(line);\n", 121 | "fig.add_mark(line);" 122 | ] 123 | }, 124 | { 125 | "cell_type": "code", 126 | "execution_count": 6, 127 | "metadata": {}, 128 | "outputs": [], 129 | "source": [ 130 | "line.colors = std::vector({\"green\"});" 131 | ] 132 | }, 133 | { 134 | "cell_type": "code", 135 | "execution_count": 7, 136 | "metadata": {}, 137 | "outputs": [], 138 | "source": [ 139 | "xpl::axis hx(sx ), hy(sy);\n", 140 | "hy.orientation = \"vertical\";\n", 141 | "fig.add_axis(hx);\n", 142 | "fig.add_axis(hy);" 143 | ] 144 | }, 145 | { 146 | "cell_type": "code", 147 | "execution_count": 8, 148 | "metadata": {}, 149 | "outputs": [], 150 | "source": [ 151 | "void publishJoints(float joint_states[12])\n", 152 | "{\n", 153 | " sensor_msgs::JointState joints_msg;\n", 154 | "\n", 155 | " joints_msg.header.stamp = ros::Time::now();\n", 156 | " joints_msg.name.resize(joint_names.size());\n", 157 | " joints_msg.position.resize(joint_names.size());\n", 158 | " joints_msg.name = joint_names;\n", 159 | "\n", 160 | " for (size_t i = 0; i < joint_names.size(); ++i)\n", 161 | " { \n", 162 | " joints_msg.position[i]= joint_states[i];\n", 163 | " }\n", 164 | " #ifdef USE_ROS\n", 165 | " joint_states_publisher.publish(joints_msg);\n", 166 | " #endif\n", 167 | "}" 168 | ] 169 | }, 170 | { 171 | "cell_type": "code", 172 | "execution_count": 9, 173 | "metadata": {}, 174 | "outputs": [ 175 | { 176 | "data": { 177 | "application/vnd.jupyter.widget-view+json": { 178 | "model_id": "18324ee625124685aade1012999fbcfa", 179 | "version_major": 2, 180 | "version_minor": 0 181 | }, 182 | "text/plain": [ 183 | "A Jupyter widget" 184 | ] 185 | }, 186 | "execution_count": 9, 187 | "metadata": {}, 188 | "output_type": "execute_result" 189 | } 190 | ], 191 | "source": [ 192 | "champ::Pose req_pose;\n", 193 | "req_pose.position.z = gait_config.nominal_height;\n", 194 | "\n", 195 | "champ::Velocities req_vel;\n", 196 | "req_vel.linear.x = 0.5;\n", 197 | "\n", 198 | "geometry::Transformation target_foot_positions[4];\n", 199 | "float target_joint_positions[12];\n", 200 | "\n", 201 | "fig" 202 | ] 203 | }, 204 | { 205 | "cell_type": "code", 206 | "execution_count": 10, 207 | "metadata": {}, 208 | "outputs": [], 209 | "source": [ 210 | "for(size_t i = 0; i < 500; i++)\n", 211 | "{ \n", 212 | " body_controller.poseCommand(target_foot_positions, req_pose);\n", 213 | " leg_controller.velocityCommand(target_foot_positions, req_vel);\n", 214 | " kinematics.inverse(target_joint_positions, target_foot_positions);\n", 215 | " \n", 216 | " robotviz.updateJoints(target_joint_positions[1], target_joint_positions[2]);\n", 217 | " //robotviz.updateJoints(-0.75, 0.75);\n", 218 | " \n", 219 | " #ifdef USE_ROS\n", 220 | " publishJoints(target_joint_positions);\n", 221 | " #endif\n", 222 | " \n", 223 | " usleep(10000);\n", 224 | "}" 225 | ] 226 | } 227 | ], 228 | "metadata": { 229 | "kernelspec": { 230 | "display_name": "C++14", 231 | "language": "C++14", 232 | "name": "xcpp14" 233 | }, 234 | "language_info": { 235 | "codemirror_mode": "text/x-c++src", 236 | "file_extension": ".cpp", 237 | "mimetype": "text/x-c++src", 238 | "name": "c++", 239 | "version": "14" 240 | } 241 | }, 242 | "nbformat": 4, 243 | "nbformat_minor": 4 244 | } 245 | -------------------------------------------------------------------------------- /robotviz/robotviz.h: -------------------------------------------------------------------------------- 1 | #ifndef ROBOTVIZ_H 2 | #define ROBOTVIZ_H 3 | //#include "quadruped_base/quadruped_leg.h" 4 | #include "Eigen/Dense" 5 | 6 | class RobotViz 7 | { 8 | xpl::lines *line_; 9 | Eigen::Vector3d knee_; 10 | Eigen::Vector3d foot_; 11 | 12 | public: 13 | RobotViz(xpl::lines &line): 14 | line_(&line), 15 | knee_{0, 0, -0.141}, 16 | foot_{0, 0, -0.141} 17 | { 18 | //line_->x = std::vector({0.0, 0.5, 1.0, 1.1, 1.2, 1.3}); 19 | //line_->y = std::vector({0.0, 0.7, 0.5, 1.1, 1.2, 1.2}); 20 | updateJoints(0,0); 21 | } 22 | 23 | Eigen::Vector3d rotate(const Eigen::Vector3d pos, const float alpha, const float phi, const float beta) 24 | { 25 | Eigen::Matrix3d Rx; 26 | Eigen::Matrix3d Ry; 27 | Eigen::Matrix3d Rz; 28 | Eigen::Vector3d xformed_pos; 29 | 30 | Rz << cos(beta), -sin(beta), 0, sin(beta), cos(beta), 0, 0, 0, 1; 31 | xformed_pos = (Rz * pos).eval(); 32 | 33 | Ry << cos(phi), 0, sin(phi), 0, 1, 0, -sin(phi), 0, cos(phi); 34 | xformed_pos = (Ry * xformed_pos).eval(); 35 | 36 | Rx << 1, 0, 0, 0, cos(alpha), -sin(alpha), 0, sin(alpha), cos(alpha); 37 | xformed_pos = (Rx * xformed_pos).eval(); 38 | 39 | return xformed_pos; 40 | } 41 | 42 | void updateJoints(float upper_leg, float lower_leg) 43 | { 44 | Eigen::Vector3d new_knee; 45 | 46 | new_knee = knee_; 47 | new_knee = rotate(new_knee, 0, upper_leg, 0); 48 | //Eigen::Matrix3d Ry ; 49 | //Ry << cos(upper_leg), 0, sin(upper_leg), 0, 1, 0, -sin(upper_leg), 0, cos(upper_leg); 50 | //new_knee = (Ry * new_knee).eval(); 51 | 52 | 53 | Eigen::Vector3d new_foot; 54 | //new_foot += new_knee; 55 | //new_foot = rotate(new_foot, 0, lower_leg, 0); 56 | new_foot = foot_; 57 | new_foot = rotate(new_foot, 0, lower_leg, 0); 58 | new_foot += knee_; 59 | new_foot = rotate(new_foot, 0, upper_leg,0); 60 | 61 | line_->x = std::vector({0, new_knee[0], new_foot[0]}); 62 | line_->y = std::vector({0, new_knee[2], new_foot[2]}); 63 | } 64 | 65 | }; 66 | 67 | #endif --------------------------------------------------------------------------------